Sin descripción
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

save_sync_solution.go 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. package solutionmanagement
  2. import (
  3. "context"
  4. "fmt"
  5. "regexp"
  6. "git.x2erp.com/qdy/go-base/ctx"
  7. "git.x2erp.com/qdy/go-base/logger"
  8. "git.x2erp.com/qdy/go-base/model/response"
  9. "git.x2erp.com/qdy/go-base/util"
  10. "git.x2erp.com/qdy/go-db/factory/database"
  11. "git.x2erp.com/qdy/go-svc-configure/internal/tables"
  12. "github.com/jmoiron/sqlx"
  13. )
  14. // SaveSyncSolution 保存同步方案(主表+子表)
  15. func SaveSyncSolution(req *SyncSolutionRequest, ctx context.Context, dbFactory *database.DBFactory, reqCtx *ctx.RequestContext) *response.QueryResult[SyncSolutionDetail] {
  16. logger.Debug("SaveSyncSolution-开始保存同步方案")
  17. // 参数验证
  18. if err := validateSyncSolutionRequest(req); err != nil {
  19. logger.ErrorC(reqCtx, fmt.Sprintf("参数验证失败: %v", err))
  20. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("参数验证失败: %v", err), reqCtx)
  21. }
  22. // 获取数据库连接并开始事务
  23. db := dbFactory.GetDB()
  24. tx, err := db.BeginTxx(ctx, nil)
  25. if err != nil {
  26. logger.ErrorC(reqCtx, fmt.Sprintf("开始事务失败: %v", err))
  27. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("开始事务失败: %v", err), reqCtx)
  28. }
  29. defer func() {
  30. if p := recover(); p != nil {
  31. tx.Rollback()
  32. panic(p)
  33. }
  34. }()
  35. // 获取当前用户
  36. creator := reqCtx.UserID
  37. if creator == "" {
  38. creator = "system"
  39. }
  40. // 1. 处理主表:检查是否存在 → 存在则更新,不存在则插入
  41. solutionExists, err := checkSolutionExists(ctx, tx, req.SolutionCode)
  42. if err != nil {
  43. tx.Rollback()
  44. logger.ErrorC(reqCtx, fmt.Sprintf("检查方案存在性失败: %v", err))
  45. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("检查方案存在性失败: %v", err), reqCtx)
  46. }
  47. var solution tables.SyncSolutionDB
  48. if solutionExists {
  49. // 更新主表
  50. solution, err = updateSolution(ctx, tx, req, creator)
  51. if err != nil {
  52. tx.Rollback()
  53. logger.ErrorC(reqCtx, fmt.Sprintf("更新方案失败: %v", err))
  54. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("更新方案失败: %v", err), reqCtx)
  55. }
  56. logger.Debug(fmt.Sprintf("更新方案成功: %s", req.SolutionCode))
  57. } else {
  58. // 插入主表
  59. solution, err = insertSolution(ctx, tx, req, creator)
  60. if err != nil {
  61. tx.Rollback()
  62. logger.ErrorC(reqCtx, fmt.Sprintf("插入方案失败: %v", err))
  63. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("插入方案失败: %v", err), reqCtx)
  64. }
  65. logger.Debug(fmt.Sprintf("插入方案成功: %s", req.SolutionCode))
  66. }
  67. // 2. 处理子表:逐条检查 → 存在则更新,不存在则新增
  68. var sqlScripts []tables.SyncSolutionSQLDB
  69. for _, script := range req.SQLScripts {
  70. // 设置关联的solution_id
  71. script.SolutionID = req.SolutionCode
  72. // 检查子表记录是否存在
  73. sqlExists, err := checkSQLScriptExists(ctx, tx, script.SyncID, req.SolutionCode)
  74. if err != nil {
  75. tx.Rollback()
  76. logger.ErrorC(reqCtx, fmt.Sprintf("检查SQL脚本存在性失败: %v", err))
  77. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("检查SQL脚本存在性失败: %v", err), reqCtx)
  78. }
  79. var sqlScript tables.SyncSolutionSQLDB
  80. if sqlExists {
  81. // 更新子表
  82. sqlScript, err = updateSQLScript(ctx, tx, &script, creator)
  83. if err != nil {
  84. tx.Rollback()
  85. logger.ErrorC(reqCtx, fmt.Sprintf("更新SQL脚本失败: %v", err))
  86. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("更新SQL脚本失败: %v", err), reqCtx)
  87. }
  88. logger.Debug(fmt.Sprintf("更新SQL脚本成功: %s", script.SyncID))
  89. } else {
  90. // 插入子表
  91. sqlScript, err = insertSQLScript(ctx, tx, &script, creator)
  92. if err != nil {
  93. tx.Rollback()
  94. logger.ErrorC(reqCtx, fmt.Sprintf("插入SQL脚本失败: %v", err))
  95. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("插入SQL脚本失败: %v", err), reqCtx)
  96. }
  97. logger.Debug(fmt.Sprintf("插入SQL脚本成功: %s", script.SyncID))
  98. }
  99. sqlScripts = append(sqlScripts, sqlScript)
  100. }
  101. // 提交事务
  102. if err := tx.Commit(); err != nil {
  103. logger.ErrorC(reqCtx, fmt.Sprintf("提交事务失败: %v", err))
  104. return util.CreateErrorResult[SyncSolutionDetail](fmt.Sprintf("提交事务失败: %v", err), reqCtx)
  105. }
  106. logger.Debug(fmt.Sprintf("成功保存同步方案: %s, 包含 %d 个SQL脚本", req.SolutionCode, len(sqlScripts)))
  107. // 构建返回结果
  108. detail := SyncSolutionDetail{
  109. Solution: solution,
  110. SQLScripts: sqlScripts,
  111. }
  112. return util.CreateSuccessResultData[SyncSolutionDetail](detail, reqCtx)
  113. }
  114. // validateSyncSolutionRequest 验证同步方案请求
  115. func validateSyncSolutionRequest(req *SyncSolutionRequest) error {
  116. if req.SolutionCode == "" {
  117. return fmt.Errorf("方案代码不能为空")
  118. }
  119. // 验证方案代码格式:仅允许字母、数字、下划线
  120. match, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", req.SolutionCode)
  121. if !match {
  122. return fmt.Errorf("方案代码只能包含字母、数字、下划线")
  123. }
  124. if req.SolutionType == "" {
  125. return fmt.Errorf("方案类型不能为空")
  126. }
  127. // 验证方案类型
  128. validTypes := map[string]bool{"全量": true, "增量": true, "计算": true}
  129. if !validTypes[req.SolutionType] {
  130. return fmt.Errorf("方案类型必须是'全量'、'增量'或'计算'")
  131. }
  132. if req.SolutionName == "" {
  133. return fmt.Errorf("方案名称不能为空")
  134. }
  135. // 验证子表数据
  136. for i, script := range req.SQLScripts {
  137. if script.SyncID == "" {
  138. return fmt.Errorf("第%d个SQL脚本的同步代码不能为空", i+1)
  139. }
  140. if script.SyncName == "" {
  141. return fmt.Errorf("第%d个SQL脚本的同步名称不能为空", i+1)
  142. }
  143. if script.CodeSQL == "" {
  144. return fmt.Errorf("第%d个SQL脚本的SQL代码不能为空", i+1)
  145. }
  146. if script.CountSQL == "" {
  147. return fmt.Errorf("第%d个SQL脚本的统计SQL代码不能为空", i+1)
  148. }
  149. // 验证同步代码格式
  150. match, _ := regexp.MatchString("^[a-zA-Z0-9_.-]+$", script.SyncID)
  151. if !match {
  152. return fmt.Errorf("第%d个SQL脚本的同步代码只能包含字母、数字、下划线、点、横线", i+1)
  153. }
  154. }
  155. return nil
  156. }
  157. // checkSolutionExists 检查方案是否存在
  158. func checkSolutionExists(ctx context.Context, tx *sqlx.Tx, solutionCode string) (bool, error) {
  159. var count int
  160. query := "SELECT COUNT(*) FROM sync_solution WHERE solution_code = ? AND deleted_at IS NULL"
  161. err := tx.GetContext(ctx, &count, query, solutionCode)
  162. return count > 0, err
  163. }
  164. // insertSolution 插入方案主表
  165. func insertSolution(ctx context.Context, tx *sqlx.Tx, req *SyncSolutionRequest, creator string) (tables.SyncSolutionDB, error) {
  166. query := `
  167. INSERT INTO sync_solution (solution_code, solution_type, solution_name, description, creator)
  168. VALUES (?, ?, ?, ?, ?)
  169. `
  170. _, err := tx.ExecContext(ctx, query,
  171. req.SolutionCode,
  172. req.SolutionType,
  173. req.SolutionName,
  174. req.Description,
  175. creator,
  176. )
  177. if err != nil {
  178. return tables.SyncSolutionDB{}, err
  179. }
  180. // 查询刚插入的记录
  181. var solution tables.SyncSolutionDB
  182. selectQuery := `
  183. SELECT id, solution_code, solution_type, solution_name, description, creator, created_at, updated_at, deleted_at
  184. FROM sync_solution
  185. WHERE solution_code = ? AND deleted_at IS NULL
  186. `
  187. err = tx.GetContext(ctx, &solution, selectQuery, req.SolutionCode)
  188. return solution, err
  189. }
  190. // updateSolution 更新方案主表
  191. func updateSolution(ctx context.Context, tx *sqlx.Tx, req *SyncSolutionRequest, creator string) (tables.SyncSolutionDB, error) {
  192. query := `
  193. UPDATE sync_solution
  194. SET solution_type = ?, solution_name = ?, description = ?, updated_at = CURRENT_TIMESTAMP
  195. WHERE solution_code = ? AND deleted_at IS NULL
  196. `
  197. _, err := tx.ExecContext(ctx, query,
  198. req.SolutionType,
  199. req.SolutionName,
  200. req.Description,
  201. req.SolutionCode,
  202. )
  203. if err != nil {
  204. return tables.SyncSolutionDB{}, err
  205. }
  206. // 查询更新后的记录
  207. var solution tables.SyncSolutionDB
  208. selectQuery := `
  209. SELECT id, solution_code, solution_type, solution_name, description, creator, created_at, updated_at, deleted_at
  210. FROM sync_solution
  211. WHERE solution_code = ? AND deleted_at IS NULL
  212. `
  213. err = tx.GetContext(ctx, &solution, selectQuery, req.SolutionCode)
  214. return solution, err
  215. }
  216. // checkSQLScriptExists 检查SQL脚本是否存在
  217. func checkSQLScriptExists(ctx context.Context, tx *sqlx.Tx, syncID, solutionID string) (bool, error) {
  218. var count int
  219. query := "SELECT COUNT(*) FROM sync_solution_sql WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL"
  220. err := tx.GetContext(ctx, &count, query, syncID, solutionID)
  221. return count > 0, err
  222. }
  223. // insertSQLScript 插入SQL脚本子表
  224. func insertSQLScript(ctx context.Context, tx *sqlx.Tx, script *SyncSQLScript, creator string) (tables.SyncSolutionSQLDB, error) {
  225. query := `
  226. INSERT INTO sync_solution_sql (sync_id, sync_name, solution_id, code_sql, count_sql, is_active, creator)
  227. VALUES (?, ?, ?, ?, ?, ?, ?)
  228. `
  229. _, err := tx.ExecContext(ctx, query,
  230. script.SyncID,
  231. script.SyncName,
  232. script.SolutionID,
  233. script.CodeSQL,
  234. script.CountSQL,
  235. script.IsActive,
  236. creator,
  237. )
  238. if err != nil {
  239. return tables.SyncSolutionSQLDB{}, err
  240. }
  241. // 查询刚插入的记录
  242. var sqlScript tables.SyncSolutionSQLDB
  243. selectQuery := `
  244. SELECT id, sync_id, sync_name, solution_id, code_sql, count_sql, is_active, creator, created_at, updated_at, deleted_at
  245. FROM sync_solution_sql
  246. WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL
  247. `
  248. err = tx.GetContext(ctx, &sqlScript, selectQuery, script.SyncID, script.SolutionID)
  249. return sqlScript, err
  250. }
  251. // updateSQLScript 更新SQL脚本子表
  252. func updateSQLScript(ctx context.Context, tx *sqlx.Tx, script *SyncSQLScript, creator string) (tables.SyncSolutionSQLDB, error) {
  253. query := `
  254. UPDATE sync_solution_sql
  255. SET sync_name = ?, code_sql = ?, count_sql = ?, is_active = ?, updated_at = CURRENT_TIMESTAMP
  256. WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL
  257. `
  258. _, err := tx.ExecContext(ctx, query,
  259. script.SyncName,
  260. script.CodeSQL,
  261. script.CountSQL,
  262. script.IsActive,
  263. script.SyncID,
  264. script.SolutionID,
  265. )
  266. if err != nil {
  267. return tables.SyncSolutionSQLDB{}, err
  268. }
  269. // 查询更新后的记录
  270. var sqlScript tables.SyncSolutionSQLDB
  271. selectQuery := `
  272. SELECT id, sync_id, sync_name, solution_id, code_sql, count_sql, is_active, creator, created_at, updated_at, deleted_at
  273. FROM sync_solution_sql
  274. WHERE sync_id = ? AND solution_id = ? AND deleted_at IS NULL
  275. `
  276. err = tx.GetContext(ctx, &sqlScript, selectQuery, script.SyncID, script.SolutionID)
  277. return sqlScript, err
  278. }