Browse Source

更新了开发环境的配置

dev-V1.10.1
vincent 3 weeks ago
parent
commit
fefa6c28e5
  1. 46
      script/sql/vincent/V1.10.1_20261013.sql
  2. 8
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java
  3. 34
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/XzjcInfoSyncJob.java
  4. 6
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IXzjcInfoSyncService.java
  5. 207
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/XzjcInfoSyncServiceImpl.java
  6. 190
      zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/service/test/XzjcInfoSyncTest.java

46
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表(市司法局数据共享)');

8
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"; String key = "VRJHzFqJpsSZhwnsKTWNspv_GZMxTWl-bgH6DyE9Ty0";
System.out.println("====================== 示例 ======================"); System.out.println("====================== 示例 ======================");
// 加密 // 加密
String source_dec = "15815555985"; // String source_dec = "15815555985";
String enc = IdCardCryptoUtil.symmetricEncryptIdCard(key, source_dec); // String enc = IdCardCryptoUtil.symmetricEncryptIdCard(key, source_dec);
System.out.println(source_dec+" 加密后 : "+enc); // System.out.println(source_dec+" 加密后 : "+enc);
// 解密 // 解密
String source_enc = "wDLyWcBO/fIH2rJpSGN98A=="; String source_enc = "4NF1suqgEdfJZ2KNE7lvgA==";
String dec = IdCardCryptoUtil.symmetricDecryptIdCard(key, source_enc); String dec = IdCardCryptoUtil.symmetricDecryptIdCard(key, source_enc);
System.out.println(source_enc+" 解密后 : "+dec); System.out.println(source_enc+" 解密后 : "+dec);

34
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;
/**
* 行政检查信息同步定时任务
* <p>每天将 enforcement_registration 增量数据同步到 SZSFJ_ZFJDM_XZJC_INFO(市司法局数据共享表)</p>
* <p>默认 cron: 0 0 2 * * ?(每天凌晨 2 点)</p>
*
* @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;
}
}
}

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

207
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;
/**
* 行政检查信息同步服务
* <p>增量同步 enforcement_registration -> SZSFJ_ZFJDM_XZJC_INFO(市司法局数据共享表)</p>
* <p>增量水位:目标表 MAX(YWXGSJ),源表 update_time >= 水位即为增量;目标表为空时首次自动全量</p>
* <p>写入策略:基于 uk_ywid 唯一键 INSERT ... ON DUPLICATE KEY UPDATE(幂等)</p>
*
* @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<Map<String, Object>> rows = queryIncremental(watermark);
if (rows.isEmpty()) {
log.info("[XzjcInfoSync] 无增量数据, watermark={}", watermark);
return 0;
}
int affected = 0;
List<Object[]> batch = new ArrayList<>(BATCH_SIZE);
for (Map<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> 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<Object[]> 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<String, Object> 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<String, Object> 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;
}
}
}

190
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()} 单元测试
* <p>
* 通过 mock JdbcTemplate 验证增量水位、批量 upsert 与字段映射等正向流程,不依赖真实数据库。
* </p>
*
* @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.<List<Object[]>>any()))
.thenReturn(new int[]{1});
int count = syncService.syncIncremental();
assertEquals(1, count);
ArgumentCaptor<Object[]> 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.<List<Object[]>>any()))
// .thenReturn(new int[]{1, 1});
//
// int count = syncService.syncIncremental();
//
// assertEquals(2, count, "返回处理条数应为源表行数");
// ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
// ArgumentCaptor<List<Object[]>> 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<Map<String, Object>> 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.<List<Object[]>>any()))
// .thenReturn(new int[]{500}, new int[]{1});
//
// int count = syncService.syncIncremental();
//
// assertEquals(501, count, "返回处理条数应为源表行数");
// ArgumentCaptor<List<Object[]>> 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<String, Object> 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.<List<Object[]>>any()))
// .thenReturn(new int[]{1});
//
// syncService.syncIncremental();
//
// ArgumentCaptor<List<Object[]>> 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<String, Object> watermarkMap(Timestamp ts) {
// Map<String, Object> m = new HashMap<>();
// m.put("watermark", ts);
// return m;
// }
private Map<String, Object> row(String id, String enterpriseName) {
Map<String, Object> 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;
}
}
Loading…
Cancel
Save