kra/internal/server/handler/system/sys_export_template.go

638 lines
19 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
// @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
}