yxh
7 天以前 68ff33beff11310e830b0615b5a81f83fad0f683
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package cn.lihu.jh.module.ecg.dal.mysql.queuesequence;
 
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Update;
import org.junit.Test;
 
import java.lang.reflect.Method;
import java.time.LocalDate;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
 
/**
 * 【回归测试】防止 {@code seq_counter} 的原子推进语句再次触发线上故障。
 *
 * <h4>背景(真实故障,2026-10-01)</h4>
 * 预约确认接口 500,日志:
 * <pre>
 * org.mybatis.spring.MyBatisSystemException: ... UnsupportedOperationException
 *   at JsqlParserSupport.processInsert(JsqlParserSupport.java:107)
 *   at DataPermissionInterceptor.beforePrepare(DataPermissionInterceptor.java:86)
 * ### The error may involve QueueSequenceMapper.advanceNormalSeq
 * </pre>
 * <b>根因</b>:MyBatis-Plus 的 {@code DataPermissionInterceptor} 会用 JSqlParser 解析每一条 SQL,
 * 而 JSqlParser 的 {@code processInsert} <b>不支持 MySQL 的
 * {@code INSERT ... ON DUPLICATE KEY UPDATE}</b> 语法,直接抛 {@code UnsupportedOperationException}。
 *
 * <b>修复</b>:在这两个方法上标注 {@code @InterceptorIgnore(dataPermission = "true")}
 * ({@code seq_counter} 表没有 dept_id/user_id,数据权限对它本无意义)。
 *
 * <h4>本测试防的是什么</h4>
 * 若有人后续"清理注解"或改写 SQL 时把这个注解删掉,
 * <b>编译与常规单测都不会报错</b>(只有真实走 MyBatis 拦截器链才会触发),
 * 直到线上签到时才炸。故在此<b>显式断言注解存在</b>。
 */
public class SeqCounterInterceptorIgnoreTest {
 
    private static final String IGNORE_MSG =
            "seq_counter 的原子推进语句使用 MySQL 专有的 INSERT ... ON DUPLICATE KEY UPDATE 语法,"
          + "JSqlParser 无法解析。必须保留 @InterceptorIgnore(dataPermission=\"true\"),"
          + "否则 DataPermissionInterceptor 会抛 UnsupportedOperationException,"
          + "导致预约确认(签到)直接 500。详见该注解上方的故障记录。";
 
    @Test
    public void advanceNormalSeq_mustIgnoreDataPermission() throws Exception {
        assertIgnorePresent("advanceNormalSeq");
    }
 
    @Test
    public void advanceReservedSeq_mustIgnoreDataPermission() throws Exception {
        assertIgnorePresent("advanceReservedSeq");
    }
 
    @Test
    public void advanceSql_shouldStillUseOnDuplicateKeyUpdate() throws Exception {
        for (String name : new String[]{"advanceNormalSeq", "advanceReservedSeq"}) {
            Method m = QueueSequenceMapper.class.getMethod(name, Integer.class, LocalDate.class);
            Update up = m.getAnnotation(Update.class);
            assertNotNull(name + " 应保留 @Update 注解", up);
            String sql = String.join(" ", up.value()).toUpperCase();
            assertTrue(name + " 应使用 ON DUPLICATE KEY UPDATE 实现原子推进(当前 SQL=" + sql + ")",
                    sql.contains("ON DUPLICATE KEY UPDATE"));
            assertTrue(name + " 应作用于 seq_counter 表(当前 SQL=" + sql + ")",
                    sql.contains("SEQ_COUNTER"));
        }
    }
 
    private void assertIgnorePresent(String name) throws Exception {
        Method m = QueueSequenceMapper.class.getMethod(name, Integer.class, LocalDate.class);
        InterceptorIgnore ignore = m.getAnnotation(InterceptorIgnore.class);
        assertNotNull(name + " 缺少 @InterceptorIgnore。\n" + IGNORE_MSG, ignore);
        assertEquals(name + " 的 @InterceptorIgnore.dataPermission 必须为 \"true\"。\n" + IGNORE_MSG,
                "true", ignore.dataPermission());
    }
}