mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
191 lines
5.8 KiB
Go
191 lines
5.8 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package handler
|
|
|
|
import (
|
|
"Wavelet/pkg/logger"
|
|
"Wavelet/pkg/response"
|
|
"Wavelet/plugins/domain/admin/errs"
|
|
"Wavelet/plugins/domain/admin/model"
|
|
"Wavelet/plugins/domain/admin/service"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// GetDBOverview 获取数据库运行概览
|
|
// @Summary 获取数据库运行概览
|
|
// @Description 获取数据库类型、版本、名称、文件大小、表数量及当前连接数,需要管理员权限
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security SessionCookie
|
|
// @Success 200 {object} response.Any{data=model.DBOverviewResponse} "获取成功"
|
|
// @Failure 401 {object} response.Any "未登录"
|
|
// @Failure 403 {object} response.Any "无管理员权限"
|
|
// @Failure 500 {object} response.Any "内部错误"
|
|
// @Router /api/v1/admin/db-manage/overview [get]
|
|
func GetDBOverview(c *gin.Context) {
|
|
overview, err := service.DatabaseOverview(c.Request.Context())
|
|
if err != nil {
|
|
response.AbortInternal(c, err.Error())
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response.OK(overview))
|
|
}
|
|
|
|
// ListDBTables 获取数据库所有表名
|
|
// @Summary 获取数据库所有表名
|
|
// @Description 返回当前数据库的所有用户自定义表名称列表,需要管理员权限
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security SessionCookie
|
|
// @Success 200 {object} response.Any{data=[]string} "获取成功"
|
|
// @Failure 401 {object} response.Any "未登录"
|
|
// @Failure 403 {object} response.Any "无管理员权限"
|
|
// @Failure 500 {object} response.Any "内部错误"
|
|
// @Router /api/v1/admin/db-manage/tables [get]
|
|
func ListDBTables(c *gin.Context) {
|
|
tables, err := service.DatabaseTableNames(c.Request.Context())
|
|
if err != nil {
|
|
response.AbortInternal(c, err.Error())
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response.OK(tables))
|
|
}
|
|
|
|
// GetDBTableData 获取数据表 data
|
|
func GetDBTableData(c *gin.Context) {
|
|
var req model.GetTableDataRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
response.AbortBadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
data, err := service.DatabaseTableData(c.Request.Context(), req)
|
|
if err != nil {
|
|
if msg, ok := errs.AsInvalidInput(err); ok {
|
|
response.AbortBadRequest(c, msg)
|
|
return
|
|
}
|
|
response.AbortInternal(c, err.Error())
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response.OK(data))
|
|
}
|
|
|
|
// ExecuteSQL 执行 SQL 查询
|
|
// @Summary 执行 SQL 查询
|
|
// @Description 在当前数据库中执行任意自定义 SQL,如果是查询语句将返回格式化后的列与数据集,否则返回受影响行数,需要管理员权限
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security SessionCookie
|
|
// @Param request body model.ExecuteSQLRequest true "SQL 请求参数"
|
|
// @Success 200 {object} response.Any{data=model.ExecuteSQLResponse} "执行完毕"
|
|
// @Failure 400 {object} response.Any "SQL 语句错误"
|
|
// @Failure 401 {object} response.Any "未登录"
|
|
// @Failure 403 {object} response.Any "无管理员权限"
|
|
// @Failure 500 {object} response.Any "内部错误"
|
|
// @Router /api/v1/admin/db-manage/query [post]
|
|
func ExecuteSQL(c *gin.Context) {
|
|
var req model.ExecuteSQLRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.AbortBadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
trimmedSQL := strings.TrimSpace(req.SQL)
|
|
if trimmedSQL == "" {
|
|
response.AbortBadRequest(c, errs.InvalidSQLStatement)
|
|
return
|
|
}
|
|
|
|
resp, err := service.ExecuteCustomSQL(c.Request.Context(), trimmedSQL)
|
|
if err != nil {
|
|
if errors.Is(err, errs.ErrDatabaseUninitialized) {
|
|
response.AbortInternal(c, err.Error())
|
|
return
|
|
}
|
|
response.AbortBadRequest(c, err.Error())
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response.OK(resp))
|
|
}
|
|
|
|
// GetDatabaseInfo 获取当前数据库类型及版本信息
|
|
// @Summary 获取数据库信息
|
|
// @Description 返回当前使用的数据库类型(sqlite/postgres)、名称/路径及版本字符串,需要管理员权限
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security SessionCookie
|
|
// @Success 200 {object} response.Any{data=model.DatabaseInfoResponse} "获取成功"
|
|
// @Failure 401 {object} response.Any "未登录"
|
|
// @Failure 403 {object} response.Any "无管理员权限"
|
|
// @Router /api/v1/admin/db-info [get]
|
|
func GetDatabaseInfo(c *gin.Context) {
|
|
c.JSON(http.StatusOK, response.OK(service.DatabaseInfo(c.Request.Context())))
|
|
}
|
|
|
|
// ExportDatabase 导出数据库
|
|
// @Summary 导出数据库
|
|
// @Description SQLite 时直接下载 .db 文件;PostgreSQL 时执行 pg_dump 并流式下载 .sql 文件,需要管理员权限
|
|
// @Tags admin
|
|
// @Produce application/octet-stream
|
|
// @Security SessionCookie
|
|
// @Success 200 {file} binary "数据库文件"
|
|
// @Failure 401 {object} response.Any "未登录"
|
|
// @Failure 403 {object} response.Any "无管理员权限"
|
|
// @Failure 500 {object} response.Any "导出失败"
|
|
// @Router /api/v1/admin/db-export [get]
|
|
func ExportDatabase(c *gin.Context) {
|
|
if !service.GetDBConfig().Enabled {
|
|
exportSQLite(c)
|
|
} else {
|
|
exportPostgres(c)
|
|
}
|
|
}
|
|
|
|
func exportSQLite(c *gin.Context) {
|
|
f, fi, err := service.OpenSQLiteExportFile()
|
|
if err != nil {
|
|
response.AbortInternal(c, err.Error())
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = f.Close()
|
|
}()
|
|
|
|
c.Header("Content-Disposition", `attachment; filename="wavelet.db"`)
|
|
c.Header("Content-Type", "application/octet-stream")
|
|
c.Header("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
|
c.Status(http.StatusOK)
|
|
http.ServeContent(c.Writer, c.Request, "wavelet.db", fi.ModTime(), f)
|
|
}
|
|
|
|
func exportPostgres(c *gin.Context) {
|
|
cmd, fileName, err := service.NewPgDumpCommand(c.Request.Context())
|
|
if err != nil {
|
|
response.AbortInternal(c, err.Error())
|
|
return
|
|
}
|
|
|
|
c.Header("Content-Disposition", `attachment; filename="`+fileName+`"`)
|
|
c.Header("Content-Type", "application/octet-stream")
|
|
c.Status(http.StatusOK)
|
|
|
|
cmd.Stdout = c.Writer
|
|
cmd.Stderr = nil
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
logger.ErrorF(c.Request.Context(), "[db-export] pg_dump failed: %v", err)
|
|
}
|
|
}
|