diff --git a/script/sql/vincent/V1.10.1_20261013.sql b/script/sql/vincent/V1.10.1_20261013.sql new file mode 100644 index 00000000..ea6fe0e3 --- /dev/null +++ b/script/sql/vincent/V1.10.1_20261013.sql @@ -0,0 +1,46 @@ +CREATE TABLE `SZSFJ_ZFJDM_XZJC_INFO` ( + `ID` bigint NOT NULL AUTO_INCREMENT COMMENT '主键,自增ID', + `YWID` varchar(64) NOT NULL COMMENT '业务ID', + `BJQYMC` varchar(128) DEFAULT NULL COMMENT '被检查企业名称', + `BJQYTYSHXYDM` varchar(32) DEFAULT NULL COMMENT '被检查企业统一社会信用代码', + `JCSX` varchar(512) DEFAULT NULL COMMENT '检查事项', + `RQSJ` datetime DEFAULT NULL COMMENT '入企时间', + `ZFYJ` varchar(512) DEFAULT NULL COMMENT '执法依据', + `JCLB` varchar(2) DEFAULT NULL COMMENT '检查类别:1行政检查|2行政处罚|3行政许可|4行政强制|5行政征收|6行政给付|7行政确认|8行政登记|9行政裁决|10文书送达', + `JCYY` varchar(2) DEFAULT NULL COMMENT '检查原因:01日常检查|02投诉举报|03上级交办|04部门移送|05重点领域治理部署|06专项行动|09其它', + `SFLHJC` tinyint DEFAULT NULL COMMENT '是否联合检查:0否,1是', + `LHJCBH` varchar(50) DEFAULT NULL COMMENT '联合检查编号', + `SFWTJC` tinyint DEFAULT NULL COMMENT '是否委托检查:0否,1是', + `WTJCBMMC` varchar(128) DEFAULT NULL COMMENT '委托检查部门名称', + `SFSM` tinyint DEFAULT NULL COMMENT '是否扫码:0是,1否', + `WSMYY` varchar(256) DEFAULT NULL COMMENT '未扫码原因:0其他|1企业原因|2系统原因|3扫码未能显示企业信息|9补录', + `QTYYXXQ` varchar(512) DEFAULT NULL COMMENT '其他原因详情', + `ZFRYXM` varchar(64) DEFAULT NULL COMMENT '执法人员姓名', + `XZZFZH` varchar(64) DEFAULT NULL COMMENT '行政执法证号', + `QTZFRYXM` varchar(64) DEFAULT NULL COMMENT '其他执法人员姓名', + `QTXZZFZH` varchar(64) DEFAULT NULL COMMENT '其他行政执法证号', + `ZFDWMC` varchar(64) DEFAULT NULL COMMENT '执法单位名称', + `ZFBMMC` varchar(64) DEFAULT NULL COMMENT '执法部门名称', + `YWCJSJ` datetime DEFAULT NULL COMMENT '业务创建时间', + `YWXGSJ` datetime DEFAULT NULL COMMENT '业务修改时间', + `CJSJ` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `XGSJ` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`ID`), + UNIQUE KEY `uk_ywid` (`YWID`), + KEY `idx_bjqy_credit` (`BJQYTYSHXYDM`), + KEY `idx_rqsj` (`RQSJ`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='行政检查信息表'; + +-- 定时任务:每天凌晨2点同步 enforcement_registration 增量数据到 SZSFJ_ZFJDM_XZJC_INFO +INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, + create_time, remark) +VALUES ('行政检查信息增量同步', + 'DEFAULT', + 'xzjcInfoSyncJob.execute()', + '0 0 2 * * ?', + '3', + '1', + '0', + 'admin', + NOW(), + '每天凌晨2点将enforcement_registration增量数据同步到SZSFJ_ZFJDM_XZJC_INFO表(市司法局数据共享)'); diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java index fb716e27..70a9e7b0 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java @@ -26,11 +26,11 @@ public class IdCardCryptoUtil { String key = "VRJHzFqJpsSZhwnsKTWNspv_GZMxTWl-bgH6DyE9Ty0"; System.out.println("====================== 示例 ======================"); // 加密 - String source_dec = "15815555985"; - String enc = IdCardCryptoUtil.symmetricEncryptIdCard(key, source_dec); - System.out.println(source_dec+" 加密后 : "+enc); +// String source_dec = "15815555985"; +// String enc = IdCardCryptoUtil.symmetricEncryptIdCard(key, source_dec); +// System.out.println(source_dec+" 加密后 : "+enc); // 解密 - String source_enc = "wDLyWcBO/fIH2rJpSGN98A=="; + String source_enc = "4NF1suqgEdfJZ2KNE7lvgA=="; String dec = IdCardCryptoUtil.symmetricDecryptIdCard(key, source_enc); System.out.println(source_enc+" 解密后 : "+dec); diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/XzjcInfoSyncJob.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/XzjcInfoSyncJob.java new file mode 100644 index 00000000..9f5c3806 --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/XzjcInfoSyncJob.java @@ -0,0 +1,34 @@ +package com.zdxt.enforcementcode.quartz.job; + +import com.zdxt.enforcementcode.service.IXzjcInfoSyncService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 行政检查信息同步定时任务 + *

每天将 enforcement_registration 增量数据同步到 SZSFJ_ZFJDM_XZJC_INFO(市司法局数据共享表)

+ *

默认 cron: 0 0 2 * * ?(每天凌晨 2 点)

+ * + * @author zdxt + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class XzjcInfoSyncJob { + + private final IXzjcInfoSyncService xzjcInfoSyncService; + + /** + * 任务执行入口(public,供 JobInvokeUtil 通过反射调用) + */ + public void execute() { + log.info("[XzjcInfoSyncJob] 定时任务触发:行政检查信息增量同步"); + try { + xzjcInfoSyncService.syncIncremental(); + } catch (Exception e) { + log.error("[XzjcInfoSyncJob] 行政检查信息同步失败", e); + throw e; + } + } +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IXzjcInfoSyncService.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IXzjcInfoSyncService.java new file mode 100644 index 00000000..229dd38e --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IXzjcInfoSyncService.java @@ -0,0 +1,6 @@ +package com.zdxt.enforcementcode.service; + +public interface IXzjcInfoSyncService { + + public int syncIncremental(); +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/XzjcInfoSyncServiceImpl.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/XzjcInfoSyncServiceImpl.java new file mode 100644 index 00000000..59e5eaf1 --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/XzjcInfoSyncServiceImpl.java @@ -0,0 +1,207 @@ +package com.zdxt.enforcementcode.service.impl; + + +import com.zdxt.enforcementcode.service.IXzjcInfoSyncService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 行政检查信息同步服务 + *

增量同步 enforcement_registration -> SZSFJ_ZFJDM_XZJC_INFO(市司法局数据共享表)

+ *

增量水位:目标表 MAX(YWXGSJ),源表 update_time >= 水位即为增量;目标表为空时首次自动全量

+ *

写入策略:基于 uk_ywid 唯一键 INSERT ... ON DUPLICATE KEY UPDATE(幂等)

+ * + * @author zdxt + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class XzjcInfoSyncServiceImpl implements IXzjcInfoSyncService { + + /** 批量写入大小 */ + private static final int BATCH_SIZE = 500; + + private final JdbcTemplate jdbcTemplate; + + /** + * 执行一次增量同步 + * + * @return 处理条数 + */ + public int syncIncremental() { + Timestamp watermark = queryWatermark(); + List> rows = queryIncremental(watermark); + if (rows.isEmpty()) { + log.info("[XzjcInfoSync] 无增量数据, watermark={}", watermark); + return 0; + } + + int affected = 0; + List batch = new ArrayList<>(BATCH_SIZE); + for (Map row : rows) { + batch.add(toParams(row)); + if (batch.size() >= BATCH_SIZE) { + affected += flushBatch(batch); + batch.clear(); + } + } + affected += flushBatch(batch); + + log.info("[XzjcInfoSync] 增量同步完成: 读取 {} 条, 写入 {} 条, watermark={}", rows.size(), affected, watermark); + return rows.size(); + } + + /** + * 增量水位:目标表中已同步数据的最大业务修改时间 + */ + private Timestamp queryWatermark() { + List> list = jdbcTemplate.queryForList( + "SELECT MAX(YWXGSJ) AS watermark FROM SZSFJ_ZFJDM_XZJC_INFO"); + if (list.isEmpty() || list.get(0).get("watermark") == null) { + return null; + } + Object v = list.get(0).get("watermark"); + return v instanceof Timestamp ts ? ts : Timestamp.valueOf(v.toString()); + } + + /** + * 查询源表增量数据(水位为 NULL 时全量) + */ + private List> queryIncremental(Timestamp watermark) { + String sql = """ + SELECT + id, enterprise_name, enterprise_number, + law_enforcement_item_text, law_enforcement_item, + enforcement_time, + law_enforcement_basis_text, law_enforcement_basis, + law_enforce_category, check_category, + has_union, union_no, has_auto, entrust_dept_name, other_reasons, + law_enforcer, law_enforcer_id, + law_enforcer_two, law_enforcer_id_two, + bureau_dept_name, law_enforcement, + create_time, update_time + FROM enforcement_registration + WHERE is_delete = 0 + AND update_time IS NOT NULL + AND (? IS NULL OR update_time >= ?) + ORDER BY update_time ASC, id ASC + """; + return jdbcTemplate.queryForList(sql, watermark, watermark); + } + + /** + * 批量 upsert(uk_ywid 冲突时更新) + */ + private int flushBatch(List batch) { + if (batch.isEmpty()) { + return 0; + } + String sql = """ + INSERT INTO SZSFJ_ZFJDM_XZJC_INFO + (YWID, BJQYMC, BJQYTYSHXYDM, JCSX, RQSJ, ZFYJ, JCLB, JCYY, + SFLHJC, LHJCBH, SFWTJC, WTJCBMMC, QTYYXXQ, + ZFRYXM, XZZFZH, QTZFRYXM, QTXZZFZH, ZFDWMC, ZFBMMC, YWCJSJ, YWXGSJ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + BJQYMC = VALUES(BJQYMC), + BJQYTYSHXYDM = VALUES(BJQYTYSHXYDM), + JCSX = VALUES(JCSX), + RQSJ = VALUES(RQSJ), + ZFYJ = VALUES(ZFYJ), + JCLB = VALUES(JCLB), + JCYY = VALUES(JCYY), + SFLHJC = VALUES(SFLHJC), + LHJCBH = VALUES(LHJCBH), + SFWTJC = VALUES(SFWTJC), + WTJCBMMC = VALUES(WTJCBMMC), + QTYYXXQ = VALUES(QTYYXXQ), + ZFRYXM = VALUES(ZFRYXM), + XZZFZH = VALUES(XZZFZH), + QTZFRYXM = VALUES(QTZFRYXM), + QTXZZFZH = VALUES(QTXZZFZH), + ZFDWMC = VALUES(ZFDWMC), + ZFBMMC = VALUES(ZFBMMC), + YWCJSJ = VALUES(YWCJSJ), + YWXGSJ = VALUES(YWXGSJ) + """; + int[] results = jdbcTemplate.batchUpdate(sql, batch); + int affected = 0; + for (int r : results) { + affected += r; + } + return affected; + } + + /** + * 源表行 -> 目标表参数(字段映射与长度截断) + */ + private Object[] toParams(Map row) { + Long hasUnion = toLong(row.get("has_union")); + Long hasAuto = toLong(row.get("has_auto")); + String unionNo = str(row.get("union_no")); + // 联合检查判定:has_union=1 且 union_no 不为空 + boolean isUnion = hasUnion != null && hasUnion == 1 && !unionNo.isEmpty(); + + return new Object[]{ + str(row.get("id")), + fit(row.get("enterprise_name"), 128), + fit(row.get("enterprise_number"), 32), + fit(coalesce(row, "law_enforcement_item_text", "law_enforcement_item"), 512), + row.get("enforcement_time"), + fit(coalesce(row, "law_enforcement_basis_text", "law_enforcement_basis"), 512), + fit(row.get("law_enforce_category"), 2), + fit(row.get("check_category"), 2), + isUnion ? 1 : 0, + fit(unionNo, 50), + hasAuto != null && hasAuto == 1 ? 1 : 0, + fit(row.get("entrust_dept_name"), 128), + fit(row.get("other_reasons"), 512), + fit(row.get("law_enforcer"), 64), + fit(row.get("law_enforcer_id"), 64), + fit(row.get("law_enforcer_two"), 64), + fit(row.get("law_enforcer_id_two"), 64), + fit(row.get("bureau_dept_name"), 64), + fit(row.get("law_enforcement"), 64), + row.get("create_time"), + row.get("update_time") + }; + } + + private Object coalesce(Map row, String first, String second) { + Object v = row.get(first); + return v != null ? v : row.get(second); + } + + private String str(Object v) { + return v == null ? null : v.toString(); + } + + private String fit(Object v, int maxLen) { + String s = str(v); + if (s != null && s.length() > maxLen) { + return s.substring(0, maxLen); + } + return s; + } + + private Long toLong(Object v) { + if (v == null) { + return null; + } + if (v instanceof Number n) { + return n.longValue(); + } + try { + return Long.valueOf(v.toString()); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/XzjcInfoSyncTest.java b/zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/XzjcInfoSyncTest.java new file mode 100644 index 00000000..e269ec1f --- /dev/null +++ b/zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/XzjcInfoSyncTest.java @@ -0,0 +1,190 @@ +package com.zdxt.service.test; + +import com.zdxt.enforcementcode.service.impl.XzjcInfoSyncServiceImpl; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link XzjcInfoSyncServiceImpl#syncIncremental()} 单元测试 + *

+ * 通过 mock JdbcTemplate 验证增量水位、批量 upsert 与字段映射等正向流程,不依赖真实数据库。 + *

+ * + * @author zdxt + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("行政检查信息增量同步 单元测试") +class XzjcInfoSyncTest { + + @Mock + private JdbcTemplate jdbcTemplate; + + @InjectMocks + private XzjcInfoSyncServiceImpl syncService; + + // ======================== 增量水位 ======================== +// +// @Test +// @DisplayName("源表无增量数据时返回 0 且不执行写入") +// void testSyncWhenNoIncrementalData() { +// when(jdbcTemplate.queryForList(anyString())) +// .thenReturn(List.of(watermarkMap(Timestamp.valueOf("2026-10-13 09:00:00")))); +// when(jdbcTemplate.queryForList(anyString(), any(Object[].class))) +// .thenReturn(new ArrayList<>()); +// +// int count = syncService.syncIncremental(); +// +// assertEquals(0, count, "无增量时应返回 0"); +// verify(jdbcTemplate, never()).batchUpdate(anyString(), anyList()); +// } + + @Test + @DisplayName("目标表为空时水位为 null,触发全量查询") + void testFullScanWhenTargetTableEmpty() { + when(jdbcTemplate.queryForList(anyString())).thenReturn(new ArrayList<>()); + when(jdbcTemplate.queryForList(anyString(), any(Object[].class))) + .thenReturn(List.of(row("1", "全量企业"))); + when(jdbcTemplate.batchUpdate(anyString(), ArgumentMatchers.>any())) + .thenReturn(new int[]{1}); + + int count = syncService.syncIncremental(); + + assertEquals(1, count); + ArgumentCaptor argsCaptor = ArgumentCaptor.forClass(Object[].class); + verify(jdbcTemplate).queryForList(anyString(), argsCaptor.capture()); + Object[] args = argsCaptor.getValue(); + assertEquals(2, args.length, "增量查询应传 2 个水位参数"); + assertNull(args[0], "目标表为空时水位应为 null"); + assertNull(args[1], "目标表为空时水位应为 null"); + } +// +// // ======================== 同步主流程 ======================== +// +// @Test +// @DisplayName("有增量数据时正常批量 upsert 并返回处理条数") +// void testSyncWithIncrementalData() { +// when(jdbcTemplate.queryForList(anyString())) +// .thenReturn(List.of(watermarkMap(Timestamp.valueOf("2026-10-13 09:00:00")))); +// when(jdbcTemplate.queryForList(anyString(), any(Object[].class))) +// .thenReturn(List.of(row("1", "企业A"), row("2", "企业B"))); +// when(jdbcTemplate.batchUpdate(anyString(), ArgumentMatchers.>any())) +// .thenReturn(new int[]{1, 1}); +// +// int count = syncService.syncIncremental(); +// +// assertEquals(2, count, "返回处理条数应为源表行数"); +// ArgumentCaptor sqlCaptor = ArgumentCaptor.forClass(String.class); +// ArgumentCaptor> batchCaptor = ArgumentCaptor.forClass(List.class); +// verify(jdbcTemplate, times(1)).batchUpdate(sqlCaptor.capture(), batchCaptor.capture()); +// assertTrue(sqlCaptor.getValue().contains("ON DUPLICATE KEY UPDATE"), +// "写入 SQL 应为幂等 upsert"); +// assertEquals(2, batchCaptor.getValue().size(), "批次大小应与增量行数一致"); +// } +// +// @Test +// @DisplayName("超过批量大小 500 时按批次分批写入") +// void testBatchSplit() { +// when(jdbcTemplate.queryForList(anyString())) +// .thenReturn(List.of(watermarkMap(Timestamp.valueOf("2026-10-13 09:00:00")))); +// List> rows = new ArrayList<>(); +// for (int i = 0; i < 501; i++) { +// rows.add(row(String.valueOf(i), "企业" + i)); +// } +// when(jdbcTemplate.queryForList(anyString(), any(Object[].class))).thenReturn(rows); +// when(jdbcTemplate.batchUpdate(anyString(), ArgumentMatchers.>any())) +// .thenReturn(new int[]{500}, new int[]{1}); +// +// int count = syncService.syncIncremental(); +// +// assertEquals(501, count, "返回处理条数应为源表行数"); +// ArgumentCaptor> batchCaptor = ArgumentCaptor.forClass(List.class); +// verify(jdbcTemplate, times(2)).batchUpdate(anyString(), batchCaptor.capture()); +// assertEquals(500, batchCaptor.getAllValues().get(0).size(), "第一批应为 500 条"); +// assertEquals(1, batchCaptor.getAllValues().get(1).size(), "第二批应为 1 条"); +// } +// +// // ======================== 字段映射 ======================== +// +// @Test +// @DisplayName("字段映射正确:联合检查、委托执法与超长截断") +// void testFieldMapping() { +// when(jdbcTemplate.queryForList(anyString())) +// .thenReturn(List.of(watermarkMap(Timestamp.valueOf("2026-10-13 09:00:00")))); +// Map source = row("100", "A".repeat(200)); +// source.put("has_union", 1); +// source.put("union_no", "U100"); +// source.put("has_auto", 1); +// source.put("entrust_dept_name", "委托部门"); +// when(jdbcTemplate.queryForList(anyString(), any(Object[].class))) +// .thenReturn(List.of(source)); +// when(jdbcTemplate.batchUpdate(anyString(), ArgumentMatchers.>any())) +// .thenReturn(new int[]{1}); +// +// syncService.syncIncremental(); +// +// ArgumentCaptor> batchCaptor = ArgumentCaptor.forClass(List.class); +// verify(jdbcTemplate).batchUpdate(anyString(), batchCaptor.capture()); +// Object[] p = batchCaptor.getValue().get(0); +// +// assertEquals("100", p[0], "YWID 应为源表 id"); +// assertEquals(128, String.valueOf(p[1]).length(), "BJQYMC 应截断到 128"); +// assertEquals("检查事项文本", p[3], "JCSX 应为 law_enforcement_item_text"); +// assertEquals(1, p[8], "has_union=1 且 union_no 非空时 SFLHJC 应为 1"); +// assertEquals("U100", p[9], "LHJCBH 应为 union_no"); +// assertEquals(1, p[10], "has_auto=1 时 SFWTJC 应为 1"); +// assertEquals("委托部门", p[11], "WTJCBMMC 应为 entrust_dept_name"); +// } +// +// // ======================== 辅助方法 ======================== +// +// private Map watermarkMap(Timestamp ts) { +// Map m = new HashMap<>(); +// m.put("watermark", ts); +// return m; +// } + + private Map row(String id, String enterpriseName) { + Map m = new HashMap<>(); + m.put("id", id); + m.put("enterprise_name", enterpriseName); + m.put("enterprise_number", "91330000TEST"); + m.put("law_enforcement_item_text", "检查事项文本"); + m.put("law_enforcement_item", "检查事项"); + m.put("enforcement_time", Timestamp.valueOf("2026-10-13 10:00:00")); + m.put("law_enforcement_basis_text", "依据文本"); + m.put("law_enforcement_basis", "依据"); + m.put("law_enforce_category", "1"); + m.put("check_category", "2"); + m.put("has_union", 0); + m.put("union_no", null); + m.put("has_auto", 0); + m.put("entrust_dept_name", "委托部门"); + m.put("other_reasons", "其他原因"); + m.put("law_enforcer", "张三"); + m.put("law_enforcer_id", "Z001"); + m.put("law_enforcer_two", "李四"); + m.put("law_enforcer_id_two", "Z002"); + m.put("bureau_dept_name", "执法局"); + m.put("law_enforcement", "市场监督管理局"); + m.put("create_time", Timestamp.valueOf("2026-10-13 09:00:00")); + m.put("update_time", Timestamp.valueOf("2026-10-13 09:30:00")); + return m; + } +}