kra/internal/biz/system/export_template.go

599 lines
15 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package system
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/url"
"strconv"
"strings"
"time"
)
// ExportTemplate 导出模板实体
type ExportTemplate struct {
ID uint
DBName string
Name string
TableName string
TemplateID string
TemplateInfo string
Limit *int
Order string
Conditions []*Condition
JoinTemplate []*JoinTemplate
CreatedAt time.Time
UpdatedAt time.Time
}
// Condition 条件实体
type Condition struct {
ID uint
TemplateID string
From string
Column string
Operator string
}
// JoinTemplate 关联模板实体
type JoinTemplate struct {
ID uint
TemplateID string
Joins string
Table string
On string
}
// ExportTemplateSearchReq 导出模板搜索请求
type ExportTemplateSearchReq struct {
Page int
PageSize int
Name string
TableName string
TemplateID string
StartCreatedAt *time.Time
EndCreatedAt *time.Time
}
// ExportTemplateRepo 导出模板仓储接口
type ExportTemplateRepo interface {
Create(ctx context.Context, template *ExportTemplate) error
Update(ctx context.Context, template *ExportTemplate) error
Delete(ctx context.Context, id uint) error
FindByID(ctx context.Context, id uint) (*ExportTemplate, error)
FindByTemplateID(ctx context.Context, templateID string) (*ExportTemplate, error)
GetList(ctx context.Context, req *ExportTemplateSearchReq) ([]*ExportTemplate, int64, error)
DeleteConditionsByTemplateID(ctx context.Context, templateID string) error
DeleteJoinsByTemplateID(ctx context.Context, templateID string) error
CreateConditions(ctx context.Context, conditions []*Condition) error
CreateJoins(ctx context.Context, joins []*JoinTemplate) error
// Excel 相关
GetDB(dbName string) interface{}
ExecuteQuery(ctx context.Context, dbName, sql string, args ...interface{}) ([]map[string]interface{}, error)
GetColumnTypes(ctx context.Context, dbName, tableName string) ([]string, error)
HasDeletedAtColumn(ctx context.Context, tableName string) bool
ImportData(ctx context.Context, dbName, tableName string, items []map[string]interface{}) error
}
// ExportTemplateUsecase 导出模板用例
type ExportTemplateUsecase struct {
repo ExportTemplateRepo
}
// NewExportTemplateUsecase 创建导出模板用例
func NewExportTemplateUsecase(repo ExportTemplateRepo) *ExportTemplateUsecase {
return &ExportTemplateUsecase{repo: repo}
}
// CreateExportTemplate 创建导出模板
func (uc *ExportTemplateUsecase) CreateExportTemplate(ctx context.Context, template *ExportTemplate) error {
return uc.repo.Create(ctx, template)
}
// DeleteExportTemplate 删除导出模板
func (uc *ExportTemplateUsecase) DeleteExportTemplate(ctx context.Context, id uint) error {
return uc.repo.Delete(ctx, id)
}
// DeleteExportTemplateByIds 批量删除导出模板
func (uc *ExportTemplateUsecase) DeleteExportTemplateByIds(ctx context.Context, ids []uint) error {
for _, id := range ids {
if err := uc.repo.Delete(ctx, id); err != nil {
return err
}
}
return nil
}
// UpdateExportTemplate 更新导出模板
func (uc *ExportTemplateUsecase) UpdateExportTemplate(ctx context.Context, template *ExportTemplate) error {
// 删除旧的条件和关联
if err := uc.repo.DeleteConditionsByTemplateID(ctx, template.TemplateID); err != nil {
return err
}
if err := uc.repo.DeleteJoinsByTemplateID(ctx, template.TemplateID); err != nil {
return err
}
// 保存条件
conditions := template.Conditions
template.Conditions = nil
// 保存关联
joins := template.JoinTemplate
template.JoinTemplate = nil
// 更新模板
if err := uc.repo.Update(ctx, template); err != nil {
return err
}
// 创建新的条件
if len(conditions) > 0 {
for i := range conditions {
conditions[i].ID = 0
}
if err := uc.repo.CreateConditions(ctx, conditions); err != nil {
return err
}
}
// 创建新的关联
if len(joins) > 0 {
for i := range joins {
joins[i].ID = 0
}
if err := uc.repo.CreateJoins(ctx, joins); err != nil {
return err
}
}
return nil
}
// GetExportTemplate 根据ID获取导出模板
func (uc *ExportTemplateUsecase) GetExportTemplate(ctx context.Context, id uint) (*ExportTemplate, error) {
return uc.repo.FindByID(ctx, id)
}
// GetExportTemplateList 分页获取导出模板列表
func (uc *ExportTemplateUsecase) GetExportTemplateList(ctx context.Context, req *ExportTemplateSearchReq) ([]*ExportTemplate, int64, error) {
return uc.repo.GetList(ctx, req)
}
// ExportExcel 导出Excel
func (uc *ExportTemplateUsecase) ExportExcel(ctx context.Context, templateID string, values url.Values) (*bytes.Buffer, string, error) {
params := values.Get("params")
paramsValues, err := url.ParseQuery(params)
if err != nil {
return nil, "", fmt.Errorf("解析 params 参数失败: %v", err)
}
template, err := uc.repo.FindByTemplateID(ctx, templateID)
if err != nil {
return nil, "", err
}
// 解析模板信息
var templateInfoMap = make(map[string]string)
columns, err := getJSONKeys(template.TemplateInfo)
if err != nil {
return nil, "", err
}
if err := json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap); err != nil {
return nil, "", err
}
var tableTitle []string
var selectKeyFmt []string
for _, key := range columns {
selectKeyFmt = append(selectKeyFmt, key)
tableTitle = append(tableTitle, templateInfoMap[key])
}
selects := strings.Join(selectKeyFmt, ", ")
// 构建SQL
var sb strings.Builder
sb.WriteString("SELECT ")
sb.WriteString(selects)
sb.WriteString(" FROM ")
sb.WriteString(template.TableName)
// JOIN
if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate {
sb.WriteString(" ")
sb.WriteString(join.Joins)
sb.WriteString(" ")
sb.WriteString(join.Table)
sb.WriteString(" ON ")
sb.WriteString(join.On)
}
}
// WHERE
var wheres []string
var args []interface{}
// 软删除过滤
filterDeleted := paramsValues.Get("filterDeleted") == "true"
if filterDeleted {
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
for _, join := range template.JoinTemplate {
if uc.repo.HasDeletedAtColumn(ctx, join.Table) {
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
}
}
}
// 条件
for _, condition := range template.Conditions {
value := paramsValues.Get(condition.From)
op := strings.ToUpper(strings.TrimSpace(condition.Operator))
if op == "BETWEEN" {
startValue := paramsValues.Get("start" + condition.From)
endValue := paramsValues.Get("end" + condition.From)
if startValue != "" && endValue != "" {
wheres = append(wheres, fmt.Sprintf("%s BETWEEN ? AND ?", condition.Column))
args = append(args, startValue, endValue)
}
continue
}
if value == "" {
continue
}
if op == "LIKE" {
wheres = append(wheres, fmt.Sprintf("%s LIKE ?", condition.Column))
args = append(args, "%"+value+"%")
} else if op == "IN" || op == "NOT IN" {
wheres = append(wheres, fmt.Sprintf("%s %s (?)", condition.Column, op))
args = append(args, value)
} else {
wheres = append(wheres, fmt.Sprintf("%s %s ?", condition.Column, op))
args = append(args, value)
}
}
if len(wheres) > 0 {
sb.WriteString(" WHERE ")
sb.WriteString(strings.Join(wheres, " AND "))
}
// ORDER
order := paramsValues.Get("order")
if order == "" && template.Order != "" {
order = template.Order
}
if order != "" {
sb.WriteString(" ORDER BY ")
sb.WriteString(order)
}
// LIMIT
limit := paramsValues.Get("limit")
if limit == "" && template.Limit != nil && *template.Limit != 0 {
limit = strconv.Itoa(*template.Limit)
}
if limit != "" {
sb.WriteString(" LIMIT ")
sb.WriteString(limit)
}
// OFFSET
offset := paramsValues.Get("offset")
if offset != "" {
sb.WriteString(" OFFSET ")
sb.WriteString(offset)
}
// 执行查询
tableMap, err := uc.repo.ExecuteQuery(ctx, template.DBName, sb.String(), args...)
if err != nil {
return nil, "", err
}
// 生成Excel
return uc.generateExcel(tableMap, columns, tableTitle, template)
}
// generateExcel 生成Excel文件
func (uc *ExportTemplateUsecase) generateExcel(tableMap []map[string]interface{}, columns, tableTitle []string, template *ExportTemplate) (*bytes.Buffer, string, error) {
// 简化实现生成CSV格式
var buf bytes.Buffer
// 写入标题行
buf.WriteString(strings.Join(tableTitle, ","))
buf.WriteString("\n")
// 写入数据行
for _, row := range tableMap {
var values []string
for _, col := range columns {
col = strings.ReplaceAll(col, "\"", "")
col = strings.ReplaceAll(col, "`", "")
if len(template.JoinTemplate) > 0 {
columnAs := strings.Split(col, " as ")
if len(columnAs) > 1 {
col = strings.TrimSpace(columnAs[1])
} else {
columnArr := strings.Split(col, ".")
if len(columnArr) > 1 {
col = columnArr[1]
}
}
}
if t, ok := row[col].(time.Time); ok {
values = append(values, t.Format("2006-01-02 15:04:05"))
} else {
values = append(values, fmt.Sprintf("%v", row[col]))
}
}
buf.WriteString(strings.Join(values, ","))
buf.WriteString("\n")
}
return &buf, template.Name, nil
}
// PreviewSQL 预览SQL
func (uc *ExportTemplateUsecase) PreviewSQL(ctx context.Context, templateID string, values url.Values) (string, error) {
params := values.Get("params")
paramsValues, _ := url.ParseQuery(params)
template, err := uc.repo.FindByTemplateID(ctx, templateID)
if err != nil {
return "", err
}
// 解析模板信息
var templateInfoMap = make(map[string]string)
columns, err := getJSONKeys(template.TemplateInfo)
if err != nil {
return "", err
}
if err := json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap); err != nil {
return "", err
}
var selectKeyFmt []string
for _, key := range columns {
selectKeyFmt = append(selectKeyFmt, key)
}
selects := strings.Join(selectKeyFmt, ", ")
// 构建SQL
var sb strings.Builder
sb.WriteString("SELECT ")
sb.WriteString(selects)
sb.WriteString(" FROM ")
sb.WriteString(template.TableName)
// JOIN
if len(template.JoinTemplate) > 0 {
for _, join := range template.JoinTemplate {
sb.WriteString(" ")
sb.WriteString(join.Joins)
sb.WriteString(" ")
sb.WriteString(join.Table)
sb.WriteString(" ON ")
sb.WriteString(join.On)
}
}
// WHERE
var wheres []string
filterDeleted := false
if paramsValues != nil {
filterDeleted = paramsValues.Get("filterDeleted") == "true"
}
if filterDeleted {
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
for _, join := range template.JoinTemplate {
if uc.repo.HasDeletedAtColumn(ctx, join.Table) {
wheres = append(wheres, fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
}
}
}
// 条件
for _, condition := range template.Conditions {
op := strings.ToUpper(strings.TrimSpace(condition.Operator))
col := strings.TrimSpace(condition.Column)
val := ""
if paramsValues != nil {
val = paramsValues.Get(condition.From)
}
switch op {
case "BETWEEN":
startValue := ""
endValue := ""
if paramsValues != nil {
startValue = paramsValues.Get("start" + condition.From)
endValue = paramsValues.Get("end" + condition.From)
}
if startValue != "" && endValue != "" {
wheres = append(wheres, fmt.Sprintf("%s BETWEEN '%s' AND '%s'", col, startValue, endValue))
} else {
wheres = append(wheres, fmt.Sprintf("%s BETWEEN {start%s} AND {end%s}", col, condition.From, condition.From))
}
case "IN", "NOT IN":
if val != "" {
wheres = append(wheres, fmt.Sprintf("%s %s ('%s')", col, op, val))
} else {
wheres = append(wheres, fmt.Sprintf("%s %s ({%s})", col, op, condition.From))
}
case "LIKE":
if val != "" {
wheres = append(wheres, fmt.Sprintf("%s LIKE '%%%s%%'", col, val))
} else {
wheres = append(wheres, fmt.Sprintf("%s LIKE {%%%s%%}", col, condition.From))
}
default:
if val != "" {
wheres = append(wheres, fmt.Sprintf("%s %s '%s'", col, op, val))
} else {
wheres = append(wheres, fmt.Sprintf("%s %s {%s}", col, op, condition.From))
}
}
}
if len(wheres) > 0 {
sb.WriteString(" WHERE ")
sb.WriteString(strings.Join(wheres, " AND "))
}
// ORDER
order := ""
if paramsValues != nil {
order = paramsValues.Get("order")
}
if order == "" && template.Order != "" {
order = template.Order
}
if order != "" {
sb.WriteString(" ORDER BY ")
sb.WriteString(order)
}
// LIMIT/OFFSET
limitStr := ""
offsetStr := ""
if paramsValues != nil {
limitStr = paramsValues.Get("limit")
offsetStr = paramsValues.Get("offset")
}
if limitStr == "" && template.Limit != nil && *template.Limit != 0 {
limitStr = strconv.Itoa(*template.Limit)
}
limitInt := 0
offsetInt := 0
if limitStr != "" {
if v, e := strconv.Atoi(limitStr); e == nil {
limitInt = v
}
}
if offsetStr != "" {
if v, e := strconv.Atoi(offsetStr); e == nil {
offsetInt = v
}
}
if limitInt > 0 {
sb.WriteString(" LIMIT ")
sb.WriteString(strconv.Itoa(limitInt))
if offsetInt > 0 {
sb.WriteString(" OFFSET ")
sb.WriteString(strconv.Itoa(offsetInt))
}
} else if offsetInt > 0 {
sb.WriteString(" OFFSET ")
sb.WriteString(strconv.Itoa(offsetInt))
}
return sb.String(), nil
}
// ExportTemplate 导出Excel模板
func (uc *ExportTemplateUsecase) ExportTemplate(ctx context.Context, templateID string) (*bytes.Buffer, string, error) {
template, err := uc.repo.FindByTemplateID(ctx, templateID)
if err != nil {
return nil, "", err
}
var templateInfoMap = make(map[string]string)
columns, err := getJSONKeys(template.TemplateInfo)
if err != nil {
return nil, "", err
}
if err := json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap); err != nil {
return nil, "", err
}
var tableTitle []string
for _, key := range columns {
tableTitle = append(tableTitle, templateInfoMap[key])
}
// 生成CSV模板
var buf bytes.Buffer
buf.WriteString(strings.Join(tableTitle, ","))
buf.WriteString("\n")
return &buf, template.Name, nil
}
// ImportExcel 导入Excel
func (uc *ExportTemplateUsecase) ImportExcel(ctx context.Context, templateID string, file *multipart.FileHeader) error {
template, err := uc.repo.FindByTemplateID(ctx, templateID)
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
// 简化实现读取CSV格式
var templateInfoMap = make(map[string]string)
if err := json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap); err != nil {
return err
}
var titleKeyMap = make(map[string]string)
for key, title := range templateInfoMap {
titleKeyMap[title] = key
}
// 这里需要实际的Excel解析逻辑
return errors.New("Excel导入功能需要excelize库支持")
}
// getJSONKeys 获取JSON对象的键保持顺序
func getJSONKeys(jsonStr string) ([]string, error) {
var result []string
dec := json.NewDecoder(strings.NewReader(jsonStr))
// 读取开始的 {
t, err := dec.Token()
if err != nil {
return nil, err
}
if t != json.Delim('{') {
return nil, errors.New("expected {")
}
for dec.More() {
// 读取键
t, err := dec.Token()
if err != nil {
return nil, err
}
key, ok := t.(string)
if !ok {
return nil, errors.New("expected string key")
}
result = append(result, key)
// 跳过值
var value interface{}
if err := dec.Decode(&value); err != nil {
return nil, err
}
}
return result, nil
}