package solutionmanagement import ( "context" "fmt" "regexp" "git.x2erp.com/qdy/go-base/ctx" "git.x2erp.com/qdy/go-base/logger" "git.x2erp.com/qdy/go-base/model/response" "git.x2erp.com/qdy/go-base/util" "git.x2erp.com/qdy/go-db/factory/database" "git.x2erp.com/qdy/go-svc-configure/internal/tables" "github.com/jmoiron/sqlx" ) // SaveSyncSolution 保存同步方案(主表+子表) func SaveSyncSolution(req *SyncSolutionRequest, ctx context.Context, dbFactory *database.DBFactory, reqCtx *ctx.RequestContext) *response.QueryResult[SyncSolutionDetail] { logger.Debug("SaveSyncSolution-开始保存同步方案") // 参数验证 if err := validateSyncSolutionRequest(req); err != nil { logger.ErrorC(reqCtx, fmt.Sprintf("参数验证失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("参数验证失败: %v", err), reqCtx) } // 获取数据库连接并开始事务 db := dbFactory.GetDB() tx, err := db.BeginTxx(ctx, nil) if err != nil { logger.ErrorC(reqCtx, fmt.Sprintf("开始事务失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("开始事务失败: %v", err), reqCtx) } defer func() { if p := recover(); p != nil { tx.Rollback() panic(p) } }() // 获取当前用户 creator := reqCtx.UserID if creator == "" { creator = "system" } // 1. 处理主表:检查是否存在 → 存在则更新,不存在则插入 solutionExists, err := checkSolutionExists(ctx, tx, req.SolutionCode) if err != nil { tx.Rollback() logger.ErrorC(reqCtx, fmt.Sprintf("检查方案存在性失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("检查方案存在性失败: %v", err), reqCtx) } var solution tables.SyncSolutionDB if solutionExists { // 更新主表 solution, err = updateSolution(ctx, tx, req, creator) if err != nil { tx.Rollback() logger.ErrorC(reqCtx, fmt.Sprintf("更新方案失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("更新方案失败: %v", err), reqCtx) } logger.Debug(fmt.Sprintf("更新方案成功: %s", req.SolutionCode)) } else { // 插入主表 solution, err = insertSolution(ctx, tx, req, creator) if err != nil { tx.Rollback() logger.ErrorC(reqCtx, fmt.Sprintf("插入方案失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("插入方案失败: %v", err), reqCtx) } logger.Debug(fmt.Sprintf("插入方案成功: %s", req.SolutionCode)) } // 2. 处理子表:逐条检查 → 存在则更新,不存在则新增 var sqlScripts []tables.SyncSolutionSQLDB for _, script := range req.SQLScripts { // 设置关联的solution_id script.SolutionID = req.SolutionCode // 检查子表记录是否存在 sqlExists, err := checkSQLScriptExists(ctx, tx, script.SyncID, req.SolutionCode) if err != nil { tx.Rollback() logger.ErrorC(reqCtx, fmt.Sprintf("检查SQL脚本存在性失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("检查SQL脚本存在性失败: %v", err), reqCtx) } var sqlScript tables.SyncSolutionSQLDB if sqlExists { // 更新子表 sqlScript, err = updateSQLScript(ctx, tx, &script, creator) if err != nil { tx.Rollback() logger.ErrorC(reqCtx, fmt.Sprintf("更新SQL脚本失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("更新SQL脚本失败: %v", err), reqCtx) } logger.Debug(fmt.Sprintf("更新SQL脚本成功: %s", script.SyncID)) } else { // 插入子表 sqlScript, err = insertSQLScript(ctx, tx, &script, creator) if err != nil { tx.Rollback() logger.ErrorC(reqCtx, fmt.Sprintf("插入SQL脚本失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("插入SQL脚本失败: %v", err), reqCtx) } logger.Debug(fmt.Sprintf("插入SQL脚本成功: %s", script.SyncID)) } sqlScripts = append(sqlScripts, sqlScript) } // 提交事务 if err := tx.Commit(); err != nil { logger.ErrorC(reqCtx, fmt.Sprintf("提交事务失败: %v", err)) return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("提交事务失败: %v", err), reqCtx) } logger.Debug(fmt.Sprintf("成功保存同步方案: %s, 包含 %d 个SQL脚本", req.SolutionCode, len(sqlScripts))) // 构建返回结果 detail := SyncSolutionDetail{ Solution: solution, SQLScripts: sqlScripts, } return util.CreateSuccessResultData[SyncSolutionDetail](detail, reqCtx) } // validateSyncSolutionRequest 验证同步方案请求 func validateSyncSolutionRequest(req *SyncSolutionRequest) error { if req.SolutionCode == "" { return fmt.Errorf("方案代码不能为空") } // 验证方案代码格式:仅允许字母、数字、下划线 match, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", req.SolutionCode) if !match { return fmt.Errorf("方案代码只能包含字母、数字、下划线") } if req.SolutionType == "" { return fmt.Errorf("方案类型不能为空") } // 验证方案类型 validTypes := map[string]bool{"全量": true, "增量": true, "计算": true} if !validTypes[req.SolutionType] { return fmt.Errorf("方案类型必须是'全量'、'增量'或'计算'") } if req.SolutionName == "" { return fmt.Errorf("方案名称不能为空") } // 验证子表数据 for i, script := range req.SQLScripts { if script.SyncID == "" { return fmt.Errorf("第%d个SQL脚本的同步代码不能为空", i+1) } if script.SyncName == "" { return fmt.Errorf("第%d个SQL脚本的同步名称不能为空", i+1) } if script.CodeSQL == "" { return fmt.Errorf("第%d个SQL脚本的SQL代码不能为空", i+1) } if script.CountSQL == "" { return fmt.Errorf("第%d个SQL脚本的统计SQL代码不能为空", i+1) } // 验证同步代码格式 match, _ := regexp.MatchString("^[a-zA-Z0-9_.-]+$", script.SyncID) if !match { return fmt.Errorf("第%d个SQL脚本的同步代码只能包含字母、数字、下划线、点、横线", i+1) } } return nil } // checkSolutionExists 检查方案是否存在 func checkSolutionExists(ctx context.Context, tx *sqlx.Tx, solutionCode string) (bool, error) { var count int query := "SELECT COUNT(*) FROM sync_solution WHERE solution_code = ? AND deleted_at IS NULL" err := tx.GetContext(ctx, &count, query, solutionCode) return count > 0, err } // insertSolution 插入方案主表 func insertSolution(ctx context.Context, tx *sqlx.Tx, req *SyncSolutionRequest, creator string) (tables.SyncSolutionDB, error) { query := ` INSERT INTO sync_solution (solution_code, solution_type, solution_name, description, creator) VALUES (?, ?, ?, ?, ?) ` _, err := tx.ExecContext(ctx, query, req.SolutionCode, req.SolutionType, req.SolutionName, req.Description, creator, ) if err != nil { return tables.SyncSolutionDB{}, err } // 查询刚插入的记录 var solution tables.SyncSolutionDB selectQuery := ` SELECT id, solution_code, solution_type, solution_name, description, creator, created_at, updated_at, deleted_at FROM sync_solution WHERE solution_code = ? AND deleted_at IS NULL ` err = tx.GetContext(ctx, &solution, selectQuery, req.SolutionCode) return solution, err } // updateSolution 更新方案主表 func updateSolution(ctx context.Context, tx *sqlx.Tx, req *SyncSolutionRequest, creator string) (tables.SyncSolutionDB, error) { query := ` UPDATE sync_solution SET solution_type = ?, solution_name = ?, description = ?, updated_at = CURRENT_TIMESTAMP WHERE solution_code = ? AND deleted_at IS NULL ` _, err := tx.ExecContext(ctx, query, req.SolutionType, req.SolutionName, req.Description, req.SolutionCode, ) if err != nil { return tables.SyncSolutionDB{}, err } // 查询更新后的记录 var solution tables.SyncSolutionDB selectQuery := ` SELECT id, solution_code, solution_type, solution_name, description, creator, created_at, updated_at, deleted_at FROM sync_solution WHERE solution_code = ? AND deleted_at IS NULL ` err = tx.GetContext(ctx, &solution, selectQuery, req.SolutionCode) return solution, err } // checkSQLScriptExists 检查SQL脚本是否存在 func checkSQLScriptExists(ctx context.Context, tx *sqlx.Tx, syncID, solutionID string) (bool, error) { var count int query := "SELECT COUNT(*) FROM sync_solution_sql WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL" err := tx.GetContext(ctx, &count, query, syncID, solutionID) return count > 0, err } // insertSQLScript 插入SQL脚本子表 func insertSQLScript(ctx context.Context, tx *sqlx.Tx, script *SyncSQLScript, creator string) (tables.SyncSolutionSQLDB, error) { query := ` INSERT INTO sync_solution_sql (sync_id, sync_name, solution_id, code_sql, count_sql, is_active, creator) VALUES (?, ?, ?, ?, ?, ?, ?) ` _, err := tx.ExecContext(ctx, query, script.SyncID, script.SyncName, script.SolutionID, script.CodeSQL, script.CountSQL, script.IsActive, creator, ) if err != nil { return tables.SyncSolutionSQLDB{}, err } // 查询刚插入的记录 var sqlScript tables.SyncSolutionSQLDB selectQuery := ` SELECT id, sync_id, sync_name, solution_id, code_sql, count_sql, is_active, creator, created_at, updated_at, deleted_at FROM sync_solution_sql WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL ` err = tx.GetContext(ctx, &sqlScript, selectQuery, script.SyncID, script.SolutionID) return sqlScript, err } // updateSQLScript 更新SQL脚本子表 func updateSQLScript(ctx context.Context, tx *sqlx.Tx, script *SyncSQLScript, creator string) (tables.SyncSolutionSQLDB, error) { query := ` UPDATE sync_solution_sql SET sync_name = ?, code_sql = ?, count_sql = ?, is_active = ?, updated_at = CURRENT_TIMESTAMP WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL ` _, err := tx.ExecContext(ctx, query, script.SyncName, script.CodeSQL, script.CountSQL, script.IsActive, script.SyncID, script.SolutionID, ) if err != nil { return tables.SyncSolutionSQLDB{}, err } // 查询更新后的记录 var sqlScript tables.SyncSolutionSQLDB selectQuery := ` SELECT id, sync_id, sync_name, solution_id, code_sql, count_sql, is_active, creator, created_at, updated_at, deleted_at FROM sync_solution_sql WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL ` err = tx.GetContext(ctx, &sqlScript, selectQuery, script.SyncID, script.SolutionID) return sqlScript, err }