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());
|
}
|
}
|