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 // @Tags SysExportTemplate // @Summary 创建导出模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body CreateSysExportTemplateRequest true "创建导出模板" // @Success 200 {object} response.Response{msg=string} "创建成功" // @Router /sysExportTemplate/createSysExportTemplate [post] 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 // @Tags SysExportTemplate // @Summary 删除导出模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body DeleteSysExportTemplateRequest true "删除导出模板" // @Success 200 {object} response.Response{msg=string} "删除成功" // @Router /sysExportTemplate/deleteSysExportTemplate [delete] 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 // @Tags SysExportTemplate // @Summary 批量删除导出模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body DeleteSysExportTemplateByIdsRequest true "批量删除导出模板" // @Success 200 {object} response.Response{msg=string} "批量删除成功" // @Router /sysExportTemplate/deleteSysExportTemplateByIds [delete] 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 // @Tags SysExportTemplate // @Summary 更新导出模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data body UpdateSysExportTemplateRequest true "更新导出模板" // @Success 200 {object} response.Response{msg=string} "更新成功" // @Router /sysExportTemplate/updateSysExportTemplate [put] 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 // @Tags SysExportTemplate // @Summary 用id查询导出模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param ID query uint true "用id查询导出模板" // @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "查询成功" // @Router /sysExportTemplate/findSysExportTemplate [get] 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 // @Tags SysExportTemplate // @Summary 分页获取导出模板列表 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param data query GetSysExportTemplateListRequest true "分页获取导出模板列表" // @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" // @Router /sysExportTemplate/getSysExportTemplateList [get] 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 // @Tags SysExportTemplate // @Summary 导出表格token // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param templateID query string true "模板ID" // @Success 200 {object} response.Response{data=string,msg=string} "获取导出链接" // @Router /sysExportTemplate/exportExcel [get] 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 // @Tags ExportExcelByToken // @Summary 导出表格 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param token query string true "导出token" // @Success 200 {file} csv "导出CSV文件" // @Router /sysExportTemplate/exportExcelByToken [get] 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 // @Tags SysExportTemplate // @Summary 预览最终生成的SQL // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param templateID query string true "导出模板ID" // @Param params query string false "查询参数编码字符串" // @Success 200 {object} response.Response{data=map[string]string,msg=string} "获取成功" // @Router /sysExportTemplate/previewSQL [get] 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 // @Tags SysExportTemplate // @Summary 导出表格模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param templateID query string true "模板ID" // @Success 200 {object} response.Response{data=string,msg=string} "获取导出链接" // @Router /sysExportTemplate/exportTemplate [get] 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 // @Tags ExportTemplateByToken // @Summary 通过token导出表格模板 // @Security ApiKeyAuth // @accept application/json // @Produce application/json // @Param token query string true "导出token" // @Success 200 {file} csv "导出CSV模板文件" // @Router /sysExportTemplate/exportTemplateByToken [get] 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 // @Tags SysImportTemplate // @Summary 导入表格 // @Security ApiKeyAuth // @accept multipart/form-data // @Produce application/json // @Param templateID query string true "模板ID" // @Param file formData file true "导入文件" // @Success 200 {object} response.Response{msg=string} "导入成功" // @Router /sysExportTemplate/importExcel [post] 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 }