-- 用途:把 yxe_task.reward 为 NULL 的历史脏数据规范化为 0,消除业绩统计接口的空指针隐患 -- 日期:2026-10-11 -- 背景:小程序 pages/performance/index 页面请求的 /yxe-user-task/userRank 与 /yxe-task/data -- 同时返回 {"code":1,"message":"操作失败","error":"java.lang.NullPointerException"}, -- 页面「我的完成进度/完成率」与人员排行榜全部空白。 -- 根因:yxe_task.reward 为 NULL 的任务被计入完成数后执行 -- BigDecimal.ZERO.add(task.getReward()) -- → BigDecimal.add(null) 抛 NPE,整个接口失败。 -- 本库命中数据为 yxe_task.id=663(title='测试手动',status=2 已完成, -- 其 yxe_user_task.id=630 / user_id=875 的 status=2)。注意任务状态为 2 的只有这 1 条, -- 但它足以让「全量排行榜」接口整体失败——userRank 不传 name 时遍历全部用户。 -- 影响:1 行 -- 性质:幂等,可重复执行;只处理 status 已完成但仍为 NULL 的奖励金额 -- 配套代码: -- 1) 读侧兜底:YxeTaskController#data、YxeUserTaskController#userRank -- 对 reward 做 nullToZero,接口不再报错; -- 2) 写侧堵源:YxeTaskController#save / #saveBatch 在新增时把缺失的 reward 兜底为 0, -- 不再产生新的 NULL 脏数据。 -- 注意 #update / #updateBatch **刻意不补默认值**:二者是部分更新语义 -- (MyBatis-Plus 默认 NOT_NULL 策略),null 表示“本次不修改该字段”, -- 若在那里兜底会把任务原有奖金静默清零,直接影响用户实得收益。 -- 3) 回归测试:src/test/java/com/gxwebsoft/yxe/controller/TaskRewardNullSafetyTest.java -- 说明:本脚本属于「历史数据规范化」。即使不执行,接口也已不再报错(读侧兜底), -- 但金额会按 0 计;执行后数据本身也归于一致。 -- 执行前核对:先看清 NULL 分布在哪些任务、是否已有人完成任务 SELECT t.id AS task_id, t.title, t.status AS task_status, t.reward, COUNT(ut.id) AS user_task_cnt, SUM(ut.status = 2) AS completed_cnt FROM yxe_task t LEFT JOIN yxe_user_task ut ON ut.task_id = t.id WHERE t.reward IS NULL GROUP BY t.id, t.title, t.status, t.reward ORDER BY t.id; -- 变更:仅规范化「已完成/进行中」且 reward 为 NULL 的任务,避免影响尚未开始的任务 UPDATE yxe_task SET reward = 0.00 WHERE reward IS NULL AND status IN (1, 2); -- 执行后核对(remaining_null 应为 0) SELECT SUM(reward IS NULL) AS remaining_null, SUM(reward IS NULL AND status IN (1, 2)) AS remaining_null_active, COUNT(*) AS total FROM yxe_task;