540 lines
14 KiB
Go
540 lines
14 KiB
Go
package system
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"sync"
|
||
"time"
|
||
|
||
"kra/internal/biz/system"
|
||
"kra/pkg/response"
|
||
"kra/pkg/utils"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// 用于token一次性存储
|
||
var (
|
||
exportTokenCache = make(map[string]interface{})
|
||
exportTokenExpiration = make(map[string]time.Time)
|
||
tokenMutex sync.RWMutex
|
||
)
|
||
|
||
// 五分钟检测窗口过期
|
||
func cleanupExpiredTokens() {
|
||
for {
|
||
time.Sleep(5 * time.Minute)
|
||
tokenMutex.Lock()
|
||
now := time.Now()
|
||
for token, expiry := range exportTokenExpiration {
|
||
if now.After(expiry) {
|
||
delete(exportTokenCache, token)
|
||
delete(exportTokenExpiration, token)
|
||
}
|
||
}
|
||
tokenMutex.Unlock()
|
||
}
|
||
}
|
||
|
||
func init() {
|
||
go cleanupExpiredTokens()
|
||
}
|
||
|
||
type ExportTemplateApi struct{}
|
||
|
||
// CreateSysExportTemplateRequest 创建导出模板请求
|
||
type CreateSysExportTemplateRequest struct {
|
||
DBName string `json:"dbName"`
|
||
Name string `json:"name" binding:"required"`
|
||
TableName string `json:"tableName"`
|
||
TemplateID string `json:"templateID"`
|
||
TemplateInfo string `json:"templateInfo"`
|
||
Limit *int `json:"limit"`
|
||
Order string `json:"order"`
|
||
Conditions []*ConditionRequest `json:"conditions"`
|
||
JoinTemplate []*JoinTemplateRequest `json:"joinTemplate"`
|
||
}
|
||
|
||
type ConditionRequest struct {
|
||
TemplateID string `json:"templateID"`
|
||
From string `json:"from"`
|
||
Column string `json:"column"`
|
||
Operator string `json:"operator"`
|
||
}
|
||
|
||
type JoinTemplateRequest struct {
|
||
TemplateID string `json:"templateID"`
|
||
Joins string `json:"joins"`
|
||
Table string `json:"table"`
|
||
On string `json:"on"`
|
||
}
|
||
|
||
// UpdateSysExportTemplateRequest 更新导出模板请求
|
||
type UpdateSysExportTemplateRequest struct {
|
||
ID uint `json:"ID" binding:"required"`
|
||
DBName string `json:"dbName"`
|
||
Name string `json:"name" binding:"required"`
|
||
TableName string `json:"tableName"`
|
||
TemplateID string `json:"templateID"`
|
||
TemplateInfo string `json:"templateInfo"`
|
||
Limit *int `json:"limit"`
|
||
Order string `json:"order"`
|
||
Conditions []*ConditionRequest `json:"conditions"`
|
||
JoinTemplate []*JoinTemplateRequest `json:"joinTemplate"`
|
||
}
|
||
|
||
// DeleteSysExportTemplateRequest 删除导出模板请求
|
||
type DeleteSysExportTemplateRequest struct {
|
||
ID uint `json:"ID" binding:"required"`
|
||
}
|
||
|
||
// DeleteSysExportTemplateByIdsRequest 批量删除导出模板请求
|
||
type DeleteSysExportTemplateByIdsRequest struct {
|
||
Ids []uint `json:"ids" binding:"required"`
|
||
}
|
||
|
||
// GetSysExportTemplateListRequest 获取导出模板列表请求
|
||
type GetSysExportTemplateListRequest struct {
|
||
Page int `form:"page"`
|
||
PageSize int `form:"pageSize"`
|
||
Name string `form:"name"`
|
||
TableName string `form:"tableName"`
|
||
TemplateID string `form:"templateID"`
|
||
StartCreatedAt *time.Time `form:"startCreatedAt"`
|
||
EndCreatedAt *time.Time `form:"endCreatedAt"`
|
||
}
|
||
|
||
// CreateSysExportTemplate 创建导出模板
|
||
func (e *ExportTemplateApi) CreateSysExportTemplate(c *gin.Context) {
|
||
var req CreateSysExportTemplateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
template := &system.ExportTemplate{
|
||
DBName: req.DBName,
|
||
Name: req.Name,
|
||
TableName: req.TableName,
|
||
TemplateID: req.TemplateID,
|
||
TemplateInfo: req.TemplateInfo,
|
||
Limit: req.Limit,
|
||
Order: req.Order,
|
||
}
|
||
|
||
// 转换条件
|
||
if len(req.Conditions) > 0 {
|
||
template.Conditions = make([]*system.Condition, len(req.Conditions))
|
||
for i, c := range req.Conditions {
|
||
template.Conditions[i] = &system.Condition{
|
||
TemplateID: c.TemplateID,
|
||
From: c.From,
|
||
Column: c.Column,
|
||
Operator: c.Operator,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 转换关联
|
||
if len(req.JoinTemplate) > 0 {
|
||
template.JoinTemplate = make([]*system.JoinTemplate, len(req.JoinTemplate))
|
||
for i, j := range req.JoinTemplate {
|
||
template.JoinTemplate[i] = &system.JoinTemplate{
|
||
TemplateID: j.TemplateID,
|
||
Joins: j.Joins,
|
||
Table: j.Table,
|
||
On: j.On,
|
||
}
|
||
}
|
||
}
|
||
|
||
if err := exportTemplateUsecase.CreateExportTemplate(c, template); err != nil {
|
||
response.FailWithMessage("创建失败", c)
|
||
return
|
||
}
|
||
response.OkWithMessage("创建成功", c)
|
||
}
|
||
|
||
// DeleteSysExportTemplate 删除导出模板
|
||
func (e *ExportTemplateApi) DeleteSysExportTemplate(c *gin.Context) {
|
||
var req DeleteSysExportTemplateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
if err := exportTemplateUsecase.DeleteExportTemplate(c, req.ID); err != nil {
|
||
response.FailWithMessage("删除失败", c)
|
||
return
|
||
}
|
||
response.OkWithMessage("删除成功", c)
|
||
}
|
||
|
||
// DeleteSysExportTemplateByIds 批量删除导出模板
|
||
func (e *ExportTemplateApi) DeleteSysExportTemplateByIds(c *gin.Context) {
|
||
var req DeleteSysExportTemplateByIdsRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
if err := exportTemplateUsecase.DeleteExportTemplateByIds(c, req.Ids); err != nil {
|
||
response.FailWithMessage("批量删除失败", c)
|
||
return
|
||
}
|
||
response.OkWithMessage("批量删除成功", c)
|
||
}
|
||
|
||
// UpdateSysExportTemplate 更新导出模板
|
||
func (e *ExportTemplateApi) UpdateSysExportTemplate(c *gin.Context) {
|
||
var req UpdateSysExportTemplateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
template := &system.ExportTemplate{
|
||
ID: req.ID,
|
||
DBName: req.DBName,
|
||
Name: req.Name,
|
||
TableName: req.TableName,
|
||
TemplateID: req.TemplateID,
|
||
TemplateInfo: req.TemplateInfo,
|
||
Limit: req.Limit,
|
||
Order: req.Order,
|
||
}
|
||
|
||
// 转换条件
|
||
if len(req.Conditions) > 0 {
|
||
template.Conditions = make([]*system.Condition, len(req.Conditions))
|
||
for i, c := range req.Conditions {
|
||
template.Conditions[i] = &system.Condition{
|
||
TemplateID: c.TemplateID,
|
||
From: c.From,
|
||
Column: c.Column,
|
||
Operator: c.Operator,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 转换关联
|
||
if len(req.JoinTemplate) > 0 {
|
||
template.JoinTemplate = make([]*system.JoinTemplate, len(req.JoinTemplate))
|
||
for i, j := range req.JoinTemplate {
|
||
template.JoinTemplate[i] = &system.JoinTemplate{
|
||
TemplateID: j.TemplateID,
|
||
Joins: j.Joins,
|
||
Table: j.Table,
|
||
On: j.On,
|
||
}
|
||
}
|
||
}
|
||
|
||
if err := exportTemplateUsecase.UpdateExportTemplate(c, template); err != nil {
|
||
response.FailWithMessage("更新失败", c)
|
||
return
|
||
}
|
||
response.OkWithMessage("更新成功", c)
|
||
}
|
||
|
||
// FindSysExportTemplate 根据ID获取导出模板
|
||
func (e *ExportTemplateApi) FindSysExportTemplate(c *gin.Context) {
|
||
var req struct {
|
||
ID uint `form:"ID" binding:"required"`
|
||
}
|
||
if err := c.ShouldBindQuery(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
template, err := exportTemplateUsecase.GetExportTemplate(c, req.ID)
|
||
if err != nil {
|
||
response.FailWithMessage("查询失败", c)
|
||
return
|
||
}
|
||
response.OkWithData(gin.H{"resysExportTemplate": toExportTemplateResponse(template)}, c)
|
||
}
|
||
|
||
// GetSysExportTemplateList 分页获取导出模板列表
|
||
func (e *ExportTemplateApi) GetSysExportTemplateList(c *gin.Context) {
|
||
var req GetSysExportTemplateListRequest
|
||
if err := c.ShouldBindQuery(&req); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
|
||
searchReq := &system.ExportTemplateSearchReq{
|
||
Page: req.Page,
|
||
PageSize: req.PageSize,
|
||
Name: req.Name,
|
||
TableName: req.TableName,
|
||
TemplateID: req.TemplateID,
|
||
StartCreatedAt: req.StartCreatedAt,
|
||
EndCreatedAt: req.EndCreatedAt,
|
||
}
|
||
|
||
list, total, err := exportTemplateUsecase.GetExportTemplateList(c, searchReq)
|
||
if err != nil {
|
||
response.FailWithMessage("获取失败", c)
|
||
return
|
||
}
|
||
|
||
respList := make([]interface{}, len(list))
|
||
for i, t := range list {
|
||
respList[i] = toExportTemplateResponse(t)
|
||
}
|
||
|
||
response.OkWithDetailed(response.PageResult{
|
||
List: respList,
|
||
Total: total,
|
||
Page: req.Page,
|
||
PageSize: req.PageSize,
|
||
}, "获取成功", c)
|
||
}
|
||
|
||
// ExportExcel 导出Excel(获取token)
|
||
func (e *ExportTemplateApi) ExportExcel(c *gin.Context) {
|
||
templateID := c.Query("templateID")
|
||
if templateID == "" {
|
||
response.FailWithMessage("模板ID不能为空", c)
|
||
return
|
||
}
|
||
|
||
queryParams := c.Request.URL.Query()
|
||
|
||
// 创造一次性token
|
||
token := utils.RandomString(32)
|
||
|
||
// 记录本次请求参数
|
||
exportParams := map[string]interface{}{
|
||
"templateID": templateID,
|
||
"queryParams": queryParams,
|
||
}
|
||
|
||
// 参数保留记录完成鉴权
|
||
tokenMutex.Lock()
|
||
exportTokenCache[token] = exportParams
|
||
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||
tokenMutex.Unlock()
|
||
|
||
// 生成一次性链接
|
||
exportUrl := fmt.Sprintf("/sysExportTemplate/exportExcelByToken?token=%s", token)
|
||
response.OkWithData(exportUrl, c)
|
||
}
|
||
|
||
// ExportExcelByToken 通过token导出Excel
|
||
func (e *ExportTemplateApi) ExportExcelByToken(c *gin.Context) {
|
||
token := c.Query("token")
|
||
if token == "" {
|
||
response.FailWithMessage("导出token不能为空", c)
|
||
return
|
||
}
|
||
|
||
// 获取token并且从缓存中剔除
|
||
tokenMutex.RLock()
|
||
exportParamsRaw, exists := exportTokenCache[token]
|
||
expiry, _ := exportTokenExpiration[token]
|
||
tokenMutex.RUnlock()
|
||
|
||
if !exists || time.Now().After(expiry) {
|
||
response.FailWithMessage("导出token无效或已过期", c)
|
||
return
|
||
}
|
||
|
||
// 从token获取参数
|
||
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||
if !ok {
|
||
response.FailWithMessage("解析导出参数失败", c)
|
||
return
|
||
}
|
||
|
||
// 获取导出参数
|
||
templateID := exportParams["templateID"].(string)
|
||
queryParams := exportParams["queryParams"].(url.Values)
|
||
|
||
// 清理一次性token
|
||
tokenMutex.Lock()
|
||
delete(exportTokenCache, token)
|
||
delete(exportTokenExpiration, token)
|
||
tokenMutex.Unlock()
|
||
|
||
// 导出
|
||
file, name, err := exportTemplateUsecase.ExportExcel(c, templateID, queryParams)
|
||
if err != nil {
|
||
response.FailWithMessage("获取失败", c)
|
||
return
|
||
}
|
||
|
||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".csv"))
|
||
c.Header("success", "true")
|
||
c.Data(http.StatusOK, "text/csv", file.Bytes())
|
||
}
|
||
|
||
// PreviewSQL 预览SQL
|
||
func (e *ExportTemplateApi) PreviewSQL(c *gin.Context) {
|
||
templateID := c.Query("templateID")
|
||
if templateID == "" {
|
||
response.FailWithMessage("模板ID不能为空", c)
|
||
return
|
||
}
|
||
|
||
queryParams := c.Request.URL.Query()
|
||
|
||
sqlPreview, err := exportTemplateUsecase.PreviewSQL(c, templateID, queryParams)
|
||
if err != nil {
|
||
response.FailWithMessage("获取失败", c)
|
||
return
|
||
}
|
||
response.OkWithData(gin.H{"sql": sqlPreview}, c)
|
||
}
|
||
|
||
// ExportTemplate 导出Excel模板(获取token)
|
||
func (e *ExportTemplateApi) ExportTemplate(c *gin.Context) {
|
||
templateID := c.Query("templateID")
|
||
if templateID == "" {
|
||
response.FailWithMessage("模板ID不能为空", c)
|
||
return
|
||
}
|
||
|
||
// 创造一次性token
|
||
token := utils.RandomString(32)
|
||
|
||
// 记录本次请求参数
|
||
exportParams := map[string]interface{}{
|
||
"templateID": templateID,
|
||
"isTemplate": true,
|
||
}
|
||
|
||
// 参数保留记录完成鉴权
|
||
tokenMutex.Lock()
|
||
exportTokenCache[token] = exportParams
|
||
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||
tokenMutex.Unlock()
|
||
|
||
// 生成一次性链接
|
||
exportUrl := fmt.Sprintf("/sysExportTemplate/exportTemplateByToken?token=%s", token)
|
||
response.OkWithData(exportUrl, c)
|
||
}
|
||
|
||
// ExportTemplateByToken 通过token导出模板
|
||
func (e *ExportTemplateApi) ExportTemplateByToken(c *gin.Context) {
|
||
token := c.Query("token")
|
||
if token == "" {
|
||
response.FailWithMessage("导出token不能为空", c)
|
||
return
|
||
}
|
||
|
||
// 获取token并且从缓存中剔除
|
||
tokenMutex.RLock()
|
||
exportParamsRaw, exists := exportTokenCache[token]
|
||
expiry, _ := exportTokenExpiration[token]
|
||
tokenMutex.RUnlock()
|
||
|
||
if !exists || time.Now().After(expiry) {
|
||
response.FailWithMessage("导出token无效或已过期", c)
|
||
return
|
||
}
|
||
|
||
// 从token获取参数
|
||
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||
if !ok {
|
||
response.FailWithMessage("解析导出参数失败", c)
|
||
return
|
||
}
|
||
|
||
// 检查是否为模板导出
|
||
isTemplate, _ := exportParams["isTemplate"].(bool)
|
||
if !isTemplate {
|
||
response.FailWithMessage("token类型错误", c)
|
||
return
|
||
}
|
||
|
||
// 获取导出参数
|
||
templateID := exportParams["templateID"].(string)
|
||
|
||
// 清理一次性token
|
||
tokenMutex.Lock()
|
||
delete(exportTokenCache, token)
|
||
delete(exportTokenExpiration, token)
|
||
tokenMutex.Unlock()
|
||
|
||
// 导出模板
|
||
file, name, err := exportTemplateUsecase.ExportTemplate(c, templateID)
|
||
if err != nil {
|
||
response.FailWithMessage("获取失败", c)
|
||
return
|
||
}
|
||
|
||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.csv"))
|
||
c.Header("success", "true")
|
||
c.Data(http.StatusOK, "text/csv", file.Bytes())
|
||
}
|
||
|
||
// ImportExcel 导入Excel
|
||
func (e *ExportTemplateApi) ImportExcel(c *gin.Context) {
|
||
templateID := c.Query("templateID")
|
||
if templateID == "" {
|
||
response.FailWithMessage("模板ID不能为空", c)
|
||
return
|
||
}
|
||
|
||
file, err := c.FormFile("file")
|
||
if err != nil {
|
||
response.FailWithMessage("文件获取失败", c)
|
||
return
|
||
}
|
||
|
||
if err := exportTemplateUsecase.ImportExcel(c, templateID, file); err != nil {
|
||
response.FailWithMessage(err.Error(), c)
|
||
return
|
||
}
|
||
response.OkWithMessage("导入成功", c)
|
||
}
|
||
|
||
// 转换为响应结构
|
||
func toExportTemplateResponse(t *system.ExportTemplate) map[string]interface{} {
|
||
resp := map[string]interface{}{
|
||
"ID": t.ID,
|
||
"dbName": t.DBName,
|
||
"name": t.Name,
|
||
"tableName": t.TableName,
|
||
"templateID": t.TemplateID,
|
||
"templateInfo": t.TemplateInfo,
|
||
"limit": t.Limit,
|
||
"order": t.Order,
|
||
"createdAt": t.CreatedAt,
|
||
"updatedAt": t.UpdatedAt,
|
||
}
|
||
|
||
if len(t.Conditions) > 0 {
|
||
conditions := make([]map[string]interface{}, len(t.Conditions))
|
||
for i, c := range t.Conditions {
|
||
conditions[i] = map[string]interface{}{
|
||
"ID": c.ID,
|
||
"templateID": c.TemplateID,
|
||
"from": c.From,
|
||
"column": c.Column,
|
||
"operator": c.Operator,
|
||
}
|
||
}
|
||
resp["conditions"] = conditions
|
||
}
|
||
|
||
if len(t.JoinTemplate) > 0 {
|
||
joins := make([]map[string]interface{}, len(t.JoinTemplate))
|
||
for i, j := range t.JoinTemplate {
|
||
joins[i] = map[string]interface{}{
|
||
"ID": j.ID,
|
||
"templateID": j.TemplateID,
|
||
"joins": j.Joins,
|
||
"table": j.Table,
|
||
"on": j.On,
|
||
}
|
||
}
|
||
resp["joinTemplate"] = joins
|
||
}
|
||
|
||
return resp
|
||
}
|