Compare commits
95 Commits
979ed9669f
...
dev-ui
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d004a66e8 | |||
| 2bcba71259 | |||
| ffaa2fb2a0 | |||
| dc8055ce7a | |||
| 90f483f427 | |||
| 2d9828ea5f | |||
| 0a0313af7c | |||
| 175a50bf96 | |||
| 7fec8e21be | |||
| 75d094dad4 | |||
| 17628486ae | |||
| dd424cca50 | |||
| 1561df924f | |||
| 657977dfb7 | |||
| 4a75d68bf6 | |||
| b70f967153 | |||
| 6492fca2dd | |||
| e0188b5e5b | |||
| c3b56bdf2a | |||
| 6d90bb4e58 | |||
| 4a7cfde4af | |||
| 15aa9117c3 | |||
| ded07a2c33 | |||
| 7a317204f7 | |||
| 2c6c982ed3 | |||
| 486a499af7 | |||
| c999497693 | |||
| 3f3e9268f2 | |||
| 9c02812675 | |||
| b7e9a8da03 | |||
| bb41fd7e89 | |||
| 8b9226643c | |||
| 6d2f40843a | |||
| 21c4e91f41 | |||
| 87fb6443e6 | |||
| ce66dc3ba8 | |||
| f3c1e2ea93 | |||
| 87b2352001 | |||
| 999350265b | |||
| f8ee1ecf1c | |||
| 35467fd361 | |||
| 64cb847db3 | |||
| bf290c509c | |||
| c5b2033a3d | |||
| d45e9410ef | |||
| 457e6c1d27 | |||
| 850f97ea22 | |||
| de6e6bd628 | |||
| 3a867e5857 | |||
| 19a60c987e | |||
| 7ce721ef93 | |||
| 000e8698a5 | |||
| 9d3e8beceb | |||
| 0ea504f6b3 | |||
| a39594faf8 | |||
| 1b45296df3 | |||
| 1fadb38d99 | |||
| 9917d10e59 | |||
| be443d1b31 | |||
| b822cc202e | |||
| 598f5dec1c | |||
| 0bf73a923f | |||
| ec67794f88 | |||
| 3ef45bc398 | |||
| 37e17ac903 | |||
| d561d068d6 | |||
| 43bc0e4f65 | |||
| 3fe78d8d3a | |||
| 4c58966529 | |||
| 3bc60fedeb | |||
| 4d1acc7484 | |||
| 402a0c3e2f | |||
| 5980ed0790 | |||
| 75cb8967da | |||
| 90a5c42313 | |||
| 356bcdd6de | |||
| 9a60371a8f | |||
| 380f9b4e7a | |||
| 928f65dfca | |||
| c64146ac40 | |||
| 0541ce0ac6 | |||
| 26c639134e | |||
| 0f7b57e824 | |||
| 104e8697fe | |||
| bbc6a2050b | |||
| bf7a4c0538 | |||
| b2e177dd24 | |||
| 2071d04c08 | |||
| 4988ab5944 | |||
| c00d5475e6 | |||
| 0b64532959 | |||
| 9f0ad4ce87 | |||
| 75b5989774 | |||
| d8c069a836 | |||
| 26be75adad |
84
AGENTS.md
84
AGENTS.md
@@ -1,44 +1,5 @@
|
|||||||
# AGENTS.md - AI Coding Assistant Guide
|
# AGENTS.md - AI Coding Assistant Guide
|
||||||
|
|
||||||
## 全局执行规则
|
|
||||||
|
|
||||||
### Git
|
|
||||||
|
|
||||||
- Git 提交时使用中文添加描述
|
|
||||||
- 无视 `.DS_Store`
|
|
||||||
|
|
||||||
### AGENT
|
|
||||||
|
|
||||||
- `using-superpowers` 只有在用户明确声明调用时才允许启用;不要因为“会话开始”“任务较复杂”或“可能适用”而自动调用
|
|
||||||
- 当用户没有明确声明 `using-superpowers` 时,按普通流程直接处理需求
|
|
||||||
- 默认不开启 subagent
|
|
||||||
- 如用户明确要求启用 subagent,所有 subagent 必须使用 `gpt-5.5`;默认推理强度为高,审查类 subagent 的推理强度为超高,测试类 subagent 的推理强度为中
|
|
||||||
|
|
||||||
### 文档
|
|
||||||
|
|
||||||
- 写完的设计文档必须要由审查类子代理进行内容审查,确保方案与实施方法符合实际需求,并检查是否还有需要明确的功能点
|
|
||||||
- 当功能设计涉及到前端和后端都有改动时,输出两份执行文档,一份为后端的实施计划,一份为前端的实施计划。如果不是前后端架构的项目不需要输出两份执行文档
|
|
||||||
- 当功能修改只涉及到前端或只涉及到后端,只需要输出对应的实施计划
|
|
||||||
- 每一次改动都需要留下实施文档,记录修改的内容
|
|
||||||
- 每次写设计文档的时候,都要检查一下保存路径是否正确
|
|
||||||
|
|
||||||
### 测试
|
|
||||||
|
|
||||||
- 测试结束后,自动结束测试时开启的前后端进程
|
|
||||||
- 前端 Node 需要使用 nvm 进行控制版本
|
|
||||||
- 在完成页面功能开发后,必须使用 `browser-use` 技能打开浏览器进行实际页面测试,禁止打开 prototype 原型页面进行测试
|
|
||||||
- 所有生成的测试文件不需要上传到 Git
|
|
||||||
|
|
||||||
### 方案规范
|
|
||||||
|
|
||||||
当需要给出方案时必须符合以下规范:
|
|
||||||
|
|
||||||
- 不允许给出兼容性或补丁性的方案,不允许过度设计,保持最短路径实现且不能违反第一条要求
|
|
||||||
- 不允许自行给出用户提供的需求以外的方案,例如一些兜底和降级方案,这可能导致业务逻辑偏移问题
|
|
||||||
- 必须确保方案的逻辑正确,必须经过全链路的逻辑验证
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目概述
|
## 项目概述
|
||||||
|
|
||||||
本仓库是纪检初核系统主仓库,基于若依 `v3.9.1`,当前技术栈以 `Java 21 + Spring Boot 3 + Vue 2` 为主,并包含独立的流水分析 Mock 服务、Docker 部署文件、SQL 脚本、实施文档与测试文档。
|
本仓库是纪检初核系统主仓库,基于若依 `v3.9.1`,当前技术栈以 `Java 21 + Spring Boot 3 + Vue 2` 为主,并包含独立的流水分析 Mock 服务、Docker 部署文件、SQL 脚本、实施文档与测试文档。
|
||||||
@@ -54,52 +15,26 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 高优先级规则
|
|
||||||
|
|
||||||
- 使用简体中文进行思考和对话
|
|
||||||
- Git 提交说明必须使用中文
|
|
||||||
- 忽略 `.DS_Store` 文件,不将其视为本次任务需要处理或提交的有效变更
|
|
||||||
- 仅当用户明确声明调用 `using-superpowers` 时才允许启用;未明确声明时按普通流程直接处理需求
|
|
||||||
- Git 提交前必须检查暂存区,仅允许包含本次任务相关文件;若存在无关文件,必须先移出暂存或与用户确认
|
|
||||||
- 每一次改动都需要留下实施文档,记录修改内容、影响范围与验证情况
|
|
||||||
- 功能设计同时涉及前端和后端改动时,必须分别输出后端与前端两份实施计划;若仅涉及单侧,则只输出对应实施计划
|
|
||||||
- 新增或修改设计文档、实施计划、实施记录前,必须先确认保存路径是否正确
|
|
||||||
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
|
|
||||||
- 测试结束后,自动关闭测试过程中启动的前后端进程
|
|
||||||
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`
|
|
||||||
- 禁止在前端源码、配置、示例数据或页面默认值中硬编码或预填真实账号密码;登录页不得将密码保存到 Cookie、localStorage 或 sessionStorage
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 协作约定
|
## 协作约定
|
||||||
|
|
||||||
### 基础协作
|
### 基础协作
|
||||||
|
|
||||||
|
- 使用简体中文进行思考和对话
|
||||||
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
|
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
|
||||||
- 给出方案时,必须保持最短路径实现,不允许提供兼容性、补丁性或过度设计的方案
|
|
||||||
- 不允许自行扩展出用户需求之外的兜底、降级或变体方案,避免业务逻辑偏移
|
|
||||||
- 输出方案前必须完成全链路逻辑校验,确保方案逻辑正确、链路闭环
|
|
||||||
|
|
||||||
### Git 与变更管理
|
### Git 与变更管理
|
||||||
|
|
||||||
- Git 提交前必须检查暂存区,仅保留本次任务相关文件
|
- Git 提交前必须检查暂存区,仅保留本次任务相关文件;若存在无关文件,必须先移出暂存或与用户确认,禁止顺带提交
|
||||||
- 若暂存区存在无关文件,必须先移出暂存或与用户确认,禁止顺带提交
|
|
||||||
- `.DS_Store` 默认忽略,不纳入任务变更范围
|
|
||||||
|
|
||||||
### 文档产出
|
### 文档产出
|
||||||
|
|
||||||
- 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划
|
- 后端实施计划放在 `docs/plans/backend/`,前端实施计划放在 `docs/plans/frontend/`
|
||||||
- 功能设计同时涉及前端和后端改动时,实施计划分别放在 `docs/plans/backend/` 与 `docs/plans/frontend/`
|
- 综合实施计划、联调计划和通用实施文档放在 `docs/plans/fullstack/`
|
||||||
- 功能修改只涉及前端或只涉及后端时,只输出对应的实施计划
|
- 每一次改动都需要留下实施文档,记录修改内容、影响范围与验证情况;实施记录优先放在 `docs/reports/implementation/`
|
||||||
- 非前后端架构项目不强制拆分两份实施计划
|
|
||||||
- 每一次改动都需要留下实施文档,实施记录优先放在 `docs/reports/implementation/`
|
|
||||||
- 每次新增或修改设计文档、实施计划、实施记录前,都要先确认保存路径是否正确
|
|
||||||
|
|
||||||
### 测试与运行
|
### 测试与运行
|
||||||
|
|
||||||
- 测试结束后,自动关闭测试过程中启动的前后端进程
|
|
||||||
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`,不要直接手工执行 `java -jar` 替代正式重启流程
|
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`,不要直接手工执行 `java -jar` 替代正式重启流程
|
||||||
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
|
|
||||||
|
|
||||||
### 数据库与编码
|
### 数据库与编码
|
||||||
|
|
||||||
@@ -258,7 +193,8 @@ return AjaxResult.success(result);
|
|||||||
- 请求统一使用 `@/utils/request`
|
- 请求统一使用 `@/utils/request`
|
||||||
- 新增页面或功能入口时,同步检查 `sys_menu`、路由、权限标识
|
- 新增页面或功能入口时,同步检查 `sys_menu`、路由、权限标识
|
||||||
- 优先延续现有 `ccdi*` 业务目录与命名方式,不随意新造平行目录
|
- 优先延续现有 `ccdi*` 业务目录与命名方式,不随意新造平行目录
|
||||||
- 登录页只能在用户主动选择时保存用户名,不允许保存密码或预填默认密码
|
- 禁止在前端源码、配置、示例数据或页面默认值中硬编码或预填真实账号密码
|
||||||
|
- 登录页只能在用户主动选择时保存用户名,不允许将密码保存到 Cookie、localStorage 或 sessionStorage,也不允许预填默认密码
|
||||||
|
|
||||||
### 导入功能规范
|
### 导入功能规范
|
||||||
|
|
||||||
@@ -386,10 +322,6 @@ ccdi/
|
|||||||
- 新增文档优先落到 `docs/` 下的规范目录,不要继续堆放到 `docs/plans/` 根目录
|
- 新增文档优先落到 `docs/` 下的规范目录,不要继续堆放到 `docs/plans/` 根目录
|
||||||
- 只有历史资料或外部原始材料才放入 `assets/`
|
- 只有历史资料或外部原始材料才放入 `assets/`
|
||||||
- 如果移动了文档,需同步修正文档内引用路径
|
- 如果移动了文档,需同步修正文档内引用路径
|
||||||
- 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划
|
|
||||||
- 功能设计同时涉及前端和后端改动时,必须分别输出后端与前端两份实施计划;若仅涉及前端或仅涉及后端,则只输出对应实施计划;非前后端架构项目不强制拆分双文档
|
|
||||||
- 每一次改动都需要留下实施文档,记录本次修改内容、影响范围与验证情况,实施记录优先放在 `docs/reports/implementation/`
|
|
||||||
- 每次新增或修改设计文档、实施计划、实施记录前,都要先确认保存路径是否正确
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -399,5 +331,3 @@ ccdi/
|
|||||||
- `lsfx-mock-server` 是独立子项目,除非任务涉及流水分析联调,否则不要把其实现规则混入主 Java 工程
|
- `lsfx-mock-server` 是独立子项目,除非任务涉及流水分析联调,否则不要把其实现规则混入主 Java 工程
|
||||||
- `docker/backend`、`docker/frontend`、`docker/mock` 分别对应三类运行时镜像
|
- `docker/backend`、`docker/frontend`、`docker/mock` 分别对应三类运行时镜像
|
||||||
- `sql/migration/` 用于增量迁移脚本,新增修复脚本优先按日期或功能命名
|
- `sql/migration/` 用于增量迁移脚本,新增修复脚本优先按日期或功能命名
|
||||||
- 启动前后端或 Mock 服务做验证后,结束测试时要主动停止进程,避免残留占用端口
|
|
||||||
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
|
|
||||||
|
|||||||
@@ -57,12 +57,6 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.ruoyi</groupId>
|
|
||||||
<artifactId>ccdi-lsfx</artifactId>
|
|
||||||
<version>3.9.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ruoyi.info.collection.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/** 工商信息同步专用线程池。 */
|
||||||
|
@Configuration
|
||||||
|
public class EnterpriseProfileExecutorConfig {
|
||||||
|
|
||||||
|
@Bean("enterpriseProfileExecutor")
|
||||||
|
public Executor enterpriseProfileExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(3);
|
||||||
|
executor.setMaxPoolSize(3);
|
||||||
|
executor.setQueueCapacity(1000);
|
||||||
|
executor.setThreadNamePrefix("enterprise-profile-");
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ import com.ruoyi.common.enums.BusinessType;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiCreditInfoQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiCreditInfoQueryDTO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CreditInfoListVO;
|
import com.ruoyi.info.collection.domain.vo.CreditInfoListVO;
|
||||||
import com.ruoyi.info.collection.service.ICcdiCreditInfoService;
|
import com.ruoyi.info.collection.service.ICcdiCreditInfoService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -42,7 +44,8 @@ public class CcdiCreditInfoController extends BaseController {
|
|||||||
@Log(title = "征信维护", businessType = BusinessType.IMPORT)
|
@Log(title = "征信维护", businessType = BusinessType.IMPORT)
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
public AjaxResult upload(@RequestParam("files") MultipartFile[] files) {
|
public AjaxResult upload(@RequestParam("files") MultipartFile[] files) {
|
||||||
return AjaxResult.success("上传成功", creditInfoService.upload(Arrays.asList(files)));
|
CallerContext caller = CallerContext.from(SecurityUtils.getLoginUser());
|
||||||
|
return AjaxResult.success("上传成功", creditInfoService.upload(Arrays.asList(files), caller));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "查询征信维护列表")
|
@Operation(summary = "查询征信维护列表")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.ruoyi.common.core.page.PageDomain;
|
|||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.core.page.TableSupport;
|
import com.ruoyi.common.core.page.TableSupport;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
|
||||||
@@ -18,6 +19,8 @@ import com.ruoyi.info.collection.domain.vo.ImportResultVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
|
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
|
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseEntitySyncService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -56,6 +59,9 @@ public class CcdiEnterpriseBaseInfoController extends BaseController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService;
|
private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnterpriseEntitySyncService enterpriseEntitySyncService;
|
||||||
|
|
||||||
@Operation(summary = "查询实体库列表")
|
@Operation(summary = "查询实体库列表")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:list')")
|
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:list')")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@@ -73,12 +79,36 @@ public class CcdiEnterpriseBaseInfoController extends BaseController {
|
|||||||
return success(enterpriseBaseInfoService.selectEnterpriseBaseInfoById(socialCreditCode));
|
return success(enterpriseBaseInfoService.selectEnterpriseBaseInfoById(socialCreditCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "重新查询实体工商信息")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:refresh')")
|
||||||
|
@PostMapping("/enterprise-profile/refresh/{socialCreditCode}")
|
||||||
|
public AjaxResult refreshEnterpriseProfile(@PathVariable String socialCreditCode) {
|
||||||
|
enterpriseEntitySyncService.refresh(CallerContext.from(SecurityUtils.getLoginUser()), socialCreditCode);
|
||||||
|
return AjaxResult.success("工商信息更新成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "启动实体工商信息历史补全")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:backfill')")
|
||||||
|
@PostMapping("/enterprise-profile/backfill")
|
||||||
|
public AjaxResult startEnterpriseProfileBackfill() {
|
||||||
|
String taskId = enterpriseEntitySyncService.startBackfill(CallerContext.from(SecurityUtils.getLoginUser()));
|
||||||
|
return AjaxResult.success("工商信息补全任务已提交", taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询实体工商信息历史补全状态")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:backfill')")
|
||||||
|
@GetMapping("/enterprise-profile/backfill/{taskId}")
|
||||||
|
public AjaxResult getEnterpriseProfileBackfillStatus(@PathVariable String taskId) {
|
||||||
|
return AjaxResult.success(enterpriseEntitySyncService.getBackfillStatus(taskId));
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "新增实体库信息")
|
@Operation(summary = "新增实体库信息")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:add')")
|
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:add')")
|
||||||
@Log(title = "实体库管理", businessType = BusinessType.INSERT)
|
@Log(title = "实体库管理", businessType = BusinessType.INSERT)
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public AjaxResult add(@Validated @RequestBody CcdiEnterpriseBaseInfoAddDTO addDTO) {
|
public AjaxResult add(@Validated @RequestBody CcdiEnterpriseBaseInfoAddDTO addDTO) {
|
||||||
return toAjax(enterpriseBaseInfoService.insertEnterpriseBaseInfo(addDTO));
|
return toAjax(enterpriseBaseInfoService.insertEnterpriseBaseInfo(
|
||||||
|
CallerContext.from(SecurityUtils.getLoginUser()), addDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "修改实体库信息")
|
@Operation(summary = "修改实体库信息")
|
||||||
@@ -86,7 +116,8 @@ public class CcdiEnterpriseBaseInfoController extends BaseController {
|
|||||||
@Log(title = "实体库管理", businessType = BusinessType.UPDATE)
|
@Log(title = "实体库管理", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping
|
@PutMapping
|
||||||
public AjaxResult edit(@Validated @RequestBody CcdiEnterpriseBaseInfoEditDTO editDTO) {
|
public AjaxResult edit(@Validated @RequestBody CcdiEnterpriseBaseInfoEditDTO editDTO) {
|
||||||
return toAjax(enterpriseBaseInfoService.updateEnterpriseBaseInfo(editDTO));
|
return toAjax(enterpriseBaseInfoService.updateEnterpriseBaseInfo(
|
||||||
|
CallerContext.from(SecurityUtils.getLoginUser()), editDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "删除实体库信息")
|
@Operation(summary = "删除实体库信息")
|
||||||
@@ -113,7 +144,8 @@ public class CcdiEnterpriseBaseInfoController extends BaseController {
|
|||||||
return error("至少需要一条数据");
|
return error("至少需要一条数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
String taskId = enterpriseBaseInfoService.importEnterpriseBaseInfo(list);
|
String taskId = enterpriseBaseInfoService.importEnterpriseBaseInfo(
|
||||||
|
CallerContext.from(SecurityUtils.getLoginUser()), list);
|
||||||
ImportResultVO result = new ImportResultVO();
|
ImportResultVO result = new ImportResultVO();
|
||||||
result.setTaskId(taskId);
|
result.setTaskId(taskId);
|
||||||
result.setStatus("PROCESSING");
|
result.setStatus("PROCESSING");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +49,30 @@ public class CcdiEnterpriseBaseInfo implements Serializable {
|
|||||||
/** 法定代表人 */
|
/** 法定代表人 */
|
||||||
private String legalRepresentative;
|
private String legalRepresentative;
|
||||||
|
|
||||||
|
/** 注册资本 */
|
||||||
|
private BigDecimal registeredCapital;
|
||||||
|
|
||||||
|
/** 注册资本单位 */
|
||||||
|
private String registeredCapitalUnit;
|
||||||
|
|
||||||
|
/** 注册日期 */
|
||||||
|
private Date registerDate;
|
||||||
|
|
||||||
|
/** 区域编码 */
|
||||||
|
private String regionCode;
|
||||||
|
|
||||||
|
/** 区域名称 */
|
||||||
|
private String regionName;
|
||||||
|
|
||||||
|
/** 从业人数 */
|
||||||
|
private Integer employeeCount;
|
||||||
|
|
||||||
|
/** 工商缓存ID */
|
||||||
|
private Long cacheInfoId;
|
||||||
|
|
||||||
|
/** 工商同步时间 */
|
||||||
|
private Date enterpriseSyncTime;
|
||||||
|
|
||||||
/** 法定代表人证件类型 */
|
/** 法定代表人证件类型 */
|
||||||
private String legalCertType;
|
private String legalCertType;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.ruoyi.info.collection.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 工商信息原始响应缓存。 */
|
||||||
|
@Data
|
||||||
|
@TableName("ccdi_enterpriseinfo_query_cache")
|
||||||
|
public class CcdiEnterpriseInfoQueryCache {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long infoId;
|
||||||
|
private String queryParam;
|
||||||
|
private String queryResult;
|
||||||
|
private String queryType;
|
||||||
|
private Date createdDate;
|
||||||
|
private Date validDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.ruoyi.info.collection.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 实体库企业完整股东。 */
|
||||||
|
@Data
|
||||||
|
@TableName("ccdi_enterprise_shareholder")
|
||||||
|
public class CcdiEnterpriseShareholder {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long shareholderId;
|
||||||
|
private String socialCreditCode;
|
||||||
|
private Integer shareholderSeq;
|
||||||
|
private String shareholderName;
|
||||||
|
private String shareholderType;
|
||||||
|
private String shareholderCreditCode;
|
||||||
|
private BigDecimal stockPercent;
|
||||||
|
private BigDecimal subscribedCapital;
|
||||||
|
private String capitalUnit;
|
||||||
|
private Long cacheInfoId;
|
||||||
|
private Date syncTime;
|
||||||
|
private String createBy;
|
||||||
|
private Date createTime;
|
||||||
|
private String updateBy;
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -39,6 +39,10 @@ public class CcdiAccountInfoQueryDTO implements Serializable {
|
|||||||
@Schema(description = "账户姓名")
|
@Schema(description = "账户姓名")
|
||||||
private String accountName;
|
private String accountName;
|
||||||
|
|
||||||
|
/** 账户号码 */
|
||||||
|
@Schema(description = "账户号码")
|
||||||
|
private String accountNo;
|
||||||
|
|
||||||
/** 账户类型 */
|
/** 账户类型 */
|
||||||
@Schema(description = "账户类型")
|
@Schema(description = "账户类型")
|
||||||
private String accountType;
|
private String accountType;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.ruoyi.info.collection.domain.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 统一工商信息结构。 */
|
||||||
|
@Data
|
||||||
|
public class EnterpriseProfile {
|
||||||
|
private String creditCode;
|
||||||
|
private String enterpriseName;
|
||||||
|
private BigDecimal registeredCapital;
|
||||||
|
private String registeredCapitalUnit;
|
||||||
|
private LocalDate registerDate;
|
||||||
|
private LocalDate establishDate;
|
||||||
|
private String industryCode;
|
||||||
|
private String industryName;
|
||||||
|
private String organizationTypeCode;
|
||||||
|
private String organizationTypeName;
|
||||||
|
private String regionCode;
|
||||||
|
private String regionName;
|
||||||
|
private String registerAddress;
|
||||||
|
private Integer employeeCount;
|
||||||
|
private String legalRepresentative;
|
||||||
|
private List<EnterpriseShareholderProfile> shareholders = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.ruoyi.info.collection.domain.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 统一工商查询结果。 */
|
||||||
|
public record EnterpriseProfileQueryResult(
|
||||||
|
EnterpriseProfile profile,
|
||||||
|
Long cacheInfoId,
|
||||||
|
Date validDate,
|
||||||
|
boolean fromCache
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.info.collection.domain.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/** 工商股东结构。 */
|
||||||
|
@Data
|
||||||
|
public class EnterpriseShareholderProfile {
|
||||||
|
private Integer sequence;
|
||||||
|
private String shareholderName;
|
||||||
|
private BigDecimal stockPercent;
|
||||||
|
private BigDecimal subscribedCapital;
|
||||||
|
private String capitalUnit;
|
||||||
|
}
|
||||||
@@ -5,7 +5,10 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体库管理 VO
|
* 实体库管理 VO
|
||||||
@@ -47,6 +50,39 @@ public class CcdiEnterpriseBaseInfoVO implements Serializable {
|
|||||||
@Schema(description = "法定代表人")
|
@Schema(description = "法定代表人")
|
||||||
private String legalRepresentative;
|
private String legalRepresentative;
|
||||||
|
|
||||||
|
@Schema(description = "注册资本")
|
||||||
|
private BigDecimal registeredCapital;
|
||||||
|
|
||||||
|
@Schema(description = "注册资本单位")
|
||||||
|
private String registeredCapitalUnit;
|
||||||
|
|
||||||
|
@Schema(description = "注册日期")
|
||||||
|
private Date registerDate;
|
||||||
|
|
||||||
|
@Schema(description = "区域编码")
|
||||||
|
private String regionCode;
|
||||||
|
|
||||||
|
@Schema(description = "区域名称")
|
||||||
|
private String regionName;
|
||||||
|
|
||||||
|
@Schema(description = "从业人数")
|
||||||
|
private Integer employeeCount;
|
||||||
|
|
||||||
|
@Schema(description = "工商缓存ID")
|
||||||
|
private Long cacheInfoId;
|
||||||
|
|
||||||
|
@Schema(description = "工商同步时间")
|
||||||
|
private Date enterpriseSyncTime;
|
||||||
|
|
||||||
|
@Schema(description = "缓存有效期")
|
||||||
|
private Date enterpriseCacheValidDate;
|
||||||
|
|
||||||
|
@Schema(description = "是否允许重新查询")
|
||||||
|
private Boolean canRefreshEnterpriseProfile;
|
||||||
|
|
||||||
|
@Schema(description = "完整股东列表")
|
||||||
|
private List<EnterpriseShareholderVO> shareholders = new ArrayList<>();
|
||||||
|
|
||||||
@Schema(description = "法定代表人证件类型")
|
@Schema(description = "法定代表人证件类型")
|
||||||
private String legalCertType;
|
private String legalCertType;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.info.collection.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/** 工商信息历史补全任务状态。 */
|
||||||
|
@Data
|
||||||
|
public class EnterpriseProfileSyncTaskVO {
|
||||||
|
private String taskId;
|
||||||
|
private String status;
|
||||||
|
private Integer totalCount;
|
||||||
|
private Integer completedCount;
|
||||||
|
private Integer successCount;
|
||||||
|
private Integer skippedCount;
|
||||||
|
private Integer failureCount;
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ruoyi.info.collection.domain.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/** 企业股东明细。 */
|
||||||
|
@Data
|
||||||
|
@Schema(description = "企业股东明细")
|
||||||
|
public class EnterpriseShareholderVO {
|
||||||
|
private Integer shareholderSeq;
|
||||||
|
private String shareholderName;
|
||||||
|
private BigDecimal stockPercent;
|
||||||
|
private BigDecimal subscribedCapital;
|
||||||
|
private String capitalUnit;
|
||||||
|
}
|
||||||
@@ -28,6 +28,14 @@ public interface CcdiBaseStaffMapper extends BaseMapper<CcdiBaseStaff> {
|
|||||||
Page<CcdiBaseStaffVO> selectBaseStaffPageWithDept(@Param("page") Page<CcdiBaseStaffVO> page,
|
Page<CcdiBaseStaffVO> selectBaseStaffPageWithDept(@Param("page") Page<CcdiBaseStaffVO> page,
|
||||||
@Param("query") CcdiBaseStaffQueryDTO queryDTO);
|
@Param("query") CcdiBaseStaffQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询员工详情(包含部门名称)
|
||||||
|
*
|
||||||
|
* @param staffId 员工ID
|
||||||
|
* @return 员工详情
|
||||||
|
*/
|
||||||
|
CcdiBaseStaffVO selectBaseStaffByIdWithDept(@Param("staffId") Long staffId);
|
||||||
|
|
||||||
int insertOrUpdateBatch(@Param("list") List<CcdiBaseStaff> list);
|
int insertOrUpdateBatch(@Param("list") List<CcdiBaseStaff> list);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -52,4 +52,8 @@ public interface CcdiEnterpriseBaseInfoMapper extends BaseMapper<CcdiEnterpriseB
|
|||||||
* @return 更新行数
|
* @return 更新行数
|
||||||
*/
|
*/
|
||||||
int updateBatch(List<CcdiEnterpriseBaseInfo> list);
|
int updateBatch(List<CcdiEnterpriseBaseInfo> list);
|
||||||
|
|
||||||
|
int updateEnterpriseProfile(CcdiEnterpriseBaseInfo entity);
|
||||||
|
|
||||||
|
List<CcdiEnterpriseBaseInfo> selectEnterpriseProfileCandidates();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.ruoyi.info.collection.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 工商信息缓存 Mapper。 */
|
||||||
|
public interface CcdiEnterpriseInfoQueryCacheMapper extends BaseMapper<CcdiEnterpriseInfoQueryCache> {
|
||||||
|
CcdiEnterpriseInfoQueryCache selectValidCache(@Param("queryParam") String queryParam,
|
||||||
|
@Param("queryType") String queryType,
|
||||||
|
@Param("now") Date now);
|
||||||
|
|
||||||
|
int upsertCache(CcdiEnterpriseInfoQueryCache cache);
|
||||||
|
|
||||||
|
CcdiEnterpriseInfoQueryCache selectCache(@Param("queryParam") String queryParam,
|
||||||
|
@Param("queryType") String queryType);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.info.collection.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseShareholder;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 实体工商股东 Mapper。 */
|
||||||
|
public interface CcdiEnterpriseShareholderMapper extends BaseMapper<CcdiEnterpriseShareholder> {
|
||||||
|
List<CcdiEnterpriseShareholder> selectByCreditCode(@Param("creditCode") String creditCode);
|
||||||
|
int deleteByCreditCode(@Param("creditCode") String creditCode);
|
||||||
|
int insertBatch(@Param("list") List<CcdiEnterpriseShareholder> list);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiCreditInfoQueryDTO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.CreditInfoDetailVO;
|
import com.ruoyi.info.collection.domain.vo.CreditInfoDetailVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CreditInfoListVO;
|
import com.ruoyi.info.collection.domain.vo.CreditInfoListVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO;
|
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -16,7 +17,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface ICcdiCreditInfoService {
|
public interface ICcdiCreditInfoService {
|
||||||
|
|
||||||
CreditInfoUploadResultVO upload(List<MultipartFile> files);
|
CreditInfoUploadResultVO upload(List<MultipartFile> files, CallerContext caller);
|
||||||
|
|
||||||
Page<CreditInfoListVO> selectCreditInfoPage(Page<CreditInfoListVO> page, CcdiCreditInfoQueryDTO queryDTO);
|
Page<CreditInfoListVO> selectCreditInfoPage(Page<CreditInfoListVO> page, CcdiCreditInfoQueryDTO queryDTO);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
|||||||
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.EnterpriseBaseInfoImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.EnterpriseBaseInfoImportFailureVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -16,7 +17,7 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public interface ICcdiEnterpriseBaseInfoImportService {
|
public interface ICcdiEnterpriseBaseInfoImportService {
|
||||||
|
|
||||||
void importEnterpriseBaseInfoAsync(List<CcdiEnterpriseBaseInfoExcel> excelList, String taskId, String userName);
|
void importEnterpriseBaseInfoAsync(List<CcdiEnterpriseBaseInfoExcel> excelList, String taskId, CallerContext caller);
|
||||||
|
|
||||||
ImportStatusVO getImportStatus(String taskId);
|
ImportStatusVO getImportStatus(String taskId);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -22,13 +23,13 @@ public interface ICcdiEnterpriseBaseInfoService {
|
|||||||
|
|
||||||
CcdiEnterpriseBaseInfoVO selectEnterpriseBaseInfoById(String socialCreditCode);
|
CcdiEnterpriseBaseInfoVO selectEnterpriseBaseInfoById(String socialCreditCode);
|
||||||
|
|
||||||
int insertEnterpriseBaseInfo(CcdiEnterpriseBaseInfoAddDTO addDTO);
|
int insertEnterpriseBaseInfo(CallerContext caller, CcdiEnterpriseBaseInfoAddDTO addDTO);
|
||||||
|
|
||||||
int updateEnterpriseBaseInfo(CcdiEnterpriseBaseInfoEditDTO editDTO);
|
int updateEnterpriseBaseInfo(CallerContext caller, CcdiEnterpriseBaseInfoEditDTO editDTO);
|
||||||
|
|
||||||
int deleteEnterpriseBaseInfoByIds(String[] socialCreditCodes);
|
int deleteEnterpriseBaseInfoByIds(String[] socialCreditCodes);
|
||||||
|
|
||||||
List<CcdiEnterpriseBaseInfoExcel> selectEnterpriseBaseInfoListForExport(CcdiEnterpriseBaseInfoQueryDTO queryDTO);
|
List<CcdiEnterpriseBaseInfoExcel> selectEnterpriseBaseInfoListForExport(CcdiEnterpriseBaseInfoQueryDTO queryDTO);
|
||||||
|
|
||||||
String importEnterpriseBaseInfo(List<CcdiEnterpriseBaseInfoExcel> excelList);
|
String importEnterpriseBaseInfo(CallerContext caller, List<CcdiEnterpriseBaseInfoExcel> excelList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
|
import com.ruoyi.info.collection.domain.vo.EnterpriseProfileSyncTaskVO;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 实体库工商信息同步服务。 */
|
||||||
|
public interface IEnterpriseEntitySyncService {
|
||||||
|
void submitSync(CallerContext caller, String socialCreditCode);
|
||||||
|
void submitSyncBatch(CallerContext caller, List<String> socialCreditCodes);
|
||||||
|
void refresh(CallerContext caller, String socialCreditCode);
|
||||||
|
String startBackfill(CallerContext caller);
|
||||||
|
EnterpriseProfileSyncTaskVO getBackfillStatus(String taskId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseProfileQueryResult;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
|
||||||
|
/** 统一工商信息查询服务。 */
|
||||||
|
public interface IEnterpriseProfileQueryService {
|
||||||
|
EnterpriseProfileQueryResult query(CallerContext caller, String enterpriseName, boolean forceRefresh);
|
||||||
|
CcdiEnterpriseInfoQueryCache findCache(String enterpriseName);
|
||||||
|
}
|
||||||
@@ -49,6 +49,10 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
private static final Set<String> ACCOUNT_TYPES = Set.of("BANK", "SECURITIES", "PAYMENT", "OTHER");
|
private static final Set<String> ACCOUNT_TYPES = Set.of("BANK", "SECURITIES", "PAYMENT", "OTHER");
|
||||||
private static final Set<String> BANK_SCOPES = Set.of("INTERNAL", "EXTERNAL");
|
private static final Set<String> BANK_SCOPES = Set.of("INTERNAL", "EXTERNAL");
|
||||||
private static final Set<String> LEVELS = Set.of("LOW", "MEDIUM", "HIGH");
|
private static final Set<String> LEVELS = Set.of("LOW", "MEDIUM", "HIGH");
|
||||||
|
private static final String OWNER_TYPE_INTERMEDIARY = "INTERMEDIARY";
|
||||||
|
private static final String BANK_SCOPE_EXTERNAL = "EXTERNAL";
|
||||||
|
private static final String RISK_LEVEL_LOW = "LOW";
|
||||||
|
private static final String RISK_LEVEL_HIGH = "HIGH";
|
||||||
private final List<AccountInfoImportFailureVO> latestImportFailures = new CopyOnWriteArrayList<>();
|
private final List<AccountInfoImportFailureVO> latestImportFailures = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@@ -195,12 +199,12 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
|
|
||||||
private void validateOwner(String ownerType, String ownerId) {
|
private void validateOwner(String ownerType, String ownerId) {
|
||||||
if (StringUtils.isEmpty(ownerId)) {
|
if (StringUtils.isEmpty(ownerId)) {
|
||||||
if ("EXTERNAL".equals(ownerType) || "INTERMEDIARY".equals(ownerType)) {
|
if ("EXTERNAL".equals(ownerType) || OWNER_TYPE_INTERMEDIARY.equals(ownerType)) {
|
||||||
throw new RuntimeException("证件号不能为空");
|
throw new RuntimeException("证件号不能为空");
|
||||||
}
|
}
|
||||||
throw new RuntimeException("所属人不能为空");
|
throw new RuntimeException("所属人不能为空");
|
||||||
}
|
}
|
||||||
if ("EXTERNAL".equals(ownerType) || "INTERMEDIARY".equals(ownerType)) {
|
if ("EXTERNAL".equals(ownerType) || OWNER_TYPE_INTERMEDIARY.equals(ownerType)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ("EMPLOYEE".equals(ownerType)) {
|
if ("EMPLOYEE".equals(ownerType)) {
|
||||||
@@ -232,8 +236,9 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void prepareAnalysisFields(CcdiAccountInfo accountInfo) {
|
private void prepareAnalysisFields(CcdiAccountInfo accountInfo) {
|
||||||
if (!"EXTERNAL".equals(accountInfo.getBankScope())) {
|
if (!BANK_SCOPE_EXTERNAL.equals(accountInfo.getBankScope())) {
|
||||||
clearAnalysisFields(accountInfo);
|
clearAnalysisFields(accountInfo);
|
||||||
|
applyIntermediaryRiskLevel(accountInfo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (accountInfo.getIsActualControl() == null) {
|
if (accountInfo.getIsActualControl() == null) {
|
||||||
@@ -249,8 +254,9 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
accountInfo.setTxnFrequencyLevel("MEDIUM");
|
accountInfo.setTxnFrequencyLevel("MEDIUM");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(accountInfo.getTxnRiskLevel())) {
|
if (StringUtils.isEmpty(accountInfo.getTxnRiskLevel())) {
|
||||||
accountInfo.setTxnRiskLevel("LOW");
|
accountInfo.setTxnRiskLevel(RISK_LEVEL_LOW);
|
||||||
}
|
}
|
||||||
|
applyIntermediaryRiskLevel(accountInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearAnalysisFields(CcdiAccountInfo accountInfo) {
|
private void clearAnalysisFields(CcdiAccountInfo accountInfo) {
|
||||||
@@ -265,6 +271,12 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
accountInfo.setTxnRiskLevel(null);
|
accountInfo.setTxnRiskLevel(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyIntermediaryRiskLevel(CcdiAccountInfo accountInfo) {
|
||||||
|
if (OWNER_TYPE_INTERMEDIARY.equals(accountInfo.getOwnerType())) {
|
||||||
|
accountInfo.setTxnRiskLevel(RISK_LEVEL_HIGH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateAmount(BigDecimal amount, String fieldLabel) {
|
private void validateAmount(BigDecimal amount, String fieldLabel) {
|
||||||
if (amount == null) {
|
if (amount == null) {
|
||||||
return;
|
return;
|
||||||
@@ -285,6 +297,7 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
addDTO.setTxnFrequencyLevel(toUpper(addDTO.getTxnFrequencyLevel()));
|
addDTO.setTxnFrequencyLevel(toUpper(addDTO.getTxnFrequencyLevel()));
|
||||||
addDTO.setTxnRiskLevel(toUpper(addDTO.getTxnRiskLevel()));
|
addDTO.setTxnRiskLevel(toUpper(addDTO.getTxnRiskLevel()));
|
||||||
addDTO.setOwnerId(normalizeOwnerId(addDTO.getOwnerId()));
|
addDTO.setOwnerId(normalizeOwnerId(addDTO.getOwnerId()));
|
||||||
|
applyIntermediaryRiskLevel(addDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void normalizeEditDto(CcdiAccountInfoEditDTO editDTO) {
|
private void normalizeEditDto(CcdiAccountInfoEditDTO editDTO) {
|
||||||
@@ -295,6 +308,19 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
editDTO.setTxnFrequencyLevel(toUpper(editDTO.getTxnFrequencyLevel()));
|
editDTO.setTxnFrequencyLevel(toUpper(editDTO.getTxnFrequencyLevel()));
|
||||||
editDTO.setTxnRiskLevel(toUpper(editDTO.getTxnRiskLevel()));
|
editDTO.setTxnRiskLevel(toUpper(editDTO.getTxnRiskLevel()));
|
||||||
editDTO.setOwnerId(normalizeOwnerId(editDTO.getOwnerId()));
|
editDTO.setOwnerId(normalizeOwnerId(editDTO.getOwnerId()));
|
||||||
|
applyIntermediaryRiskLevel(editDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyIntermediaryRiskLevel(CcdiAccountInfoAddDTO addDTO) {
|
||||||
|
if (OWNER_TYPE_INTERMEDIARY.equals(addDTO.getOwnerType())) {
|
||||||
|
addDTO.setTxnRiskLevel(RISK_LEVEL_HIGH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyIntermediaryRiskLevel(CcdiAccountInfoEditDTO editDTO) {
|
||||||
|
if (OWNER_TYPE_INTERMEDIARY.equals(editDTO.getOwnerType())) {
|
||||||
|
editDTO.setTxnRiskLevel(RISK_LEVEL_HIGH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeCurrency(String currency) {
|
private String normalizeCurrency(String currency) {
|
||||||
@@ -362,7 +388,8 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
|
|
||||||
private CcdiAccountInfoAddDTO toAddDto(CcdiAccountInfoExcel excel) {
|
private CcdiAccountInfoAddDTO toAddDto(CcdiAccountInfoExcel excel) {
|
||||||
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
|
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
|
||||||
dto.setOwnerType(parseOwnerType(excel.getOwnerType()));
|
String ownerType = parseOwnerType(excel.getOwnerType());
|
||||||
|
dto.setOwnerType(ownerType);
|
||||||
dto.setOwnerId(normalizeOwnerId(excel.getOwnerId()));
|
dto.setOwnerId(normalizeOwnerId(excel.getOwnerId()));
|
||||||
dto.setAccountName(trimToNull(excel.getAccountName()));
|
dto.setAccountName(trimToNull(excel.getAccountName()));
|
||||||
dto.setAccountNo(trimToNull(excel.getAccountNo()));
|
dto.setAccountNo(trimToNull(excel.getAccountNo()));
|
||||||
@@ -382,7 +409,9 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
dto.setCreditSingleMaxAmount(parseDecimal(excel.getCreditSingleMaxAmount()));
|
dto.setCreditSingleMaxAmount(parseDecimal(excel.getCreditSingleMaxAmount()));
|
||||||
dto.setDebitDailyMaxAmount(parseDecimal(excel.getDebitDailyMaxAmount()));
|
dto.setDebitDailyMaxAmount(parseDecimal(excel.getDebitDailyMaxAmount()));
|
||||||
dto.setCreditDailyMaxAmount(parseDecimal(excel.getCreditDailyMaxAmount()));
|
dto.setCreditDailyMaxAmount(parseDecimal(excel.getCreditDailyMaxAmount()));
|
||||||
dto.setTxnRiskLevel(parseLevel(excel.getTxnRiskLevel(), "风险等级"));
|
dto.setTxnRiskLevel(OWNER_TYPE_INTERMEDIARY.equals(ownerType)
|
||||||
|
? RISK_LEVEL_HIGH
|
||||||
|
: parseLevel(excel.getTxnRiskLevel(), "风险等级"));
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +447,7 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
|
|||||||
return "RELATION";
|
return "RELATION";
|
||||||
}
|
}
|
||||||
if ("中介".equals(value)) {
|
if ("中介".equals(value)) {
|
||||||
return "INTERMEDIARY";
|
return OWNER_TYPE_INTERMEDIARY;
|
||||||
}
|
}
|
||||||
if ("外部人员".equals(value)) {
|
if ("外部人员".equals(value)) {
|
||||||
return "EXTERNAL";
|
return "EXTERNAL";
|
||||||
|
|||||||
@@ -119,10 +119,10 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public CcdiBaseStaffVO selectBaseStaffById(Long staffId) {
|
public CcdiBaseStaffVO selectBaseStaffById(Long staffId) {
|
||||||
CcdiBaseStaff staff = baseStaffMapper.selectById(staffId);
|
CcdiBaseStaffVO vo = baseStaffMapper.selectBaseStaffByIdWithDept(staffId);
|
||||||
CcdiBaseStaffVO vo = convertToVO(staff);
|
if (vo != null) {
|
||||||
if (staff != null) {
|
vo.setStatusDesc(EmployeeStatus.getDescByCode(vo.getStatus()));
|
||||||
vo.setAssetInfoList(assetInfoService.selectByFamilyIdAndPersonId(staff.getIdCard(), staff.getIdCard()).stream().map(asset -> {
|
vo.setAssetInfoList(assetInfoService.selectByFamilyIdAndPersonId(vo.getIdCard(), vo.getIdCard()).stream().map(asset -> {
|
||||||
CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO();
|
CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO();
|
||||||
BeanUtils.copyProperties(asset, assetInfoVO);
|
BeanUtils.copyProperties(asset, assetInfoVO);
|
||||||
return assetInfoVO;
|
return assetInfoVO;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.ruoyi.info.collection.service.impl;
|
package com.ruoyi.info.collection.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
|
||||||
import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo;
|
import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo;
|
||||||
import com.ruoyi.info.collection.domain.CcdiDebtsInfo;
|
import com.ruoyi.info.collection.domain.CcdiDebtsInfo;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiCreditInfoQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiCreditInfoQueryDTO;
|
||||||
@@ -17,10 +16,12 @@ import com.ruoyi.info.collection.service.ICcdiCreditInfoService;
|
|||||||
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
|
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
|
||||||
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
|
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
|
||||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParsePayload;
|
import com.ruoyi.lsfx.domain.response.CreditParsePayload;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -33,6 +34,7 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* 征信维护服务实现
|
* 征信维护服务实现
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
||||||
|
|
||||||
@@ -59,12 +61,12 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
private CcdiCreditInfoQueryMapper queryMapper;
|
private CcdiCreditInfoQueryMapper queryMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CreditInfoUploadResultVO upload(List<MultipartFile> files) {
|
public CreditInfoUploadResultVO upload(List<MultipartFile> files, CallerContext caller) {
|
||||||
CreditInfoUploadResultVO result = new CreditInfoUploadResultVO();
|
CreditInfoUploadResultVO result = new CreditInfoUploadResultVO();
|
||||||
List<CreditInfoUploadFailureVO> failures = new ArrayList<>();
|
List<CreditInfoUploadFailureVO> failures = new ArrayList<>();
|
||||||
int totalCount = files == null ? 0 : files.size();
|
int totalCount = files == null ? 0 : files.size();
|
||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
String userName = currentUserName();
|
String userName = caller.username();
|
||||||
|
|
||||||
if (files == null || files.isEmpty()) {
|
if (files == null || files.isEmpty()) {
|
||||||
result.setTotalCount(0);
|
result.setTotalCount(0);
|
||||||
@@ -77,7 +79,7 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
try {
|
try {
|
||||||
validateHtmlFile(file);
|
validateHtmlFile(file);
|
||||||
handleSingleFile(file, userName);
|
handleSingleFile(file, userName, caller);
|
||||||
successCount++;
|
successCount++;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failures.add(buildFailure(file, null, null, e.getMessage()));
|
failures.add(buildFailure(file, null, null, e.getMessage()));
|
||||||
@@ -148,9 +150,9 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSingleFile(MultipartFile multipartFile, String userName) throws Exception {
|
private void handleSingleFile(MultipartFile multipartFile, String userName, CallerContext caller) throws Exception {
|
||||||
CreditHtmlStorageService.StoredCreditHtml storedHtml = creditHtmlStorageService.save(multipartFile);
|
CreditHtmlStorageService.StoredCreditHtml storedHtml = creditHtmlStorageService.save(multipartFile);
|
||||||
CreditParseInvokeResponse response = creditParseClient.parse(storedHtml.remotePath());
|
CreditParseInvokeResponse response = parseAndDeleteLocalFile(caller, storedHtml);
|
||||||
CreditParsePayload payload = requireResponse(response).getPayload();
|
CreditParsePayload payload = requireResponse(response).getPayload();
|
||||||
Map<String, Object> header = requireHeader(payload);
|
Map<String, Object> header = requireHeader(payload);
|
||||||
String personId = stringValue(header.get("query_cert_no"));
|
String personId = stringValue(header.get("query_cert_no"));
|
||||||
@@ -164,6 +166,25 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
replaceEmployeeCredit(personId, debts, negative, userName);
|
replaceEmployeeCredit(personId, debts, negative, userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CreditParseInvokeResponse parseAndDeleteLocalFile(
|
||||||
|
CallerContext caller, CreditHtmlStorageService.StoredCreditHtml storedHtml) throws Exception {
|
||||||
|
CreditParseInvokeResponse response;
|
||||||
|
try {
|
||||||
|
response = creditParseClient.parse(caller, storedHtml.remotePath());
|
||||||
|
} catch (Exception parseException) {
|
||||||
|
try {
|
||||||
|
creditHtmlStorageService.delete(storedHtml);
|
||||||
|
} catch (Exception deleteException) {
|
||||||
|
parseException.addSuppressed(deleteException);
|
||||||
|
log.error("征信解析失败后清理本地文件失败", deleteException);
|
||||||
|
}
|
||||||
|
throw parseException;
|
||||||
|
}
|
||||||
|
|
||||||
|
creditHtmlStorageService.delete(storedHtml);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private void validateHtmlFile(MultipartFile file) {
|
private void validateHtmlFile(MultipartFile file) {
|
||||||
String originalFilename = file == null ? null : file.getOriginalFilename();
|
String originalFilename = file == null ? null : file.getOriginalFilename();
|
||||||
if (originalFilename == null) {
|
if (originalFilename == null) {
|
||||||
@@ -286,11 +307,4 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
return negativeVO;
|
return negativeVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String currentUserName() {
|
|
||||||
try {
|
|
||||||
return SecurityUtils.getUsername();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return "system";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import com.ruoyi.info.collection.enums.EnterpriseRiskLevel;
|
|||||||
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
|
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseEntitySyncService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
@@ -47,9 +49,12 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnterpriseEntitySyncService enterpriseEntitySyncService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
public void importEnterpriseBaseInfoAsync(List<CcdiEnterpriseBaseInfoExcel> excelList, String taskId, String userName) {
|
public void importEnterpriseBaseInfoAsync(List<CcdiEnterpriseBaseInfoExcel> excelList, String taskId, CallerContext caller) {
|
||||||
List<CcdiEnterpriseBaseInfo> successRecords = new ArrayList<>();
|
List<CcdiEnterpriseBaseInfo> successRecords = new ArrayList<>();
|
||||||
List<EnterpriseBaseInfoImportFailureVO> failures = new ArrayList<>();
|
List<EnterpriseBaseInfoImportFailureVO> failures = new ArrayList<>();
|
||||||
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
|
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
|
||||||
@@ -57,7 +62,7 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
|||||||
|
|
||||||
for (CcdiEnterpriseBaseInfoExcel excel : excelList) {
|
for (CcdiEnterpriseBaseInfoExcel excel : excelList) {
|
||||||
try {
|
try {
|
||||||
CcdiEnterpriseBaseInfo entity = validateAndBuildEntity(excel, existingCreditCodes, processedCreditCodes, userName);
|
CcdiEnterpriseBaseInfo entity = validateAndBuildEntity(excel, existingCreditCodes, processedCreditCodes, caller.username());
|
||||||
successRecords.add(entity);
|
successRecords.add(entity);
|
||||||
processedCreditCodes.add(entity.getSocialCreditCode());
|
processedCreditCodes.add(entity.getSocialCreditCode());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -70,6 +75,9 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
|||||||
|
|
||||||
if (!successRecords.isEmpty()) {
|
if (!successRecords.isEmpty()) {
|
||||||
saveBatch(successRecords, 500);
|
saveBatch(successRecords, 500);
|
||||||
|
enterpriseEntitySyncService.submitSyncBatch(caller, successRecords.stream()
|
||||||
|
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
|
||||||
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package com.ruoyi.info.collection.service.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.info.collection.domain.CcdiCustEnterpriseRelation;
|
import com.ruoyi.info.collection.domain.CcdiCustEnterpriseRelation;
|
||||||
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache;
|
||||||
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
|
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
|
||||||
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
|
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
|
||||||
@@ -13,20 +13,27 @@ import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.EnterpriseShareholderVO;
|
||||||
import com.ruoyi.info.collection.enums.DataSource;
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
import com.ruoyi.info.collection.enums.EnterpriseRiskLevel;
|
import com.ruoyi.info.collection.enums.EnterpriseRiskLevel;
|
||||||
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseShareholderMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
|
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
|
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseEntitySyncService;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseProfileQueryService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -62,6 +69,15 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiEnterpriseShareholderMapper enterpriseShareholderMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnterpriseProfileQueryService enterpriseProfileQueryService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnterpriseEntitySyncService enterpriseEntitySyncService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<CcdiEnterpriseBaseInfoVO> selectEnterpriseBaseInfoPage(Page<CcdiEnterpriseBaseInfoVO> page,
|
public Page<CcdiEnterpriseBaseInfoVO> selectEnterpriseBaseInfoPage(Page<CcdiEnterpriseBaseInfoVO> page,
|
||||||
CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
|
CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
|
||||||
@@ -76,12 +92,21 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
}
|
}
|
||||||
CcdiEnterpriseBaseInfoVO vo = new CcdiEnterpriseBaseInfoVO();
|
CcdiEnterpriseBaseInfoVO vo = new CcdiEnterpriseBaseInfoVO();
|
||||||
BeanUtils.copyProperties(entity, vo);
|
BeanUtils.copyProperties(entity, vo);
|
||||||
|
vo.setShareholders(enterpriseShareholderMapper.selectByCreditCode(socialCreditCode).stream().map(item -> {
|
||||||
|
EnterpriseShareholderVO shareholder = new EnterpriseShareholderVO();
|
||||||
|
BeanUtils.copyProperties(item, shareholder);
|
||||||
|
return shareholder;
|
||||||
|
}).toList());
|
||||||
|
CcdiEnterpriseInfoQueryCache cache = enterpriseProfileQueryService.findCache(entity.getEnterpriseName());
|
||||||
|
vo.setEnterpriseCacheValidDate(cache == null ? null : cache.getValidDate());
|
||||||
|
vo.setCanRefreshEnterpriseProfile(entity.getEnterpriseSyncTime() == null || cache == null
|
||||||
|
|| cache.getValidDate() == null || !cache.getValidDate().after(new java.util.Date()));
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int insertEnterpriseBaseInfo(CcdiEnterpriseBaseInfoAddDTO addDTO) {
|
public int insertEnterpriseBaseInfo(CallerContext caller, CcdiEnterpriseBaseInfoAddDTO addDTO) {
|
||||||
if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) {
|
if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) {
|
||||||
throw new RuntimeException("该统一社会信用代码已存在");
|
throw new RuntimeException("该统一社会信用代码已存在");
|
||||||
}
|
}
|
||||||
@@ -91,12 +116,16 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
BeanUtils.copyProperties(addDTO, entity);
|
BeanUtils.copyProperties(addDTO, entity);
|
||||||
entity.setStatus(trimToNull(addDTO.getStatus()));
|
entity.setStatus(trimToNull(addDTO.getStatus()));
|
||||||
entity.setDataSource(DataSource.MANUAL.getCode());
|
entity.setDataSource(DataSource.MANUAL.getCode());
|
||||||
return enterpriseBaseInfoMapper.insert(entity);
|
int rows = enterpriseBaseInfoMapper.insert(entity);
|
||||||
|
if (rows > 0) {
|
||||||
|
submitSyncAfterCommit(caller, entity.getSocialCreditCode());
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int updateEnterpriseBaseInfo(CcdiEnterpriseBaseInfoEditDTO editDTO) {
|
public int updateEnterpriseBaseInfo(CallerContext caller, CcdiEnterpriseBaseInfoEditDTO editDTO) {
|
||||||
CcdiEnterpriseBaseInfo existing = enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode());
|
CcdiEnterpriseBaseInfo existing = enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode());
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
throw new RuntimeException("实体库记录不存在");
|
throw new RuntimeException("实体库记录不存在");
|
||||||
@@ -107,7 +136,11 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
BeanUtils.copyProperties(editDTO, entity);
|
BeanUtils.copyProperties(editDTO, entity);
|
||||||
entity.setStatus(trimToNull(editDTO.getStatus()));
|
entity.setStatus(trimToNull(editDTO.getStatus()));
|
||||||
entity.setDataSource(existing.getDataSource());
|
entity.setDataSource(existing.getDataSource());
|
||||||
return enterpriseBaseInfoMapper.updateById(entity);
|
int rows = enterpriseBaseInfoMapper.updateById(entity);
|
||||||
|
if (rows > 0 && !java.util.Objects.equals(trimToNull(existing.getEnterpriseName()), trimToNull(entity.getEnterpriseName()))) {
|
||||||
|
submitSyncAfterCommit(caller, entity.getSocialCreditCode());
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -118,6 +151,7 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
}
|
}
|
||||||
for (String socialCreditCode : socialCreditCodes) {
|
for (String socialCreditCode : socialCreditCodes) {
|
||||||
validateDeleteRelations(socialCreditCode);
|
validateDeleteRelations(socialCreditCode);
|
||||||
|
enterpriseShareholderMapper.deleteByCreditCode(socialCreditCode);
|
||||||
}
|
}
|
||||||
return enterpriseBaseInfoMapper.deleteBatchIds(List.of(socialCreditCodes));
|
return enterpriseBaseInfoMapper.deleteBatchIds(List.of(socialCreditCodes));
|
||||||
}
|
}
|
||||||
@@ -134,7 +168,7 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public String importEnterpriseBaseInfo(List<CcdiEnterpriseBaseInfoExcel> excelList) {
|
public String importEnterpriseBaseInfo(CallerContext caller, List<CcdiEnterpriseBaseInfoExcel> excelList) {
|
||||||
String taskId = UUID.randomUUID().toString();
|
String taskId = UUID.randomUUID().toString();
|
||||||
String statusKey = "import:enterpriseBaseInfo:" + taskId;
|
String statusKey = "import:enterpriseBaseInfo:" + taskId;
|
||||||
|
|
||||||
@@ -151,10 +185,24 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
|||||||
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||||
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
||||||
|
|
||||||
enterpriseBaseInfoImportService.importEnterpriseBaseInfoAsync(excelList, taskId, SecurityUtils.getUsername());
|
enterpriseBaseInfoImportService.importEnterpriseBaseInfoAsync(
|
||||||
|
excelList, taskId, caller);
|
||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void submitSyncAfterCommit(CallerContext caller, String socialCreditCode) {
|
||||||
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
enterpriseEntitySyncService.submitSync(caller, socialCreditCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
enterpriseEntitySyncService.submitSync(caller, socialCreditCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LambdaQueryWrapper<CcdiEnterpriseBaseInfo> buildQueryWrapper(CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
|
private LambdaQueryWrapper<CcdiEnterpriseBaseInfo> buildQueryWrapper(CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
|
||||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
||||||
if (queryDTO == null) {
|
if (queryDTO == null) {
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
package com.ruoyi.info.collection.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseShareholder;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseProfile;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseProfileQueryResult;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseShareholderProfile;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.EnterpriseProfileSyncTaskVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseShareholderMapper;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseEntitySyncService;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseProfileQueryService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/** 实体库工商信息同步服务实现。 */
|
||||||
|
@Service
|
||||||
|
public class EnterpriseEntitySyncServiceImpl implements IEnterpriseEntitySyncService {
|
||||||
|
|
||||||
|
private static final String TASK_PREFIX = "enterprise:profile:backfill:";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiEnterpriseBaseInfoMapper enterpriseMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiEnterpriseShareholderMapper shareholderMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnterpriseProfileQueryService queryService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TransactionTemplate transactionTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Qualifier("enterpriseProfileExecutor")
|
||||||
|
private Executor enterpriseProfileExecutor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void submitSync(CallerContext caller, String socialCreditCode) {
|
||||||
|
submitSyncBatch(caller, List.of(socialCreditCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void submitSyncBatch(CallerContext caller, List<String> socialCreditCodes) {
|
||||||
|
if (socialCreditCodes == null || socialCreditCodes.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> normalizedCodes = socialCreditCodes.stream().filter(StringUtils::hasText).distinct().toList();
|
||||||
|
if (normalizedCodes.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<CcdiEnterpriseBaseInfo> entities = enterpriseMapper.selectBatchIds(normalizedCodes);
|
||||||
|
Map<String, List<CcdiEnterpriseBaseInfo>> groups = entities.stream()
|
||||||
|
.filter(item -> StringUtils.hasText(item.getEnterpriseName()))
|
||||||
|
.collect(Collectors.groupingBy(item -> item.getEnterpriseName().trim(), LinkedHashMap::new, Collectors.toList()));
|
||||||
|
groups.forEach((name, sameNameEntities) -> CompletableFuture.runAsync(() -> {
|
||||||
|
EnterpriseProfileQueryResult result = queryService.query(caller, name, false);
|
||||||
|
sameNameEntities.stream()
|
||||||
|
.filter(item -> result.profile().getCreditCode().equals(item.getSocialCreditCode()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(item -> applyProfile(item, result, caller.username()));
|
||||||
|
}, enterpriseProfileExecutor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh(CallerContext caller, String socialCreditCode) {
|
||||||
|
CcdiEnterpriseBaseInfo entity = requireEntity(socialCreditCode);
|
||||||
|
CcdiEnterpriseInfoQueryCache cache = queryService.findCache(entity.getEnterpriseName());
|
||||||
|
boolean cacheValid = cache != null && cache.getValidDate() != null && cache.getValidDate().after(new Date());
|
||||||
|
if (entity.getEnterpriseSyncTime() != null && cacheValid) {
|
||||||
|
throw new IllegalStateException("工商缓存仍在有效期内,无需重新查询");
|
||||||
|
}
|
||||||
|
SyncOutcome outcome = syncByCreditCode(caller, socialCreditCode, !cacheValid);
|
||||||
|
if (outcome != SyncOutcome.SUCCESS) {
|
||||||
|
throw new IllegalStateException("工商信用代码与实体主键不一致");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String startBackfill(CallerContext caller) {
|
||||||
|
String taskId = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
List<CcdiEnterpriseBaseInfo> candidates = enterpriseMapper.selectEnterpriseProfileCandidates();
|
||||||
|
Map<String, List<CcdiEnterpriseBaseInfo>> groups = candidates.stream()
|
||||||
|
.filter(item -> StringUtils.hasText(item.getEnterpriseName()))
|
||||||
|
.collect(Collectors.groupingBy(item -> item.getEnterpriseName().trim(), LinkedHashMap::new, Collectors.toList()));
|
||||||
|
initializeTask(taskId, groups.size());
|
||||||
|
CompletableFuture.runAsync(() -> runBackfill(taskId, caller, groups));
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EnterpriseProfileSyncTaskVO getBackfillStatus(String taskId) {
|
||||||
|
Map<Object, Object> status = redisTemplate.opsForHash().entries(taskKey(taskId));
|
||||||
|
if (status.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("任务不存在或已过期");
|
||||||
|
}
|
||||||
|
EnterpriseProfileSyncTaskVO vo = new EnterpriseProfileSyncTaskVO();
|
||||||
|
vo.setTaskId(string(status.get("taskId")));
|
||||||
|
vo.setStatus(string(status.get("status")));
|
||||||
|
vo.setTotalCount(number(status.get("totalCount")));
|
||||||
|
vo.setCompletedCount(number(status.get("completedCount")));
|
||||||
|
vo.setSuccessCount(number(status.get("successCount")));
|
||||||
|
vo.setSkippedCount(number(status.get("skippedCount")));
|
||||||
|
vo.setFailureCount(number(status.get("failureCount")));
|
||||||
|
vo.setMessage(string(status.get("message")));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runBackfill(String taskId, CallerContext caller,
|
||||||
|
Map<String, List<CcdiEnterpriseBaseInfo>> groups) {
|
||||||
|
AtomicInteger completed = new AtomicInteger();
|
||||||
|
AtomicInteger success = new AtomicInteger();
|
||||||
|
AtomicInteger skipped = new AtomicInteger();
|
||||||
|
AtomicInteger failure = new AtomicInteger();
|
||||||
|
|
||||||
|
List<Map.Entry<String, List<CcdiEnterpriseBaseInfo>>> entries = new ArrayList<>(groups.entrySet());
|
||||||
|
for (int start = 0; start < entries.size(); start += 100) {
|
||||||
|
int end = Math.min(start + 100, entries.size());
|
||||||
|
List<CompletableFuture<Void>> futures = entries.subList(start, end).stream()
|
||||||
|
.map(entry -> CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
EnterpriseProfileQueryResult result = queryService.query(caller, entry.getKey(), false);
|
||||||
|
CcdiEnterpriseBaseInfo match = entry.getValue().stream()
|
||||||
|
.filter(item -> result.profile().getCreditCode().equals(item.getSocialCreditCode()))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
if (match == null) {
|
||||||
|
skipped.incrementAndGet();
|
||||||
|
} else {
|
||||||
|
applyProfile(match, result, caller.username());
|
||||||
|
success.incrementAndGet();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
failure.incrementAndGet();
|
||||||
|
} finally {
|
||||||
|
completed.incrementAndGet();
|
||||||
|
updateTask(taskId, "PROCESSING", completed.get(), success.get(), skipped.get(), failure.get());
|
||||||
|
}
|
||||||
|
}, enterpriseProfileExecutor)).toList();
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||||
|
}
|
||||||
|
updateTask(taskId, "COMPLETED", completed.get(), success.get(), skipped.get(), failure.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyncOutcome syncByCreditCode(CallerContext caller, String socialCreditCode,
|
||||||
|
boolean forceRefresh) {
|
||||||
|
CcdiEnterpriseBaseInfo entity = requireEntity(socialCreditCode);
|
||||||
|
EnterpriseProfileQueryResult result = queryService.query(caller, entity.getEnterpriseName(), forceRefresh);
|
||||||
|
if (!result.profile().getCreditCode().equals(entity.getSocialCreditCode())) {
|
||||||
|
return SyncOutcome.SKIPPED;
|
||||||
|
}
|
||||||
|
applyProfile(entity, result, caller.username());
|
||||||
|
return SyncOutcome.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyProfile(CcdiEnterpriseBaseInfo existing, EnterpriseProfileQueryResult result, String username) {
|
||||||
|
transactionTemplate.executeWithoutResult(status -> {
|
||||||
|
EnterpriseProfile profile = result.profile();
|
||||||
|
Date now = new Date();
|
||||||
|
CcdiEnterpriseBaseInfo update = new CcdiEnterpriseBaseInfo();
|
||||||
|
update.setSocialCreditCode(existing.getSocialCreditCode());
|
||||||
|
update.setEnterpriseName(existing.getEnterpriseName().trim());
|
||||||
|
update.setEnterpriseType(profile.getOrganizationTypeName());
|
||||||
|
update.setIndustryClass(profile.getIndustryCode());
|
||||||
|
update.setIndustryName(profile.getIndustryName());
|
||||||
|
update.setEstablishDate(toDate(profile.getEstablishDate()));
|
||||||
|
update.setRegisterAddress(profile.getRegisterAddress());
|
||||||
|
update.setLegalRepresentative(profile.getLegalRepresentative());
|
||||||
|
update.setRegisteredCapital(profile.getRegisteredCapital());
|
||||||
|
update.setRegisteredCapitalUnit(profile.getRegisteredCapitalUnit());
|
||||||
|
update.setRegisterDate(toDate(profile.getRegisterDate()));
|
||||||
|
update.setRegionCode(profile.getRegionCode());
|
||||||
|
update.setRegionName(profile.getRegionName());
|
||||||
|
update.setEmployeeCount(profile.getEmployeeCount());
|
||||||
|
update.setCacheInfoId(result.cacheInfoId());
|
||||||
|
update.setEnterpriseSyncTime(now);
|
||||||
|
update.setDataSource(DataSource.API.getCode());
|
||||||
|
update.setUpdatedBy(username);
|
||||||
|
setTopFive(update, profile.getShareholders());
|
||||||
|
if (enterpriseMapper.updateEnterpriseProfile(update) != 1) {
|
||||||
|
throw new IllegalStateException("实体工商信息更新失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
shareholderMapper.deleteByCreditCode(existing.getSocialCreditCode());
|
||||||
|
List<CcdiEnterpriseShareholder> shareholders = profile.getShareholders().stream()
|
||||||
|
.map(item -> toEntityShareholder(existing.getSocialCreditCode(), item, result.cacheInfoId(), now, username))
|
||||||
|
.toList();
|
||||||
|
if (!shareholders.isEmpty()) {
|
||||||
|
shareholderMapper.insertBatch(shareholders);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiEnterpriseShareholder toEntityShareholder(String creditCode,
|
||||||
|
EnterpriseShareholderProfile item,
|
||||||
|
Long cacheInfoId,
|
||||||
|
Date now,
|
||||||
|
String username) {
|
||||||
|
CcdiEnterpriseShareholder shareholder = new CcdiEnterpriseShareholder();
|
||||||
|
shareholder.setSocialCreditCode(creditCode);
|
||||||
|
shareholder.setShareholderSeq(item.getSequence());
|
||||||
|
shareholder.setShareholderName(item.getShareholderName());
|
||||||
|
shareholder.setStockPercent(item.getStockPercent());
|
||||||
|
shareholder.setSubscribedCapital(item.getSubscribedCapital());
|
||||||
|
shareholder.setCapitalUnit(item.getCapitalUnit());
|
||||||
|
shareholder.setCacheInfoId(cacheInfoId);
|
||||||
|
shareholder.setSyncTime(now);
|
||||||
|
shareholder.setCreateBy(username);
|
||||||
|
shareholder.setCreateTime(now);
|
||||||
|
shareholder.setUpdateBy(username);
|
||||||
|
shareholder.setUpdateTime(now);
|
||||||
|
return shareholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTopFive(CcdiEnterpriseBaseInfo entity, List<EnterpriseShareholderProfile> shareholders) {
|
||||||
|
String[] names = new String[5];
|
||||||
|
for (int i = 0; i < Math.min(names.length, shareholders.size()); i++) {
|
||||||
|
names[i] = shareholders.get(i).getShareholderName();
|
||||||
|
}
|
||||||
|
entity.setShareholder1(names[0]);
|
||||||
|
entity.setShareholder2(names[1]);
|
||||||
|
entity.setShareholder3(names[2]);
|
||||||
|
entity.setShareholder4(names[3]);
|
||||||
|
entity.setShareholder5(names[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiEnterpriseBaseInfo requireEntity(String socialCreditCode) {
|
||||||
|
CcdiEnterpriseBaseInfo entity = enterpriseMapper.selectById(socialCreditCode);
|
||||||
|
if (entity == null) {
|
||||||
|
throw new IllegalArgumentException("实体库记录不存在");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(entity.getEnterpriseName())) {
|
||||||
|
throw new IllegalArgumentException("企业名称不能为空");
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date toDate(java.time.LocalDate value) {
|
||||||
|
return value == null ? null : Date.from(value.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeTask(String taskId, int totalCount) {
|
||||||
|
Map<String, Object> values = new LinkedHashMap<>();
|
||||||
|
values.put("taskId", taskId);
|
||||||
|
values.put("status", "PROCESSING");
|
||||||
|
values.put("totalCount", totalCount);
|
||||||
|
values.put("completedCount", 0);
|
||||||
|
values.put("successCount", 0);
|
||||||
|
values.put("skippedCount", 0);
|
||||||
|
values.put("failureCount", 0);
|
||||||
|
values.put("message", "正在补全工商信息");
|
||||||
|
redisTemplate.opsForHash().putAll(taskKey(taskId), values);
|
||||||
|
redisTemplate.expire(taskKey(taskId), 7, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTask(String taskId, String status, int completed, int success, int skipped, int failure) {
|
||||||
|
Map<String, Object> values = new LinkedHashMap<>();
|
||||||
|
values.put("status", status);
|
||||||
|
values.put("completedCount", completed);
|
||||||
|
values.put("successCount", success);
|
||||||
|
values.put("skippedCount", skipped);
|
||||||
|
values.put("failureCount", failure);
|
||||||
|
values.put("message", "COMPLETED".equals(status) ? "工商信息补全完成" : "正在补全工商信息");
|
||||||
|
redisTemplate.opsForHash().putAll(taskKey(taskId), values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String taskKey(String taskId) {
|
||||||
|
return TASK_PREFIX + taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String string(Object value) {
|
||||||
|
return value == null ? null : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer number(Object value) {
|
||||||
|
return value == null ? 0 : Integer.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SyncOutcome {
|
||||||
|
SUCCESS, SKIPPED
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.ruoyi.info.collection.service.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseProfile;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseProfileQueryResult;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseInfoQueryCacheMapper;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseProfileQueryService;
|
||||||
|
import com.ruoyi.info.collection.service.support.XinhuaEnterpriseProfileParser;
|
||||||
|
import com.ruoyi.lsfx.client.XinhuaEnterpriseClient;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import com.ruoyi.lsfx.domain.response.XinhuaEnterpriseQueryResult;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 统一工商信息查询服务实现。 */
|
||||||
|
@Service
|
||||||
|
public class EnterpriseProfileQueryServiceImpl implements IEnterpriseProfileQueryService {
|
||||||
|
|
||||||
|
public static final String QUERY_TYPE = "EnterpriseProfile";
|
||||||
|
private static final int CACHE_VALID_DAYS = 180;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiEnterpriseInfoQueryCacheMapper cacheMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private XinhuaEnterpriseClient xinhuaEnterpriseClient;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private XinhuaEnterpriseProfileParser profileParser;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EnterpriseProfileQueryResult query(CallerContext caller, String enterpriseName, boolean forceRefresh) {
|
||||||
|
String normalizedName = normalize(enterpriseName);
|
||||||
|
if (!StringUtils.hasText(normalizedName)) {
|
||||||
|
throw new IllegalArgumentException("企业名称不能为空");
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
if (!forceRefresh) {
|
||||||
|
CcdiEnterpriseInfoQueryCache cache = cacheMapper.selectValidCache(normalizedName, QUERY_TYPE, now);
|
||||||
|
if (cache != null) {
|
||||||
|
return fromCache(cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XinhuaEnterpriseQueryResult external = xinhuaEnterpriseClient.query(caller, normalizedName);
|
||||||
|
EnterpriseProfile profile = profileParser.parse(external.profile());
|
||||||
|
Date successTime = new Date();
|
||||||
|
Date validDate = Date.from(LocalDateTime.ofInstant(successTime.toInstant(), ZoneId.systemDefault())
|
||||||
|
.plusDays(CACHE_VALID_DAYS)
|
||||||
|
.atZone(ZoneId.systemDefault()).toInstant());
|
||||||
|
|
||||||
|
CcdiEnterpriseInfoQueryCache cache = new CcdiEnterpriseInfoQueryCache();
|
||||||
|
cache.setQueryParam(normalizedName);
|
||||||
|
cache.setQueryType(QUERY_TYPE);
|
||||||
|
cache.setQueryResult(external.rawJson());
|
||||||
|
cache.setCreatedDate(successTime);
|
||||||
|
cache.setValidDate(validDate);
|
||||||
|
cacheMapper.upsertCache(cache);
|
||||||
|
CcdiEnterpriseInfoQueryCache saved = cacheMapper.selectCache(normalizedName, QUERY_TYPE);
|
||||||
|
return new EnterpriseProfileQueryResult(profile, saved.getInfoId(), saved.getValidDate(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CcdiEnterpriseInfoQueryCache findCache(String enterpriseName) {
|
||||||
|
String normalizedName = normalize(enterpriseName);
|
||||||
|
return StringUtils.hasText(normalizedName) ? cacheMapper.selectCache(normalizedName, QUERY_TYPE) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EnterpriseProfileQueryResult fromCache(CcdiEnterpriseInfoQueryCache cache) {
|
||||||
|
try {
|
||||||
|
JsonNode root = objectMapper.readTree(cache.getQueryResult());
|
||||||
|
EnterpriseProfile profile = profileParser.parse(root.path("data").path("mappingOutputFields"));
|
||||||
|
return new EnterpriseProfileQueryResult(profile, cache.getInfoId(), cache.getValidDate(), true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("工商缓存内容无法解析: infoId=" + cache.getInfoId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalize(String value) {
|
||||||
|
return value == null ? null : value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,10 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 征信 HTML 服务器落盘与远程访问地址生成。
|
* 征信 HTML 服务器落盘与远程访问地址生成。
|
||||||
@@ -16,6 +20,7 @@ import java.io.File;
|
|||||||
public class CreditHtmlStorageService {
|
public class CreditHtmlStorageService {
|
||||||
|
|
||||||
private static final String CREDIT_HTML_DIR = "credit-html";
|
private static final String CREDIT_HTML_DIR = "credit-html";
|
||||||
|
private static final String CREDIT_HTML_PROFILE_PREFIX = "/profile/" + CREDIT_HTML_DIR + "/";
|
||||||
private static final String[] HTML_EXTENSIONS = {"html", "htm"};
|
private static final String[] HTML_EXTENSIONS = {"html", "htm"};
|
||||||
|
|
||||||
@Value("${credit-parse.api.file-public-base-url}")
|
@Value("${credit-parse.api.file-public-base-url}")
|
||||||
@@ -26,6 +31,25 @@ public class CreditHtmlStorageService {
|
|||||||
return new StoredCreditHtml(profilePath, buildRemotePath(profilePath));
|
return new StoredCreditHtml(profilePath, buildRemotePath(profilePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete(StoredCreditHtml storedHtml) throws IOException {
|
||||||
|
String profilePath = storedHtml == null ? null : storedHtml.profilePath();
|
||||||
|
if (StringUtils.isBlank(profilePath) || !profilePath.startsWith(CREDIT_HTML_PROFILE_PREFIX)) {
|
||||||
|
throw new IllegalArgumentException("征信HTML本地文件路径非法");
|
||||||
|
}
|
||||||
|
|
||||||
|
String relativePath = profilePath.substring(CREDIT_HTML_PROFILE_PREFIX.length());
|
||||||
|
if (StringUtils.isBlank(relativePath)) {
|
||||||
|
throw new IllegalArgumentException("征信HTML本地文件路径非法");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path baseDir = Paths.get(getCreditHtmlBaseDir()).toAbsolutePath().normalize();
|
||||||
|
Path targetFile = baseDir.resolve(relativePath).normalize();
|
||||||
|
if (!targetFile.startsWith(baseDir)) {
|
||||||
|
throw new IllegalArgumentException("征信HTML本地文件路径越界");
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(targetFile);
|
||||||
|
}
|
||||||
|
|
||||||
private String getCreditHtmlBaseDir() {
|
private String getCreditHtmlBaseDir() {
|
||||||
return RuoYiConfig.getProfile() + File.separator + CREDIT_HTML_DIR;
|
return RuoYiConfig.getProfile() + File.separator + CREDIT_HTML_DIR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
||||||
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
|
import com.ruoyi.info.collection.service.IEnterpriseEntitySyncService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -27,6 +31,9 @@ public class EnterpriseAutoFillService {
|
|||||||
@Resource
|
@Resource
|
||||||
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IEnterpriseEntitySyncService enterpriseEntitySyncService;
|
||||||
|
|
||||||
public record EnterpriseFillItem(
|
public record EnterpriseFillItem(
|
||||||
String socialCreditCode,
|
String socialCreditCode,
|
||||||
String enterpriseName,
|
String enterpriseName,
|
||||||
@@ -63,6 +70,7 @@ public class EnterpriseAutoFillService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
insertBatchIgnoreDuplicate(missingEntities);
|
insertBatchIgnoreDuplicate(missingEntities);
|
||||||
|
submitSyncAfterCommit(missingEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, EnterpriseFillItem> normalizeItems(List<EnterpriseFillItem> items) {
|
private Map<String, EnterpriseFillItem> normalizeItems(List<EnterpriseFillItem> items) {
|
||||||
@@ -121,6 +129,23 @@ public class EnterpriseAutoFillService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void submitSyncAfterCommit(List<CcdiEnterpriseBaseInfo> entities) {
|
||||||
|
String username = entities.stream().map(CcdiEnterpriseBaseInfo::getCreatedBy)
|
||||||
|
.filter(StringUtils::isNotEmpty).findFirst().orElse("system");
|
||||||
|
CallerContext caller = CallerContext.of(null, username);
|
||||||
|
List<String> creditCodes = entities.stream().map(CcdiEnterpriseBaseInfo::getSocialCreditCode).toList();
|
||||||
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
enterpriseEntitySyncService.submitSyncBatch(caller, creditCodes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
enterpriseEntitySyncService.submitSyncBatch(caller, creditCodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private String trimToNull(String value) {
|
private String trimToNull(String value) {
|
||||||
if (StringUtils.isEmpty(value)) {
|
if (StringUtils.isEmpty(value)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.ruoyi.info.collection.service.support;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseProfile;
|
||||||
|
import com.ruoyi.info.collection.domain.model.EnterpriseShareholderProfile;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/** 将新华社 mappingOutputFields 解析为统一工商结构。 */
|
||||||
|
@Component
|
||||||
|
public class XinhuaEnterpriseProfileParser {
|
||||||
|
|
||||||
|
public EnterpriseProfile parse(JsonNode node) {
|
||||||
|
if (node == null || !node.isObject() || node.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("工商信息对象不能为空");
|
||||||
|
}
|
||||||
|
EnterpriseProfile profile = new EnterpriseProfile();
|
||||||
|
profile.setCreditCode(text(node, "creditCode"));
|
||||||
|
profile.setEnterpriseName(text(node, "enterpriseName"));
|
||||||
|
|
||||||
|
JsonNode capital = node.path("regCapitalAmount");
|
||||||
|
profile.setRegisteredCapital(decimal(text(capital, "regist_capi_value"), false));
|
||||||
|
profile.setRegisteredCapitalUnit(text(capital, "regist_capi_unit"));
|
||||||
|
profile.setRegisterDate(date(text(node, "estiblishTime")));
|
||||||
|
profile.setEstablishDate(date(text(node, "fromTime")));
|
||||||
|
|
||||||
|
JsonNode industry = node.path("industry");
|
||||||
|
profile.setIndustryCode(text(industry, "industry_code"));
|
||||||
|
profile.setIndustryName(text(industry, "industry"));
|
||||||
|
|
||||||
|
JsonNode orgType = node.path("companyOrgType");
|
||||||
|
if (orgType.isObject()) {
|
||||||
|
profile.setOrganizationTypeCode(text(orgType, "econ_kind_code"));
|
||||||
|
profile.setOrganizationTypeName(text(orgType, "econ_kind"));
|
||||||
|
} else if (orgType.isTextual()) {
|
||||||
|
profile.setOrganizationTypeName(normalize(orgType.asText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode city = node.path("baseCity");
|
||||||
|
profile.setRegionCode(text(city, "province_code"));
|
||||||
|
profile.setRegionName(regionName(city));
|
||||||
|
profile.setRegisterAddress(text(node, "regLocation"));
|
||||||
|
profile.setEmployeeCount(integer(node.get("employeeCount")));
|
||||||
|
profile.setLegalRepresentative(text(node, "legalPersonName"));
|
||||||
|
profile.setShareholders(shareholders(node.path("holders")));
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EnterpriseShareholderProfile> shareholders(JsonNode holders) {
|
||||||
|
List<EnterpriseShareholderProfile> result = new ArrayList<>();
|
||||||
|
if (!holders.isArray()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int sequence = 1;
|
||||||
|
for (JsonNode holder : holders) {
|
||||||
|
String name = text(holder, "stock_name");
|
||||||
|
if (!StringUtils.hasText(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EnterpriseShareholderProfile item = new EnterpriseShareholderProfile();
|
||||||
|
item.setSequence(sequence++);
|
||||||
|
item.setShareholderName(name);
|
||||||
|
item.setStockPercent(decimal(text(holder, "stock_percent"), true));
|
||||||
|
item.setSubscribedCapital(decimal(text(holder, "should_capi"), false));
|
||||||
|
item.setCapitalUnit("万元");
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String regionName(JsonNode city) {
|
||||||
|
Set<String> values = new LinkedHashSet<>();
|
||||||
|
add(values, text(city, "province"));
|
||||||
|
add(values, text(city, "city"));
|
||||||
|
add(values, text(city, "county"));
|
||||||
|
return values.isEmpty() ? null : String.join("", values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(Set<String> values, String value) {
|
||||||
|
if (StringUtils.hasText(value)) {
|
||||||
|
values.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String text(JsonNode node, String field) {
|
||||||
|
if (node == null || !node.isObject()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonNode value = node.get(field);
|
||||||
|
return value == null || value.isNull() ? null : normalize(value.asText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalize(String value) {
|
||||||
|
return value == null ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal decimal(String value, boolean percentage) {
|
||||||
|
if (!StringUtils.hasText(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = value.replace(",", "").trim();
|
||||||
|
if (percentage && normalized.endsWith("%")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1).trim();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new BigDecimal(normalized);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer integer(JsonNode value) {
|
||||||
|
if (value == null || value.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.valueOf(value.asText().trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate date(String value) {
|
||||||
|
if (!StringUtils.hasText(value) || value.length() < 10) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return LocalDate.parse(value.substring(0, 10));
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,6 +104,9 @@
|
|||||||
<if test="query.accountName != null and query.accountName != ''">
|
<if test="query.accountName != null and query.accountName != ''">
|
||||||
AND ai.account_name LIKE CONCAT('%', #{query.accountName}, '%')
|
AND ai.account_name LIKE CONCAT('%', #{query.accountName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="query.accountNo != null and query.accountNo != ''">
|
||||||
|
AND ai.account_no LIKE CONCAT('%', #{query.accountNo}, '%')
|
||||||
|
</if>
|
||||||
<if test="query.accountType != null and query.accountType != ''">
|
<if test="query.accountType != null and query.accountType != ''">
|
||||||
AND ai.account_type = #{query.accountType}
|
AND ai.account_type = #{query.accountType}
|
||||||
</if>
|
</if>
|
||||||
|
|||||||
@@ -42,7 +42,16 @@
|
|||||||
AND e.status = #{query.status}
|
AND e.status = #{query.status}
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
ORDER BY e.create_time DESC, e.staff_id DESC
|
ORDER BY e.hire_date DESC, e.staff_id DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectBaseStaffByIdWithDept" resultMap="CcdiBaseStaffVOResult">
|
||||||
|
SELECT
|
||||||
|
e.staff_id, e.name, e.dept_id, e.id_card, e.phone, e.annual_income, e.hire_date, e.is_party_member, e.status, e.create_time,
|
||||||
|
d.dept_name
|
||||||
|
FROM ccdi_base_staff e
|
||||||
|
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id
|
||||||
|
WHERE e.staff_id = #{staffId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 批量插入或更新员工信息(只更新非null字段) -->
|
<!-- 批量插入或更新员工信息(只更新非null字段) -->
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="establishDate" column="establish_date"/>
|
<result property="establishDate" column="establish_date"/>
|
||||||
<result property="registerAddress" column="register_address"/>
|
<result property="registerAddress" column="register_address"/>
|
||||||
<result property="legalRepresentative" column="legal_representative"/>
|
<result property="legalRepresentative" column="legal_representative"/>
|
||||||
|
<result property="registeredCapital" column="registered_capital"/>
|
||||||
|
<result property="registeredCapitalUnit" column="registered_capital_unit"/>
|
||||||
|
<result property="registerDate" column="register_date"/>
|
||||||
|
<result property="regionCode" column="region_code"/>
|
||||||
|
<result property="regionName" column="region_name"/>
|
||||||
|
<result property="employeeCount" column="employee_count"/>
|
||||||
|
<result property="cacheInfoId" column="cache_info_id"/>
|
||||||
|
<result property="enterpriseSyncTime" column="enterprise_sync_time"/>
|
||||||
<result property="legalCertType" column="legal_cert_type"/>
|
<result property="legalCertType" column="legal_cert_type"/>
|
||||||
<result property="legalCertNo" column="legal_cert_no"/>
|
<result property="legalCertNo" column="legal_cert_no"/>
|
||||||
<result property="shareholder1" column="shareholder1"/>
|
<result property="shareholder1" column="shareholder1"/>
|
||||||
@@ -39,6 +47,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
establish_date,
|
establish_date,
|
||||||
register_address,
|
register_address,
|
||||||
legal_representative,
|
legal_representative,
|
||||||
|
registered_capital,
|
||||||
|
registered_capital_unit,
|
||||||
|
register_date,
|
||||||
|
region_code,
|
||||||
|
region_name,
|
||||||
|
employee_count,
|
||||||
|
cache_info_id,
|
||||||
|
enterprise_sync_time,
|
||||||
legal_cert_type,
|
legal_cert_type,
|
||||||
legal_cert_no,
|
legal_cert_no,
|
||||||
shareholder1,
|
shareholder1,
|
||||||
@@ -178,4 +194,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</foreach>
|
</foreach>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="updateEnterpriseProfile" parameterType="com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo">
|
||||||
|
update ccdi_enterprise_base_info
|
||||||
|
set enterprise_type = #{enterpriseType},
|
||||||
|
industry_class = #{industryClass},
|
||||||
|
industry_name = #{industryName},
|
||||||
|
establish_date = #{establishDate},
|
||||||
|
register_address = #{registerAddress},
|
||||||
|
legal_representative = #{legalRepresentative},
|
||||||
|
registered_capital = #{registeredCapital},
|
||||||
|
registered_capital_unit = #{registeredCapitalUnit},
|
||||||
|
register_date = #{registerDate},
|
||||||
|
region_code = #{regionCode},
|
||||||
|
region_name = #{regionName},
|
||||||
|
employee_count = #{employeeCount},
|
||||||
|
cache_info_id = #{cacheInfoId},
|
||||||
|
enterprise_sync_time = #{enterpriseSyncTime},
|
||||||
|
shareholder1 = #{shareholder1},
|
||||||
|
shareholder2 = #{shareholder2},
|
||||||
|
shareholder3 = #{shareholder3},
|
||||||
|
shareholder4 = #{shareholder4},
|
||||||
|
shareholder5 = #{shareholder5},
|
||||||
|
data_source = #{dataSource},
|
||||||
|
updated_by = #{updatedBy},
|
||||||
|
update_time = now()
|
||||||
|
where social_credit_code = #{socialCreditCode}
|
||||||
|
and trim(enterprise_name) = trim(#{enterpriseName})
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectEnterpriseProfileCandidates" resultType="com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo">
|
||||||
|
select social_credit_code, enterprise_name, cache_info_id, enterprise_sync_time
|
||||||
|
from ccdi_enterprise_base_info
|
||||||
|
where enterprise_name is not null and trim(enterprise_name) != ''
|
||||||
|
order by enterprise_name, social_credit_code
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiEnterpriseInfoQueryCacheMapper">
|
||||||
|
<select id="selectValidCache" resultType="com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache">
|
||||||
|
select info_id, query_param, query_result, query_type, created_date, valid_date
|
||||||
|
from ccdi_enterpriseinfo_query_cache
|
||||||
|
where query_param = #{queryParam}
|
||||||
|
and query_type = #{queryType}
|
||||||
|
and valid_date > #{now}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectCache" resultType="com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache">
|
||||||
|
select info_id, query_param, query_result, query_type, created_date, valid_date
|
||||||
|
from ccdi_enterpriseinfo_query_cache
|
||||||
|
where query_param = #{queryParam}
|
||||||
|
and query_type = #{queryType}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="upsertCache" parameterType="com.ruoyi.info.collection.domain.CcdiEnterpriseInfoQueryCache">
|
||||||
|
insert into ccdi_enterpriseinfo_query_cache
|
||||||
|
(query_param, query_result, query_type, created_date, valid_date)
|
||||||
|
values
|
||||||
|
(#{queryParam}, #{queryResult}, #{queryType}, #{createdDate}, #{validDate})
|
||||||
|
on duplicate key update
|
||||||
|
query_result = values(query_result),
|
||||||
|
created_date = values(created_date),
|
||||||
|
valid_date = values(valid_date)
|
||||||
|
</insert>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiEnterpriseShareholderMapper">
|
||||||
|
<select id="selectByCreditCode" resultType="com.ruoyi.info.collection.domain.CcdiEnterpriseShareholder">
|
||||||
|
select shareholder_id, social_credit_code, shareholder_seq, shareholder_name,
|
||||||
|
shareholder_type, shareholder_credit_code, stock_percent, subscribed_capital,
|
||||||
|
capital_unit, cache_info_id, sync_time, create_by, create_time, update_by, update_time
|
||||||
|
from ccdi_enterprise_shareholder
|
||||||
|
where social_credit_code = #{creditCode}
|
||||||
|
order by shareholder_seq
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<delete id="deleteByCreditCode">
|
||||||
|
delete from ccdi_enterprise_shareholder where social_credit_code = #{creditCode}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<insert id="insertBatch">
|
||||||
|
insert into ccdi_enterprise_shareholder (
|
||||||
|
social_credit_code, shareholder_seq, shareholder_name, shareholder_type,
|
||||||
|
shareholder_credit_code, stock_percent, subscribed_capital, capital_unit,
|
||||||
|
cache_info_id, sync_time, create_by, create_time, update_by, update_time
|
||||||
|
) values
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(#{item.socialCreditCode}, #{item.shareholderSeq}, #{item.shareholderName}, #{item.shareholderType},
|
||||||
|
#{item.shareholderCreditCode}, #{item.stockPercent}, #{item.subscribedCapital}, #{item.capitalUnit},
|
||||||
|
#{item.cacheInfoId}, #{item.syncTime}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime})
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package com.ruoyi.info.collection.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.constant.HttpStatus;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.BaseStaffImportSubmitResultVO;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
||||||
|
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiBaseStaffControllerTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiBaseStaffController controller;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiBaseStaffService baseStaffService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiBaseStaffImportService importAsyncService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importTemplate_shouldDownloadDualSheetTemplate() {
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
controller.importTemplate(null);
|
||||||
|
|
||||||
|
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
|
||||||
|
null,
|
||||||
|
CcdiBaseStaffExcel.class,
|
||||||
|
"员工信息",
|
||||||
|
CcdiBaseStaffAssetInfoExcel.class,
|
||||||
|
"员工资产信息",
|
||||||
|
"员工信息维护导入模板"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importData_shouldWarnWhenBothSheetsAreEmpty() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"base-staff-empty.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"empty".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
AjaxResult result = controller.importData(file);
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
||||||
|
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
|
||||||
|
verifyNoInteractions(baseStaffService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importData_shouldSubmitOnlyStaffTaskWhenOnlyStaffSheetHasRows() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"base-staff.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"staff".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
CcdiBaseStaffExcel staffExcel = new CcdiBaseStaffExcel();
|
||||||
|
staffExcel.setStaffId(1001L);
|
||||||
|
BaseStaffImportSubmitResultVO submitResult = new BaseStaffImportSubmitResultVO();
|
||||||
|
submitResult.setStaffTaskId("staff-task-1");
|
||||||
|
when(baseStaffService.importBaseStaffWithAssets(List.of(staffExcel), List.of())).thenReturn(submitResult);
|
||||||
|
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
|
||||||
|
.thenReturn(List.of(staffExcel));
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
AjaxResult result = controller.importData(file);
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||||
|
assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG));
|
||||||
|
Object data = result.get(AjaxResult.DATA_TAG);
|
||||||
|
assertEquals("staff-task-1", data.getClass().getMethod("getStaffTaskId").invoke(data));
|
||||||
|
assertNull(data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importData_shouldSubmitTwoTasksWhenBothSheetsHaveRows() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"base-staff-both.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"both".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
CcdiBaseStaffExcel staffExcel = new CcdiBaseStaffExcel();
|
||||||
|
staffExcel.setStaffId(1002L);
|
||||||
|
CcdiBaseStaffAssetInfoExcel assetExcel = new CcdiBaseStaffAssetInfoExcel();
|
||||||
|
assetExcel.setPersonId("320101199001010011");
|
||||||
|
BaseStaffImportSubmitResultVO submitResult = new BaseStaffImportSubmitResultVO();
|
||||||
|
submitResult.setStaffTaskId("staff-task-2");
|
||||||
|
submitResult.setAssetTaskId("asset-task-2");
|
||||||
|
when(baseStaffService.importBaseStaffWithAssets(List.of(staffExcel), List.of(assetExcel))).thenReturn(submitResult);
|
||||||
|
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
|
||||||
|
.thenReturn(List.of(staffExcel));
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
|
||||||
|
.thenReturn(List.of(assetExcel));
|
||||||
|
|
||||||
|
AjaxResult result = controller.importData(file);
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||||
|
Object data = result.get(AjaxResult.DATA_TAG);
|
||||||
|
assertEquals("staff-task-2", data.getClass().getMethod("getStaffTaskId").invoke(data));
|
||||||
|
assertEquals("asset-task-2", data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ruoyi.info.collection.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.EnumOptionVO;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
class CcdiEnumControllerTest {
|
||||||
|
|
||||||
|
private final CcdiEnumController controller = new CcdiEnumController();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getEnterpriseRiskLevelOptions_shouldReturnConfiguredOptions() {
|
||||||
|
AjaxResult result = controller.getEnterpriseRiskLevelOptions();
|
||||||
|
List<?> data = (List<?>) result.get("data");
|
||||||
|
|
||||||
|
assertEquals(3, data.size());
|
||||||
|
EnumOptionVO first = (EnumOptionVO) data.get(0);
|
||||||
|
assertEquals("1", first.getValue());
|
||||||
|
assertEquals("高风险", first.getLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getEnterpriseSourceOptions_shouldReturnConfiguredOptions() {
|
||||||
|
AjaxResult result = controller.getEnterpriseSourceOptions();
|
||||||
|
List<?> data = (List<?>) result.get("data");
|
||||||
|
|
||||||
|
assertFalse(data.isEmpty());
|
||||||
|
EnumOptionVO first = (EnumOptionVO) data.get(0);
|
||||||
|
assertEquals("GENERAL", first.getValue());
|
||||||
|
assertEquals("一般企业", first.getLabel());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package com.ruoyi.info.collection.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.constant.HttpStatus;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
|
||||||
|
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiStaffFmyRelationControllerTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiStaffFmyRelationController controller;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiStaffFmyRelationService relationService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiStaffFmyRelationImportService relationImportService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiAssetInfoImportService assetInfoImportService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importTemplate_shouldDownloadDualSheetTemplate() {
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
controller.importTemplate(null);
|
||||||
|
|
||||||
|
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
|
||||||
|
null,
|
||||||
|
CcdiStaffFmyRelationExcel.class,
|
||||||
|
"员工亲属关系信息",
|
||||||
|
CcdiAssetInfoExcel.class,
|
||||||
|
"亲属资产信息",
|
||||||
|
"员工亲属关系维护导入模板"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importData_shouldErrorWhenBothSheetsAreEmpty() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"staff-family-empty.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"empty".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
AjaxResult result = controller.importData(file);
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
||||||
|
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
|
||||||
|
verifyNoInteractions(relationService, assetInfoImportService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importData_shouldSubmitOnlyRelationTaskWhenOnlyRelationSheetHasRows() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"staff-family-relation.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"relation".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
CcdiStaffFmyRelationExcel relationExcel = new CcdiStaffFmyRelationExcel();
|
||||||
|
relationExcel.setPersonId("320101199001010011");
|
||||||
|
StaffFmyRelationImportSubmitResultVO submitResult = new StaffFmyRelationImportSubmitResultVO();
|
||||||
|
submitResult.setRelationTaskId("relation-task-1");
|
||||||
|
when(relationService.importRelationWithAssets(List.of(relationExcel), List.of())).thenReturn(submitResult);
|
||||||
|
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
|
||||||
|
.thenReturn(List.of(relationExcel));
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
AjaxResult result = controller.importData(file);
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||||
|
assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG));
|
||||||
|
Object data = result.get(AjaxResult.DATA_TAG);
|
||||||
|
assertEquals("relation-task-1", data.getClass().getMethod("getRelationTaskId").invoke(data));
|
||||||
|
assertNull(data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importData_shouldSubmitTwoTasksWhenBothSheetsHaveRows() throws Exception {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"staff-family-both.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"both".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
CcdiStaffFmyRelationExcel relationExcel = new CcdiStaffFmyRelationExcel();
|
||||||
|
relationExcel.setPersonId("320101199001010012");
|
||||||
|
CcdiAssetInfoExcel assetExcel = new CcdiAssetInfoExcel();
|
||||||
|
assetExcel.setPersonId("320101199001010099");
|
||||||
|
StaffFmyRelationImportSubmitResultVO submitResult = new StaffFmyRelationImportSubmitResultVO();
|
||||||
|
submitResult.setRelationTaskId("relation-task-2");
|
||||||
|
submitResult.setAssetTaskId("asset-task-2");
|
||||||
|
when(relationService.importRelationWithAssets(List.of(relationExcel), List.of(assetExcel))).thenReturn(submitResult);
|
||||||
|
|
||||||
|
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
|
||||||
|
.thenReturn(List.of(relationExcel));
|
||||||
|
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
|
||||||
|
.thenReturn(List.of(assetExcel));
|
||||||
|
|
||||||
|
AjaxResult result = controller.importData(file);
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||||
|
Object data = result.get(AjaxResult.DATA_TAG);
|
||||||
|
assertEquals("relation-task-2", data.getClass().getMethod("getRelationTaskId").invoke(data));
|
||||||
|
assertEquals("asset-task-2", data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getImportFailures_shouldReturnPagedRowsWithSheetAndRowInfo() {
|
||||||
|
StaffFmyRelationImportFailureVO failure1 = new StaffFmyRelationImportFailureVO();
|
||||||
|
failure1.setSheetName("员工亲属关系信息");
|
||||||
|
failure1.setRowNum(2);
|
||||||
|
failure1.setPersonId("A1");
|
||||||
|
|
||||||
|
StaffFmyRelationImportFailureVO failure2 = new StaffFmyRelationImportFailureVO();
|
||||||
|
failure2.setSheetName("员工亲属关系信息");
|
||||||
|
failure2.setRowNum(3);
|
||||||
|
failure2.setPersonId("A2");
|
||||||
|
|
||||||
|
when(relationImportService.getImportFailures("task-1")).thenReturn(List.of(failure1, failure2));
|
||||||
|
|
||||||
|
TableDataInfo result = controller.getImportFailures("task-1", 2, 1);
|
||||||
|
|
||||||
|
assertEquals(2, result.getTotal());
|
||||||
|
assertEquals(1, result.getRows().size());
|
||||||
|
StaffFmyRelationImportFailureVO row = (StaffFmyRelationImportFailureVO) result.getRows().get(0);
|
||||||
|
assertEquals("员工亲属关系信息", row.getSheetName());
|
||||||
|
assertEquals(3, row.getRowNum());
|
||||||
|
assertEquals("A2", row.getPersonId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,18 @@ class CcdiAccountInfoMapperTest {
|
|||||||
assertTrue(sql.contains("ai.owner_type <> 'credit_customer'"), sql);
|
assertTrue(sql.contains("ai.owner_type <> 'credit_customer'"), sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectAccountInfoPage_shouldFilterByAccountNo() throws Exception {
|
||||||
|
MappedStatement mappedStatement = loadMappedStatement(
|
||||||
|
"com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper.selectAccountInfoPage");
|
||||||
|
CcdiAccountInfoQueryDTO queryDTO = new CcdiAccountInfoQueryDTO();
|
||||||
|
queryDTO.setAccountNo("6222");
|
||||||
|
|
||||||
|
String sql = renderSql(mappedStatement, Map.of("query", queryDTO)).toLowerCase();
|
||||||
|
|
||||||
|
assertTrue(sql.contains("ai.account_no like concat('%', ?, '%')"), sql);
|
||||||
|
}
|
||||||
|
|
||||||
private MappedStatement loadMappedStatement(String statementId) throws Exception {
|
private MappedStatement loadMappedStatement(String statementId) throws Exception {
|
||||||
Configuration configuration = new Configuration();
|
Configuration configuration = new Configuration();
|
||||||
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
|
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.info.collection.mapper;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiEnterpriseBaseInfoMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapperXml_shouldContainPageQueryAndImportColumns() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("mapper/info/collection/CcdiEnterpriseBaseInfoMapper.xml")) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("selectEnterpriseBaseInfoPage"), xml);
|
||||||
|
assertTrue(xml.contains("risk_level"), xml);
|
||||||
|
assertTrue(xml.contains("ent_source"), xml);
|
||||||
|
assertTrue(xml.contains("data_source"), xml);
|
||||||
|
assertTrue(xml.contains("ORDER BY create_time DESC"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ruoyi.info.collection.mapper;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiIntermediaryMapperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapperXml_shouldContainThreeRecordTypesAndRelatedKeywordQuery() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("mapper/info/collection/CcdiIntermediaryMapper.xml")) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("INTERMEDIARY"), xml);
|
||||||
|
assertTrue(xml.contains("RELATIVE"), xml);
|
||||||
|
assertTrue(xml.contains("ENTERPRISE_RELATION"), xml);
|
||||||
|
assertTrue(xml.contains("relatedIntermediaryKeyword"), xml);
|
||||||
|
assertTrue(xml.contains("related_intermediary_name"), xml);
|
||||||
|
assertTrue(xml.contains("relation_text"), xml);
|
||||||
|
assertTrue(xml.contains("CAST('实体'"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.service;
|
|||||||
import com.ruoyi.info.collection.domain.CcdiAccountInfo;
|
import com.ruoyi.info.collection.domain.CcdiAccountInfo;
|
||||||
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
|
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiAccountInfoExcel;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper;
|
import com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
|
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||||
@@ -16,6 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.springframework.beans.BeanWrapperImpl;
|
import org.springframework.beans.BeanWrapperImpl;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -109,6 +111,64 @@ class CcdiAccountInfoServiceImplTest {
|
|||||||
assertNull(wrapper.getPropertyValue("txnRiskLevel"));
|
assertNull(wrapper.getPropertyValue("txnRiskLevel"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insertInternalIntermediaryAccount_shouldForceHighRiskLevelOnAccountInfo() {
|
||||||
|
CcdiAccountInfoAddDTO dto = buildBaseAddDto();
|
||||||
|
dto.setOwnerType("INTERMEDIARY");
|
||||||
|
dto.setOwnerId("91330100MA00000001");
|
||||||
|
dto.setBankScope("INTERNAL");
|
||||||
|
dto.setIsActualControl(1);
|
||||||
|
dto.setAvgMonthTxnCount(8);
|
||||||
|
dto.setAvgMonthTxnAmount(new BigDecimal("9988.66"));
|
||||||
|
dto.setTxnFrequencyLevel("HIGH");
|
||||||
|
dto.setDebitSingleMaxAmount(new BigDecimal("111.11"));
|
||||||
|
dto.setCreditSingleMaxAmount(new BigDecimal("222.22"));
|
||||||
|
dto.setDebitDailyMaxAmount(new BigDecimal("333.33"));
|
||||||
|
dto.setCreditDailyMaxAmount(new BigDecimal("444.44"));
|
||||||
|
dto.setTxnRiskLevel("LOW");
|
||||||
|
|
||||||
|
when(accountInfoMapper.selectCount(any())).thenReturn(0L);
|
||||||
|
when(accountInfoMapper.insert(any(CcdiAccountInfo.class))).thenReturn(1);
|
||||||
|
|
||||||
|
service.insertAccountInfo(dto);
|
||||||
|
|
||||||
|
ArgumentCaptor<CcdiAccountInfo> captor = ArgumentCaptor.forClass(CcdiAccountInfo.class);
|
||||||
|
verify(accountInfoMapper).insert(captor.capture());
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(captor.getValue());
|
||||||
|
assertNull(wrapper.getPropertyValue("isActualControl"));
|
||||||
|
assertNull(wrapper.getPropertyValue("avgMonthTxnCount"));
|
||||||
|
assertNull(wrapper.getPropertyValue("avgMonthTxnAmount"));
|
||||||
|
assertNull(wrapper.getPropertyValue("txnFrequencyLevel"));
|
||||||
|
assertEquals("HIGH", wrapper.getPropertyValue("txnRiskLevel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExternalIntermediaryAccount_shouldForceHighRiskLevelOnAccountInfo() {
|
||||||
|
CcdiAccountInfoExcel excel = new CcdiAccountInfoExcel();
|
||||||
|
excel.setOwnerType("中介");
|
||||||
|
excel.setOwnerId("91330100MA00000002");
|
||||||
|
excel.setAccountName("测试中介");
|
||||||
|
excel.setAccountNo("6222024000000099");
|
||||||
|
excel.setAccountType("银行账户");
|
||||||
|
excel.setBankScope("行外");
|
||||||
|
excel.setOpenBank("中国银行");
|
||||||
|
excel.setBankCode("BOC");
|
||||||
|
excel.setCurrency("CNY");
|
||||||
|
excel.setStatus("正常");
|
||||||
|
excel.setEffectiveDate("2026-07-10");
|
||||||
|
excel.setTxnRiskLevel("LOW");
|
||||||
|
|
||||||
|
when(accountInfoMapper.selectOne(any())).thenReturn(null);
|
||||||
|
when(accountInfoMapper.selectCount(any())).thenReturn(0L);
|
||||||
|
when(accountInfoMapper.insert(any(CcdiAccountInfo.class))).thenReturn(1);
|
||||||
|
|
||||||
|
service.importAccountInfo(List.of(excel), false);
|
||||||
|
|
||||||
|
ArgumentCaptor<CcdiAccountInfo> captor = ArgumentCaptor.forClass(CcdiAccountInfo.class);
|
||||||
|
verify(accountInfoMapper).insert(captor.capture());
|
||||||
|
assertEquals("HIGH", captor.getValue().getTxnRiskLevel());
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiAccountInfoAddDTO buildBaseAddDto() {
|
private CcdiAccountInfoAddDTO buildBaseAddDto() {
|
||||||
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
|
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
|
||||||
dto.setAccountNo("6222024000000001");
|
dto.setAccountNo("6222024000000001");
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.ImportFailureVO;
|
||||||
|
import com.ruoyi.info.collection.service.impl.CcdiBaseStaffImportServiceImpl;
|
||||||
|
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.data.redis.core.HashOperations;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiBaseStaffDualImportServiceTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiBaseStaffImportServiceImpl service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper baseStaffMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HashOperations<String, Object, Object> hashOperations;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ValueOperations<String, Object> valueOperations;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SysDeptMapper deptMapper;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importBaseStaffAsync_shouldTreatExistingEmployeeAsFailureInsteadOfUpdate() {
|
||||||
|
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
|
||||||
|
excel.setStaffId(1001L);
|
||||||
|
excel.setName("张三");
|
||||||
|
excel.setDeptId(10L);
|
||||||
|
excel.setIdCard("11010519491231002X");
|
||||||
|
excel.setPhone("13812345678");
|
||||||
|
excel.setStatus("0");
|
||||||
|
excel.setPartyMember(1);
|
||||||
|
|
||||||
|
CcdiBaseStaff existing = new CcdiBaseStaff();
|
||||||
|
existing.setStaffId(1001L);
|
||||||
|
existing.setIdCard("11010519491231002X");
|
||||||
|
|
||||||
|
when(baseStaffMapper.selectBatchIds(List.of(1001L))).thenReturn(List.of(existing));
|
||||||
|
when(baseStaffMapper.selectList(any())).thenReturn(List.of(existing));
|
||||||
|
lenient().when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
|
||||||
|
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||||
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
|
|
||||||
|
service.importBaseStaffAsync(List.of(excel), "task-existing");
|
||||||
|
|
||||||
|
verify(baseStaffMapper, never()).insertBatch(any());
|
||||||
|
verify(baseStaffMapper, never()).insertOrUpdateBatch(any());
|
||||||
|
|
||||||
|
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||||
|
verify(valueOperations).set(eq("import:baseStaff:task-existing:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||||
|
ImportFailureVO failure = (ImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||||
|
assertEquals("员工信息", failure.getSheetName());
|
||||||
|
assertEquals(2, failure.getRowNum());
|
||||||
|
assertEquals(1001L, failure.getStaffId());
|
||||||
|
assertEquals("该员工ID已存在", failure.getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateStaffData_shouldRejectExistingIdCardWhenStaffIdDoesNotExist() {
|
||||||
|
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
|
||||||
|
|
||||||
|
RuntimeException exception = org.junit.jupiter.api.Assertions.assertThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> service.validateStaffData(buildExcelDto(), Set.of(), Set.of("11010519491231002X"))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("该身份证号已存在", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importBaseStaffAsync_shouldSaveFailureWhenDeptIsInvalid() {
|
||||||
|
CcdiBaseStaffExcel validExcel = buildExcel(1001L, 10L, "11010519491231002X");
|
||||||
|
CcdiBaseStaffExcel invalidExcel = buildExcel(1002L, 99L, "320101199001010014");
|
||||||
|
|
||||||
|
when(baseStaffMapper.selectBatchIds(List.of(1001L, 1002L))).thenReturn(List.of());
|
||||||
|
when(baseStaffMapper.selectList(any())).thenReturn(List.of());
|
||||||
|
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
|
||||||
|
when(deptMapper.selectDeptById(99L)).thenReturn(null);
|
||||||
|
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||||
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
|
|
||||||
|
service.importBaseStaffAsync(List.of(validExcel, invalidExcel), "task-invalid-dept");
|
||||||
|
|
||||||
|
verify(baseStaffMapper).insertBatch(any());
|
||||||
|
|
||||||
|
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||||
|
verify(valueOperations).set(eq("import:baseStaff:task-invalid-dept:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||||
|
ImportFailureVO failure = (ImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||||
|
assertEquals("员工信息", failure.getSheetName());
|
||||||
|
assertEquals(3, failure.getRowNum());
|
||||||
|
assertEquals(1002L, failure.getStaffId());
|
||||||
|
assertEquals("所属部门ID[99]不存在或已停用/删除,请检查机构号", failure.getErrorMessage());
|
||||||
|
|
||||||
|
ArgumentCaptor<Map<String, Object>> statusCaptor = ArgumentCaptor.forClass(Map.class);
|
||||||
|
verify(hashOperations).putAll(eq("import:baseStaff:task-invalid-dept"), statusCaptor.capture());
|
||||||
|
assertEquals("PARTIAL_SUCCESS", statusCaptor.getValue().get("status"));
|
||||||
|
assertEquals(1, statusCaptor.getValue().get("successCount"));
|
||||||
|
assertEquals(1, statusCaptor.getValue().get("failureCount"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO buildExcelDto() {
|
||||||
|
com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO dto = new com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO();
|
||||||
|
dto.setName("李四");
|
||||||
|
dto.setStaffId(2001L);
|
||||||
|
dto.setDeptId(10L);
|
||||||
|
dto.setIdCard("11010519491231002X");
|
||||||
|
dto.setPhone("13812345678");
|
||||||
|
dto.setStatus("0");
|
||||||
|
dto.setPartyMember(1);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiBaseStaffExcel buildExcel(Long staffId, Long deptId, String idCard) {
|
||||||
|
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
|
||||||
|
excel.setStaffId(staffId);
|
||||||
|
excel.setName("张三");
|
||||||
|
excel.setDeptId(deptId);
|
||||||
|
excel.setIdCard(idCard);
|
||||||
|
excel.setPhone("13812345678");
|
||||||
|
excel.setStatus("0");
|
||||||
|
excel.setPartyMember(1);
|
||||||
|
return excel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysDept buildDept(Long deptId, String status, String delFlag) {
|
||||||
|
SysDept dept = new SysDept();
|
||||||
|
dept.setDeptId(deptId);
|
||||||
|
dept.setDeptName("测试部门");
|
||||||
|
dept.setStatus(status);
|
||||||
|
dept.setDelFlag(delFlag);
|
||||||
|
return dept;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import com.ruoyi.info.collection.service.impl.CcdiCreditInfoServiceImpl;
|
|||||||
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
|
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
|
||||||
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
|
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
|
||||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeData;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeData;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParsePayload;
|
import com.ruoyi.lsfx.domain.response.CreditParsePayload;
|
||||||
@@ -18,6 +19,7 @@ import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
@@ -34,15 +36,26 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class CcdiCreditInfoServiceImplTest {
|
class CcdiCreditInfoServiceImplTest {
|
||||||
|
|
||||||
|
private static final CallerContext CALLER = CallerContext.of(7L, "tester");
|
||||||
|
private static final CreditHtmlStorageService.StoredCreditHtml STORED_HTML =
|
||||||
|
new CreditHtmlStorageService.StoredCreditHtml(
|
||||||
|
"/profile/credit-html/2026/05/12/a_1.html",
|
||||||
|
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html");
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private CcdiCreditInfoServiceImpl service;
|
private CcdiCreditInfoServiceImpl service;
|
||||||
|
|
||||||
@@ -73,19 +86,23 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
|
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
|
||||||
"/profile/credit-html/2026/05/12/family_1.html",
|
"/profile/credit-html/2026/05/12/family_1.html",
|
||||||
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html"));
|
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html"));
|
||||||
when(creditParseClient.parse(anyString()))
|
when(creditParseClient.parse(any(CallerContext.class), anyString()))
|
||||||
.thenReturn(successResponse("330101199202020022", "李四", "2026-03-24"));
|
.thenReturn(successResponse("330101199202020022", "李四", "2026-03-24"));
|
||||||
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
||||||
.thenReturn(List.of(buildDebt("330101199202020022")));
|
.thenReturn(List.of(buildDebt("330101199202020022")));
|
||||||
when(assembler.buildNegative(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
when(assembler.buildNegative(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
||||||
.thenReturn(buildNegative("330101199202020022"));
|
.thenReturn(buildNegative("330101199202020022"));
|
||||||
|
|
||||||
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
CreditInfoUploadResultVO result = service.upload(List.of(file), CALLER);
|
||||||
|
|
||||||
assertEquals(1, result.getSuccessCount());
|
assertEquals(1, result.getSuccessCount());
|
||||||
assertEquals(0, result.getFailureCount());
|
assertEquals(0, result.getFailureCount());
|
||||||
verify(creditParseClient).parse("http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html");
|
InOrder processingOrder = inOrder(creditParseClient, creditHtmlStorageService, debtsInfoMapper);
|
||||||
verify(debtsInfoMapper).deleteByPersonId("330101199202020022");
|
processingOrder.verify(creditParseClient)
|
||||||
|
.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html");
|
||||||
|
processingOrder.verify(creditHtmlStorageService)
|
||||||
|
.delete(any(CreditHtmlStorageService.StoredCreditHtml.class));
|
||||||
|
processingOrder.verify(debtsInfoMapper).deleteByPersonId("330101199202020022");
|
||||||
verify(negativeInfoMapper).deleteByPersonId("330101199202020022");
|
verify(negativeInfoMapper).deleteByPersonId("330101199202020022");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,15 +114,16 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
|
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
|
||||||
"/profile/credit-html/2026/05/12/a_1.html",
|
"/profile/credit-html/2026/05/12/a_1.html",
|
||||||
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
|
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
|
||||||
when(creditParseClient.parse(anyString()))
|
when(creditParseClient.parse(any(CallerContext.class), anyString()))
|
||||||
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
|
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
|
||||||
when(queryMapper.selectLatestQueryDate("330101199001010011"))
|
when(queryMapper.selectLatestQueryDate("330101199001010011"))
|
||||||
.thenReturn(LocalDate.parse("2026-03-05"));
|
.thenReturn(LocalDate.parse("2026-03-05"));
|
||||||
|
|
||||||
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
CreditInfoUploadResultVO result = service.upload(List.of(file), CALLER);
|
||||||
|
|
||||||
assertEquals(0, result.getSuccessCount());
|
assertEquals(0, result.getSuccessCount());
|
||||||
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
|
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
|
||||||
|
verify(creditHtmlStorageService).delete(STORED_HTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -118,12 +136,13 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
|
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
|
||||||
CreditParseInvokeResponse response = successResponse("330101199001010011", "张三", "2026-03-03");
|
CreditParseInvokeResponse response = successResponse("330101199001010011", "张三", "2026-03-03");
|
||||||
response.setCode(99999);
|
response.setCode(99999);
|
||||||
when(creditParseClient.parse(anyString())).thenReturn(response);
|
when(creditParseClient.parse(any(CallerContext.class), anyString())).thenReturn(response);
|
||||||
|
|
||||||
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
CreditInfoUploadResultVO result = service.upload(List.of(file), CALLER);
|
||||||
|
|
||||||
assertEquals(0, result.getSuccessCount());
|
assertEquals(0, result.getSuccessCount());
|
||||||
assertEquals("征信解析平台状态码异常: 99999", result.getFailures().get(0).getReason());
|
assertEquals("征信解析平台状态码异常: 99999", result.getFailures().get(0).getReason());
|
||||||
|
verify(creditHtmlStorageService).delete(STORED_HTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -138,17 +157,83 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
response.getData().setStatus(0);
|
response.getData().setStatus(0);
|
||||||
response.getData().setReasonCode(500);
|
response.getData().setReasonCode(500);
|
||||||
response.getData().setReasonMessage("结果解析失败");
|
response.getData().setReasonMessage("结果解析失败");
|
||||||
when(creditParseClient.parse(anyString())).thenReturn(response);
|
when(creditParseClient.parse(any(CallerContext.class), anyString())).thenReturn(response);
|
||||||
|
|
||||||
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
CreditInfoUploadResultVO result = service.upload(List.of(file), CALLER);
|
||||||
|
|
||||||
assertEquals(0, result.getSuccessCount());
|
assertEquals(0, result.getSuccessCount());
|
||||||
assertEquals("结果解析失败", result.getFailures().get(0).getReason());
|
assertEquals("结果解析失败", result.getFailures().get(0).getReason());
|
||||||
|
verify(creditHtmlStorageService).delete(STORED_HTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void uploadHtmlFiles_shouldDeleteBeforeDatabaseFailure() throws Exception {
|
||||||
|
MockMultipartFile file = htmlFile("a.html");
|
||||||
|
when(creditHtmlStorageService.save(any())).thenReturn(STORED_HTML);
|
||||||
|
when(creditParseClient.parse(any(CallerContext.class), anyString()))
|
||||||
|
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
|
||||||
|
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
||||||
|
.thenReturn(List.of(buildDebt("330101199001010011")));
|
||||||
|
when(assembler.buildNegative(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
||||||
|
.thenReturn(buildNegative("330101199001010011"));
|
||||||
|
doThrow(new RuntimeException("入库失败"))
|
||||||
|
.when(debtsInfoMapper).deleteByPersonId("330101199001010011");
|
||||||
|
|
||||||
|
CreditInfoUploadResultVO result = service.upload(List.of(file), CALLER);
|
||||||
|
|
||||||
|
assertEquals(0, result.getSuccessCount());
|
||||||
|
assertEquals("入库失败", result.getFailures().get(0).getReason());
|
||||||
|
InOrder processingOrder = inOrder(creditHtmlStorageService, debtsInfoMapper);
|
||||||
|
processingOrder.verify(creditHtmlStorageService).delete(STORED_HTML);
|
||||||
|
processingOrder.verify(debtsInfoMapper).deleteByPersonId("330101199001010011");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void uploadHtmlFiles_shouldFailBeforeValidationWhenDeleteFails() throws Exception {
|
||||||
|
MockMultipartFile file = htmlFile("a.html");
|
||||||
|
when(creditHtmlStorageService.save(any())).thenReturn(STORED_HTML);
|
||||||
|
when(creditParseClient.parse(any(CallerContext.class), anyString()))
|
||||||
|
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
|
||||||
|
doThrow(new java.io.IOException("本地文件删除失败"))
|
||||||
|
.when(creditHtmlStorageService).delete(STORED_HTML);
|
||||||
|
|
||||||
|
CreditInfoUploadResultVO result = service.upload(List.of(file), CALLER);
|
||||||
|
|
||||||
|
assertEquals(0, result.getSuccessCount());
|
||||||
|
assertEquals("本地文件删除失败", result.getFailures().get(0).getReason());
|
||||||
|
verifyNoInteractions(assembler, debtsInfoMapper, negativeInfoMapper, queryMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void uploadHtmlFiles_shouldPreserveParseErrorAndContinueWhenDeleteAlsoFails() throws Exception {
|
||||||
|
MockMultipartFile failedFile = htmlFile("failed.html");
|
||||||
|
MockMultipartFile successFile = htmlFile("success.html");
|
||||||
|
CreditHtmlStorageService.StoredCreditHtml failedStoredHtml = storedHtml("failed_1.html");
|
||||||
|
CreditHtmlStorageService.StoredCreditHtml successStoredHtml = storedHtml("success_1.html");
|
||||||
|
when(creditHtmlStorageService.save(any())).thenReturn(failedStoredHtml, successStoredHtml);
|
||||||
|
when(creditParseClient.parse(any(CallerContext.class), anyString()))
|
||||||
|
.thenThrow(new RuntimeException("解析接口异常"))
|
||||||
|
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
|
||||||
|
doThrow(new java.io.IOException("本地文件删除失败"))
|
||||||
|
.doNothing()
|
||||||
|
.when(creditHtmlStorageService).delete(any(CreditHtmlStorageService.StoredCreditHtml.class));
|
||||||
|
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
when(assembler.buildNegative(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
||||||
|
.thenReturn(null);
|
||||||
|
|
||||||
|
CreditInfoUploadResultVO result = service.upload(List.of(failedFile, successFile), CALLER);
|
||||||
|
|
||||||
|
assertEquals(1, result.getSuccessCount());
|
||||||
|
assertEquals(1, result.getFailureCount());
|
||||||
|
assertEquals("解析接口异常", result.getFailures().get(0).getReason());
|
||||||
|
verify(creditHtmlStorageService).delete(failedStoredHtml);
|
||||||
|
verify(creditHtmlStorageService).delete(successStoredHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void creditHtmlStorage_shouldStoreHtmlUnderProfileAndBuildRemotePath(@TempDir Path profileDir) throws Exception {
|
void creditHtmlStorage_shouldStoreAndDeleteHtmlWithinProfile(@TempDir Path profileDir) throws Exception {
|
||||||
String oldProfile = RuoYiConfig.getProfile();
|
String oldProfile = RuoYiConfig.getProfile();
|
||||||
new RuoYiConfig().setProfile(profileDir.toString());
|
new RuoYiConfig().setProfile(profileDir.toString());
|
||||||
try {
|
try {
|
||||||
@@ -164,11 +249,42 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
assertEquals("http://127.0.0.1:62318" + storedHtml.profilePath(), storedHtml.remotePath());
|
assertEquals("http://127.0.0.1:62318" + storedHtml.profilePath(), storedHtml.remotePath());
|
||||||
Path savedFile = profileDir.resolve(storedHtml.profilePath().substring("/profile/".length()));
|
Path savedFile = profileDir.resolve(storedHtml.profilePath().substring("/profile/".length()));
|
||||||
assertTrue(Files.exists(savedFile));
|
assertTrue(Files.exists(savedFile));
|
||||||
|
storageService.delete(storedHtml);
|
||||||
|
assertFalse(Files.exists(savedFile));
|
||||||
|
storageService.delete(storedHtml);
|
||||||
} finally {
|
} finally {
|
||||||
new RuoYiConfig().setProfile(oldProfile);
|
new RuoYiConfig().setProfile(oldProfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void creditHtmlStorage_shouldRejectPathOutsideCreditHtmlDirectory(@TempDir Path profileDir) throws Exception {
|
||||||
|
String oldProfile = RuoYiConfig.getProfile();
|
||||||
|
new RuoYiConfig().setProfile(profileDir.toString());
|
||||||
|
try {
|
||||||
|
CreditHtmlStorageService storageService = new CreditHtmlStorageService();
|
||||||
|
Path outsideFile = Files.writeString(profileDir.resolve("outside.html"), "outside");
|
||||||
|
CreditHtmlStorageService.StoredCreditHtml storedHtml = new CreditHtmlStorageService.StoredCreditHtml(
|
||||||
|
"/profile/credit-html/../outside.html", "http://127.0.0.1/profile/credit-html/../outside.html");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> storageService.delete(storedHtml));
|
||||||
|
assertTrue(Files.exists(outsideFile));
|
||||||
|
} finally {
|
||||||
|
new RuoYiConfig().setProfile(oldProfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockMultipartFile htmlFile(String fileName) {
|
||||||
|
return new MockMultipartFile(
|
||||||
|
"files", fileName, "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CreditHtmlStorageService.StoredCreditHtml storedHtml(String fileName) {
|
||||||
|
return new CreditHtmlStorageService.StoredCreditHtml(
|
||||||
|
"/profile/credit-html/2026/05/12/" + fileName,
|
||||||
|
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/" + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
private CreditParseInvokeResponse successResponse(String personId, String personName, String reportTime) {
|
private CreditParseInvokeResponse successResponse(String personId, String personName, String reportTime) {
|
||||||
CreditParsePayload payload = new CreditParsePayload();
|
CreditParsePayload payload = new CreditParsePayload();
|
||||||
Map<String, Object> header = new HashMap<>();
|
Map<String, Object> header = new HashMap<>();
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseShareholderMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.impl.CcdiEnterpriseBaseInfoServiceImpl;
|
import com.ruoyi.info.collection.service.impl.CcdiEnterpriseBaseInfoServiceImpl;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -49,13 +51,22 @@ class CcdiEnterpriseBaseInfoServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiEnterpriseShareholderMapper enterpriseShareholderMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IEnterpriseProfileQueryService enterpriseProfileQueryService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IEnterpriseEntitySyncService enterpriseEntitySyncService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void insertEnterpriseBaseInfo_shouldPersistWhenSocialCreditCodeIsUnique() {
|
void insertEnterpriseBaseInfo_shouldPersistWhenSocialCreditCodeIsUnique() {
|
||||||
CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto();
|
CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto();
|
||||||
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
||||||
when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
||||||
|
|
||||||
int result = service.insertEnterpriseBaseInfo(addDTO);
|
int result = service.insertEnterpriseBaseInfo(CallerContext.system(), addDTO);
|
||||||
|
|
||||||
assertEquals(1, result);
|
assertEquals(1, result);
|
||||||
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
||||||
@@ -72,7 +83,7 @@ class CcdiEnterpriseBaseInfoServiceImplTest {
|
|||||||
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
||||||
when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
||||||
|
|
||||||
int result = service.insertEnterpriseBaseInfo(addDTO);
|
int result = service.insertEnterpriseBaseInfo(CallerContext.system(), addDTO);
|
||||||
|
|
||||||
assertEquals(1, result);
|
assertEquals(1, result);
|
||||||
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
||||||
@@ -88,7 +99,7 @@ class CcdiEnterpriseBaseInfoServiceImplTest {
|
|||||||
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
||||||
|
|
||||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||||
() -> service.insertEnterpriseBaseInfo(addDTO));
|
() -> service.insertEnterpriseBaseInfo(CallerContext.system(), addDTO));
|
||||||
|
|
||||||
assertEquals("风险等级不在允许范围内", exception.getMessage());
|
assertEquals("风险等级不在允许范围内", exception.getMessage());
|
||||||
}
|
}
|
||||||
@@ -99,7 +110,7 @@ class CcdiEnterpriseBaseInfoServiceImplTest {
|
|||||||
when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(null);
|
when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(null);
|
||||||
|
|
||||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||||
() -> service.updateEnterpriseBaseInfo(editDTO));
|
() -> service.updateEnterpriseBaseInfo(CallerContext.system(), editDTO));
|
||||||
|
|
||||||
assertEquals("实体库记录不存在", exception.getMessage());
|
assertEquals("实体库记录不存在", exception.getMessage());
|
||||||
}
|
}
|
||||||
@@ -114,7 +125,7 @@ class CcdiEnterpriseBaseInfoServiceImplTest {
|
|||||||
when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(existing);
|
when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(existing);
|
||||||
when(enterpriseBaseInfoMapper.updateById(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
when(enterpriseBaseInfoMapper.updateById(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
||||||
|
|
||||||
int result = service.updateEnterpriseBaseInfo(editDTO);
|
int result = service.updateEnterpriseBaseInfo(CallerContext.system(), editDTO);
|
||||||
|
|
||||||
assertEquals(1, result);
|
assertEquals(1, result);
|
||||||
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
||||||
@@ -130,6 +141,7 @@ class CcdiEnterpriseBaseInfoServiceImplTest {
|
|||||||
entity.setRiskLevel("1");
|
entity.setRiskLevel("1");
|
||||||
entity.setEntSource("GENERAL");
|
entity.setEntSource("GENERAL");
|
||||||
when(enterpriseBaseInfoMapper.selectById("91310000123456789A")).thenReturn(entity);
|
when(enterpriseBaseInfoMapper.selectById("91310000123456789A")).thenReturn(entity);
|
||||||
|
when(enterpriseShareholderMapper.selectByCreditCode("91310000123456789A")).thenReturn(java.util.List.of());
|
||||||
|
|
||||||
CcdiEnterpriseBaseInfoVO vo = service.selectEnterpriseBaseInfoById("91310000123456789A");
|
CcdiEnterpriseBaseInfoVO vo = service.selectEnterpriseBaseInfoById("91310000123456789A");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
|
||||||
|
import com.ruoyi.info.collection.domain.dto.CcdiIntermediaryRelativeAddDTO;
|
||||||
|
import com.ruoyi.info.collection.domain.dto.CcdiIntermediaryPersonAddDTO;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper;
|
||||||
|
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryServiceImpl;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiIntermediaryServiceImplTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiIntermediaryServiceImpl service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiBizIntermediaryMapper bizIntermediaryMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiIntermediaryMapper intermediaryMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiIntermediaryEnterpriseRelationMapper enterpriseRelationMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiIntermediaryPersonImportService personImportService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiIntermediaryEntityImportService entityImportService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insertIntermediaryPerson_shouldForceBenrenAndClearRelatedNumId() {
|
||||||
|
CcdiIntermediaryPersonAddDTO addDTO = new CcdiIntermediaryPersonAddDTO();
|
||||||
|
addDTO.setName("测试中介");
|
||||||
|
addDTO.setPersonId("320101199001010011");
|
||||||
|
addDTO.setPersonSubType("配偶");
|
||||||
|
addDTO.setRelatedNumId("parent-id");
|
||||||
|
|
||||||
|
when(bizIntermediaryMapper.selectCount(any())).thenReturn(0L);
|
||||||
|
when(bizIntermediaryMapper.insert(any(CcdiBizIntermediary.class))).thenReturn(1);
|
||||||
|
|
||||||
|
int result = service.insertIntermediaryPerson(addDTO);
|
||||||
|
|
||||||
|
assertEquals(1, result);
|
||||||
|
ArgumentCaptor<CcdiBizIntermediary> captor = ArgumentCaptor.forClass(CcdiBizIntermediary.class);
|
||||||
|
verify(bizIntermediaryMapper).insert(captor.capture());
|
||||||
|
assertEquals("本人", captor.getValue().getPersonSubType());
|
||||||
|
assertNull(captor.getValue().getRelatedNumId());
|
||||||
|
assertEquals("MANUAL", captor.getValue().getDataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insertIntermediaryRelative_shouldRejectBenrenSubType() {
|
||||||
|
CcdiBizIntermediary owner = new CcdiBizIntermediary();
|
||||||
|
owner.setBizId("biz-1");
|
||||||
|
owner.setPersonSubType("本人");
|
||||||
|
|
||||||
|
CcdiIntermediaryRelativeAddDTO addDTO = new CcdiIntermediaryRelativeAddDTO();
|
||||||
|
addDTO.setName("测试亲属");
|
||||||
|
addDTO.setPersonId("320101199001010022");
|
||||||
|
addDTO.setPersonSubType("本人");
|
||||||
|
|
||||||
|
when(bizIntermediaryMapper.selectById("biz-1")).thenReturn(owner);
|
||||||
|
|
||||||
|
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||||
|
() -> service.insertIntermediaryRelative("biz-1", addDTO));
|
||||||
|
|
||||||
|
assertEquals("亲属关系不能为本人", exception.getMessage());
|
||||||
|
verify(bizIntermediaryMapper, never()).insert(any(CcdiBizIntermediary.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteIntermediaryByIds_shouldDeleteRelativesAndEnterpriseRelationsWhenRemovingOwner() {
|
||||||
|
CcdiBizIntermediary owner = new CcdiBizIntermediary();
|
||||||
|
owner.setBizId("biz-1");
|
||||||
|
owner.setPersonSubType("本人");
|
||||||
|
|
||||||
|
when(bizIntermediaryMapper.selectById("biz-1")).thenReturn(owner);
|
||||||
|
when(bizIntermediaryMapper.delete(any())).thenReturn(2);
|
||||||
|
when(enterpriseRelationMapper.delete(any())).thenReturn(1);
|
||||||
|
when(bizIntermediaryMapper.deleteById("biz-1")).thenReturn(1);
|
||||||
|
|
||||||
|
int result = service.deleteIntermediaryByIds(new String[]{"biz-1"});
|
||||||
|
|
||||||
|
assertEquals(1, result);
|
||||||
|
verify(bizIntermediaryMapper).delete(any());
|
||||||
|
verify(enterpriseRelationMapper).delete(any());
|
||||||
|
verify(bizIntermediaryMapper).deleteById("biz-1");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiPurchaseTransactionFeatureContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeSupplierListContractsAcrossPurchaseTransactionModels() throws Exception {
|
||||||
|
assertHasField(
|
||||||
|
"com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO",
|
||||||
|
"supplierList"
|
||||||
|
);
|
||||||
|
assertHasField(
|
||||||
|
"com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO",
|
||||||
|
"supplierList"
|
||||||
|
);
|
||||||
|
assertHasField(
|
||||||
|
"com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO",
|
||||||
|
"supplierList"
|
||||||
|
);
|
||||||
|
assertHasField(
|
||||||
|
"com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO",
|
||||||
|
"supplierCount"
|
||||||
|
);
|
||||||
|
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier"));
|
||||||
|
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionSupplierDTO"));
|
||||||
|
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO"));
|
||||||
|
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDefineSupplierSubTableAndBiddingMigrationScripts() throws Exception {
|
||||||
|
String initSql = Files.readString(repoPath("sql/ccdi_purchase_transaction.sql"));
|
||||||
|
assertTrue(initSql.contains("CREATE TABLE `ccdi_purchase_transaction_supplier`"));
|
||||||
|
assertTrue(initSql.contains("`is_bid_winner`"));
|
||||||
|
assertTrue(initSql.contains("`sort_order`"));
|
||||||
|
assertTrue(initSql.contains("utf8mb4_general_ci"));
|
||||||
|
|
||||||
|
String menuSql = Files.readString(repoPath("sql/ccdi_purchase_transaction_menu.sql"));
|
||||||
|
assertTrue(menuSql.contains("招投标信息维护"));
|
||||||
|
|
||||||
|
Path migrationPath = repoPath("sql/migration/2026-04-22-bidding-info-maintenance-supplier-detail.sql");
|
||||||
|
assertTrue(Files.exists(migrationPath), "应提供招投标供应商明细迁移脚本");
|
||||||
|
|
||||||
|
String migrationSql = Files.readString(migrationPath);
|
||||||
|
assertTrue(migrationSql.contains("ccdi_purchase_transaction_supplier"));
|
||||||
|
assertTrue(migrationSql.contains("INSERT INTO ccdi_purchase_transaction_supplier"));
|
||||||
|
assertTrue(migrationSql.contains("UPDATE sys_menu"));
|
||||||
|
assertTrue(migrationSql.contains("招投标信息维护"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseTwoSheetTemplateForBiddingImport() throws Exception {
|
||||||
|
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel"));
|
||||||
|
|
||||||
|
String controller = Files.readString(
|
||||||
|
Path.of("src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java")
|
||||||
|
);
|
||||||
|
assertTrue(controller.contains("招投标主信息"));
|
||||||
|
assertTrue(controller.contains("供应商明细"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHasField(String className, String fieldName) throws Exception {
|
||||||
|
Class<?> clazz = Class.forName(className);
|
||||||
|
Field field = clazz.getDeclaredField(fieldName);
|
||||||
|
assertNotNull(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path repoPath(String relativePath) {
|
||||||
|
return Path.of("..", relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
package com.ruoyi.info.collection.utils;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import org.apache.poi.ss.usermodel.CellType;
|
||||||
|
import org.apache.poi.ss.usermodel.DataValidation;
|
||||||
|
import org.apache.poi.ss.usermodel.DataValidationConstraint;
|
||||||
|
import org.apache.poi.ss.usermodel.DataValidationHelper;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class EasyExcelUtilImportDropdownValidationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldPassWhenAllDictDropdownColumnsKeepListValidation() throws Exception {
|
||||||
|
byte[] bytes = baseStaffWorkbook(true, true, true, 2);
|
||||||
|
|
||||||
|
List<CcdiBaseStaffExcel> rows = EasyExcelUtil.importExcel(
|
||||||
|
new ByteArrayInputStream(bytes),
|
||||||
|
CcdiBaseStaffExcel.class,
|
||||||
|
"员工信息"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(2, rows.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldFailWhenPartyMemberDropdownIsMissing() throws Exception {
|
||||||
|
byte[] bytes = baseStaffWorkbook(false, true, true, 2);
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||||
|
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("是否党员 列缺少下拉框"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldFailWhenStatusDropdownIsMissing() throws Exception {
|
||||||
|
byte[] bytes = baseStaffWorkbook(true, false, true, 2);
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||||
|
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("员工信息 Sheet 的 状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldReportAllMissingDropdownColumnsInSameSheet() throws Exception {
|
||||||
|
byte[] bytes = baseStaffWorkbook(false, false, true, 2);
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||||
|
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("员工信息 Sheet 的 是否党员、状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldFailWhenValidationIsNotListType() throws Exception {
|
||||||
|
byte[] bytes = baseStaffWorkbook(true, true, false, 2);
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||||
|
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("状态 列缺少下拉框"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldFailWhenListValidationDoesNotCoverEveryActualDataRow() throws Exception {
|
||||||
|
byte[] bytes = baseStaffWorkbook(true, true, true, 1);
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||||
|
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("状态 列缺少下拉框"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldFailWhenSecondSheetDropdownIsMissing() throws Exception {
|
||||||
|
byte[] bytes = baseStaffDualSheetWorkbookWithMissingAssetStatusDropdown();
|
||||||
|
|
||||||
|
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||||
|
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffAssetInfoExcel.class, "员工资产信息")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("员工资产信息 Sheet 的 资产状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importExcel_shouldSkipDropdownStructureValidationWhenClassHasNoDictDropdownFields() throws Exception {
|
||||||
|
byte[] bytes = plainWorkbookWithoutDropdown();
|
||||||
|
|
||||||
|
List<PlainExcel> rows = EasyExcelUtil.importExcel(
|
||||||
|
new ByteArrayInputStream(bytes),
|
||||||
|
PlainExcel.class,
|
||||||
|
"普通信息"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(1, rows.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] baseStaffWorkbook(boolean partyDropdown, boolean statusDropdown, boolean statusAsList,
|
||||||
|
int statusLastRow) throws Exception {
|
||||||
|
try (Workbook workbook = new XSSFWorkbook();
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
Sheet sheet = workbook.createSheet("员工信息");
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = {"姓名", "员工ID", "所属部门ID", "身份证号", "电话", "年收入(元/年)",
|
||||||
|
"入职时间", "是否党员", "状态"};
|
||||||
|
for (int i = 0; i < headers.length; i++) {
|
||||||
|
header.createCell(i).setCellValue(headers[i]);
|
||||||
|
}
|
||||||
|
createBaseStaffRow(sheet, 1, "张三", 9020001L, "33010619850202101X", "0", "1");
|
||||||
|
createBaseStaffRow(sheet, 2, "李四", 9020002L, "330106198603031022", "1", "1");
|
||||||
|
|
||||||
|
if (partyDropdown) {
|
||||||
|
addListValidation(sheet, 7, 1, 2, "0", "1");
|
||||||
|
}
|
||||||
|
if (statusDropdown) {
|
||||||
|
if (statusAsList) {
|
||||||
|
addListValidation(sheet, 8, 1, statusLastRow, "0", "1");
|
||||||
|
} else {
|
||||||
|
addIntegerValidation(sheet, 8, 1, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
workbook.write(outputStream);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] baseStaffDualSheetWorkbookWithMissingAssetStatusDropdown() throws Exception {
|
||||||
|
try (Workbook workbook = new XSSFWorkbook();
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
Sheet staffSheet = workbook.createSheet("员工信息");
|
||||||
|
Row staffHeader = staffSheet.createRow(0);
|
||||||
|
String[] staffHeaders = {"姓名", "员工ID", "所属部门ID", "身份证号", "电话", "年收入(元/年)",
|
||||||
|
"入职时间", "是否党员", "状态"};
|
||||||
|
for (int i = 0; i < staffHeaders.length; i++) {
|
||||||
|
staffHeader.createCell(i).setCellValue(staffHeaders[i]);
|
||||||
|
}
|
||||||
|
createBaseStaffRow(staffSheet, 1, "张三", 9020001L, "33010619850202101X", "0", "1");
|
||||||
|
addListValidation(staffSheet, 7, 1, 1, "0", "1");
|
||||||
|
addListValidation(staffSheet, 8, 1, 1, "0", "1");
|
||||||
|
|
||||||
|
Sheet assetSheet = workbook.createSheet("员工资产信息");
|
||||||
|
Row assetHeader = assetSheet.createRow(0);
|
||||||
|
String[] assetHeaders = {"员工身份证号*", "资产大类*", "资产小类*", "资产名称*", "产权占比",
|
||||||
|
"购买/评估日期", "资产原值", "当前估值*", "估值截止日期", "资产状态*", "备注"};
|
||||||
|
for (int i = 0; i < assetHeaders.length; i++) {
|
||||||
|
assetHeader.createCell(i).setCellValue(assetHeaders[i]);
|
||||||
|
}
|
||||||
|
Row assetRow = assetSheet.createRow(1);
|
||||||
|
assetRow.createCell(0).setCellValue("33010619850202101X");
|
||||||
|
assetRow.createCell(1).setCellValue("房产");
|
||||||
|
assetRow.createCell(2).setCellValue("住宅");
|
||||||
|
assetRow.createCell(3).setCellValue("测试住宅");
|
||||||
|
assetRow.createCell(7).setCellValue(1000000D);
|
||||||
|
assetRow.createCell(9).setCellValue("正常");
|
||||||
|
|
||||||
|
workbook.write(outputStream);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBaseStaffRow(Sheet sheet, int rowIndex, String name, long staffId, String idCard,
|
||||||
|
String partyMember, String status) {
|
||||||
|
Row row = sheet.createRow(rowIndex);
|
||||||
|
row.createCell(0).setCellValue(name);
|
||||||
|
row.createCell(1).setCellValue(staffId);
|
||||||
|
row.createCell(2).setCellValue(103L);
|
||||||
|
row.createCell(3, CellType.STRING).setCellValue(idCard);
|
||||||
|
row.createCell(4, CellType.STRING).setCellValue("13370000001");
|
||||||
|
row.createCell(5).setCellValue(new BigDecimal("180000").doubleValue());
|
||||||
|
row.createCell(6).setCellValue("2026-04-30");
|
||||||
|
row.createCell(7, CellType.STRING).setCellValue(partyMember);
|
||||||
|
row.createCell(8, CellType.STRING).setCellValue(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListValidation(Sheet sheet, int columnIndex, int firstRow, int lastRow, String... options) {
|
||||||
|
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||||
|
DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
|
||||||
|
DataValidation validation = helper.createValidation(
|
||||||
|
constraint,
|
||||||
|
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex)
|
||||||
|
);
|
||||||
|
sheet.addValidationData(validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addIntegerValidation(Sheet sheet, int columnIndex, int firstRow, int lastRow) {
|
||||||
|
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||||
|
DataValidationConstraint constraint = helper.createIntegerConstraint(
|
||||||
|
DataValidationConstraint.OperatorType.BETWEEN,
|
||||||
|
"0",
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
DataValidation validation = helper.createValidation(
|
||||||
|
constraint,
|
||||||
|
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex)
|
||||||
|
);
|
||||||
|
sheet.addValidationData(validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] plainWorkbookWithoutDropdown() throws Exception {
|
||||||
|
try (Workbook workbook = new XSSFWorkbook();
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
Sheet sheet = workbook.createSheet("普通信息");
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
header.createCell(0).setCellValue("名称");
|
||||||
|
Row row = sheet.createRow(1);
|
||||||
|
row.createCell(0).setCellValue("张三");
|
||||||
|
workbook.write(outputStream);
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlainExcel {
|
||||||
|
@ExcelProperty(value = "名称", index = 0)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,12 @@
|
|||||||
<artifactId>ruoyi-common</artifactId>
|
<artifactId>ruoyi-common</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 系统日志持久化 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-system</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Web -->
|
<!-- Spring Web -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.lsfx.client;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||||
import com.ruoyi.lsfx.util.HttpUtil;
|
import com.ruoyi.lsfx.util.HttpUtil;
|
||||||
@@ -45,20 +46,20 @@ public class CreditParseClient {
|
|||||||
@Value("${credit-parse.api.model:LXCUSTALL}")
|
@Value("${credit-parse.api.model:LXCUSTALL}")
|
||||||
private String defaultModel;
|
private String defaultModel;
|
||||||
|
|
||||||
public CreditParseInvokeResponse parse(String remotePath) {
|
public CreditParseInvokeResponse parse(CallerContext caller, String remotePath) {
|
||||||
return parse(defaultModel, remotePath);
|
return parse(caller, defaultModel, remotePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreditParseInvokeResponse parse(String model, String remotePath) {
|
public CreditParseInvokeResponse parse(CallerContext caller, String model, String remotePath) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
String actualModel = StringUtils.isBlank(model) ? defaultModel : model;
|
String actualModel = StringUtils.isBlank(model) ? defaultModel : model;
|
||||||
String serialNum = buildSerialNum();
|
String serialNum = buildSerialNum();
|
||||||
try {
|
try {
|
||||||
Map<String, Object> initiateParams = buildInitiateParams(serialNum, actualModel, remotePath);
|
Map<String, Object> initiateParams = buildInitiateParams(serialNum, actualModel, remotePath);
|
||||||
CreditParseInvokeResponse initiateResponse = request(creditParseUrl, initiateParams, "发起接口");
|
CreditParseInvokeResponse initiateResponse = request(caller, creditParseUrl, initiateParams, "发起接口");
|
||||||
requireSuccessfulInitiateResponse(initiateResponse, "征信解析发起接口");
|
requireSuccessfulInitiateResponse(initiateResponse, "征信解析发起接口");
|
||||||
|
|
||||||
CreditParseInvokeResponse response = queryResult(serialNum);
|
CreditParseInvokeResponse response = queryResult(caller, serialNum);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms",
|
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms",
|
||||||
@@ -94,10 +95,10 @@ public class CreditParseClient {
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreditParseInvokeResponse queryResult(String serialNum) {
|
private CreditParseInvokeResponse queryResult(CallerContext caller, String serialNum) {
|
||||||
Map<String, Object> params = buildBaseParams(serialNum);
|
Map<String, Object> params = buildBaseParams(serialNum);
|
||||||
for (int attempt = 1; attempt <= RESULT_QUERY_MAX_ATTEMPTS; attempt++) {
|
for (int attempt = 1; attempt <= RESULT_QUERY_MAX_ATTEMPTS; attempt++) {
|
||||||
CreditParseInvokeResponse response = request(creditParseResultUrl, params,
|
CreditParseInvokeResponse response = request(caller, creditParseResultUrl, params,
|
||||||
"结果接口第" + attempt + "次查询");
|
"结果接口第" + attempt + "次查询");
|
||||||
requireSuccessfulServiceResponse(response, "征信解析结果接口");
|
requireSuccessfulServiceResponse(response, "征信解析结果接口");
|
||||||
if (response.getData() == null || response.getData().getMappingOutputFields() == null) {
|
if (response.getData() == null || response.getData().getMappingOutputFields() == null) {
|
||||||
@@ -130,10 +131,10 @@ public class CreditParseClient {
|
|||||||
Thread.sleep(intervalMillis);
|
Thread.sleep(intervalMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreditParseInvokeResponse request(String url, Map<String, Object> params, String stage) {
|
private CreditParseInvokeResponse request(CallerContext caller, String url, Map<String, Object> params, String stage) {
|
||||||
try {
|
try {
|
||||||
log.info("【征信解析】{}请求: url={}, params={}", stage, url, toJson(params));
|
log.info("【征信解析】{}请求: url={}, params={}", stage, url, toJson(params));
|
||||||
String responseJson = httpUtil.postUrlEncodedFormForString(url, params, null);
|
String responseJson = httpUtil.postUrlEncodedFormForString(caller, url, params, null);
|
||||||
log.info("【征信解析】{}返回JSON: {}", stage, responseJson);
|
log.info("【征信解析】{}返回JSON: {}", stage, responseJson);
|
||||||
return objectMapper.readValue(responseJson, CreditParseInvokeResponse.class);
|
return objectMapper.readValue(responseJson, CreditParseInvokeResponse.class);
|
||||||
} catch (LsfxApiException e) {
|
} catch (LsfxApiException e) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.lsfx.client;
|
package com.ruoyi.lsfx.client;
|
||||||
|
|
||||||
import com.ruoyi.lsfx.constants.LsfxConstants;
|
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.request.DeleteFilesRequest;
|
import com.ruoyi.lsfx.domain.request.DeleteFilesRequest;
|
||||||
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
||||||
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
||||||
@@ -68,7 +69,7 @@ public class LsfxAnalysisClient {
|
|||||||
/**
|
/**
|
||||||
* 获取Token
|
* 获取Token
|
||||||
*/
|
*/
|
||||||
public GetTokenResponse getToken(GetTokenRequest request) {
|
public GetTokenResponse getToken(CallerContext caller, GetTokenRequest request) {
|
||||||
log.info("【流水分析】获取Token请求: projectNo={}, entityName={}", request.getProjectNo(), request.getEntityName());
|
log.info("【流水分析】获取Token请求: projectNo={}, entityName={}", request.getProjectNo(), request.getEntityName());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ public class LsfxAnalysisClient {
|
|||||||
params.put("analysisType", request.getAnalysisType() != null ? request.getAnalysisType() : LsfxConstants.ANALYSIS_TYPE);
|
params.put("analysisType", request.getAnalysisType() != null ? request.getAnalysisType() : LsfxConstants.ANALYSIS_TYPE);
|
||||||
|
|
||||||
String url = baseUrl + getTokenEndpoint;
|
String url = baseUrl + getTokenEndpoint;
|
||||||
GetTokenResponse response = httpUtil.postFormData(url, params, null, GetTokenResponse.class);
|
GetTokenResponse response = httpUtil.postFormData(caller, url, params, null, GetTokenResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
if (response != null && response.getData() != null) {
|
if (response != null && response.getData() != null) {
|
||||||
@@ -110,14 +111,14 @@ public class LsfxAnalysisClient {
|
|||||||
/**
|
/**
|
||||||
* 上传文件
|
* 上传文件
|
||||||
*/
|
*/
|
||||||
public UploadFileResponse uploadFile(Integer groupId, File file) {
|
public UploadFileResponse uploadFile(CallerContext caller, Integer groupId, File file) {
|
||||||
return uploadFile(groupId, file, file.getName());
|
return uploadFile(caller, groupId, file, file.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传文件
|
* 上传文件
|
||||||
*/
|
*/
|
||||||
public UploadFileResponse uploadFile(Integer groupId, File file, String uploadFileName) {
|
public UploadFileResponse uploadFile(CallerContext caller, Integer groupId, File file, String uploadFileName) {
|
||||||
String multipartFileName = StringUtils.hasText(uploadFileName) ? uploadFileName : file.getName();
|
String multipartFileName = StringUtils.hasText(uploadFileName) ? uploadFileName : file.getName();
|
||||||
log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, multipartFileName);
|
log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, multipartFileName);
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
@@ -132,7 +133,7 @@ public class LsfxAnalysisClient {
|
|||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|
||||||
UploadFileResponse response = httpUtil.uploadFile(url, params, headers, UploadFileResponse.class);
|
UploadFileResponse response = httpUtil.uploadFile(caller, url, params, headers, UploadFileResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
if (response != null && response.getData() != null) {
|
if (response != null && response.getData() != null) {
|
||||||
@@ -155,7 +156,7 @@ public class LsfxAnalysisClient {
|
|||||||
/**
|
/**
|
||||||
* 拉取行内流水
|
* 拉取行内流水
|
||||||
*/
|
*/
|
||||||
public FetchInnerFlowResponse fetchInnerFlow(FetchInnerFlowRequest request) {
|
public FetchInnerFlowResponse fetchInnerFlow(CallerContext caller, FetchInnerFlowRequest request) {
|
||||||
log.info("【流水分析】拉取行内流水请求: groupId={}, customerNo={}", request.getGroupId(), request.getCustomerNo());
|
log.info("【流水分析】拉取行内流水请求: groupId={}, customerNo={}", request.getGroupId(), request.getCustomerNo());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -168,7 +169,7 @@ public class LsfxAnalysisClient {
|
|||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|
||||||
FetchInnerFlowResponse response = httpUtil.postFormData(url, params, headers, FetchInnerFlowResponse.class);
|
FetchInnerFlowResponse response = httpUtil.postFormData(caller, url, params, headers, FetchInnerFlowResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
if (response != null && response.getData() != null) {
|
if (response != null && response.getData() != null) {
|
||||||
@@ -190,7 +191,7 @@ public class LsfxAnalysisClient {
|
|||||||
/**
|
/**
|
||||||
* 检查文件解析状态
|
* 检查文件解析状态
|
||||||
*/
|
*/
|
||||||
public CheckParseStatusResponse checkParseStatus(Integer groupId, String inprogressList) {
|
public CheckParseStatusResponse checkParseStatus(CallerContext caller, Integer groupId, String inprogressList) {
|
||||||
log.info("【流水分析】检查文件解析状态: groupId={}, inprogressList={}", groupId, inprogressList);
|
log.info("【流水分析】检查文件解析状态: groupId={}, inprogressList={}", groupId, inprogressList);
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -205,7 +206,7 @@ public class LsfxAnalysisClient {
|
|||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|
||||||
CheckParseStatusResponse response = httpUtil.postFormData(url, params, headers, CheckParseStatusResponse.class);
|
CheckParseStatusResponse response = httpUtil.postFormData(caller, url, params, headers, CheckParseStatusResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
if (response != null && response.getData() != null) {
|
if (response != null && response.getData() != null) {
|
||||||
@@ -234,7 +235,7 @@ public class LsfxAnalysisClient {
|
|||||||
* @param request 请求参数(groupId, logId, pageNow, pageSize)
|
* @param request 请求参数(groupId, logId, pageNow, pageSize)
|
||||||
* @return 流水明细列表
|
* @return 流水明细列表
|
||||||
*/
|
*/
|
||||||
public GetBankStatementResponse getBankStatement(GetBankStatementRequest request) {
|
public GetBankStatementResponse getBankStatement(CallerContext caller, GetBankStatementRequest request) {
|
||||||
log.info("【流水分析】获取银行流水请求: groupId={}, logId={}, pageNow={}, pageSize={}",
|
log.info("【流水分析】获取银行流水请求: groupId={}, logId={}, pageNow={}, pageSize={}",
|
||||||
request.getGroupId(), request.getLogId(), request.getPageNow(), request.getPageSize());
|
request.getGroupId(), request.getLogId(), request.getPageNow(), request.getPageSize());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
@@ -248,7 +249,7 @@ public class LsfxAnalysisClient {
|
|||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|
||||||
GetBankStatementResponse response = httpUtil.postFormData(url, params, headers, GetBankStatementResponse.class);
|
GetBankStatementResponse response = httpUtil.postFormData(caller, url, params, headers, GetBankStatementResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
if (response != null && response.getData() != null) {
|
if (response != null && response.getData() != null) {
|
||||||
@@ -281,7 +282,7 @@ public class LsfxAnalysisClient {
|
|||||||
* @param request 请求参数(groupId必填, logId可选)
|
* @param request 请求参数(groupId必填, logId可选)
|
||||||
* @return 文件上传状态信息
|
* @return 文件上传状态信息
|
||||||
*/
|
*/
|
||||||
public GetFileUploadStatusResponse getFileUploadStatus(GetFileUploadStatusRequest request) {
|
public GetFileUploadStatusResponse getFileUploadStatus(CallerContext caller, GetFileUploadStatusRequest request) {
|
||||||
log.info("【流水分析】获取文件上传状态: groupId={}, logId={}",
|
log.info("【流水分析】获取文件上传状态: groupId={}, logId={}",
|
||||||
request.getGroupId(), request.getLogId());
|
request.getGroupId(), request.getLogId());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
@@ -299,7 +300,7 @@ public class LsfxAnalysisClient {
|
|||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|
||||||
GetFileUploadStatusResponse response = httpUtil.get(url, params, headers,
|
GetFileUploadStatusResponse response = httpUtil.get(caller, url, params, headers,
|
||||||
GetFileUploadStatusResponse.class);
|
GetFileUploadStatusResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
@@ -334,7 +335,7 @@ public class LsfxAnalysisClient {
|
|||||||
* @param request 请求参数(groupId, logIds, userId必填)
|
* @param request 请求参数(groupId, logIds, userId必填)
|
||||||
* @return 删除结果
|
* @return 删除结果
|
||||||
*/
|
*/
|
||||||
public DeleteFilesResponse deleteFiles(DeleteFilesRequest request) {
|
public DeleteFilesResponse deleteFiles(CallerContext caller, DeleteFilesRequest request) {
|
||||||
log.info("【流水分析】删除文件请求: groupId={}, logIds={}, userId={}",
|
log.info("【流水分析】删除文件请求: groupId={}, logIds={}, userId={}",
|
||||||
request.getGroupId(), Arrays.toString(request.getLogIds()), request.getUserId());
|
request.getGroupId(), Arrays.toString(request.getLogIds()), request.getUserId());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
@@ -351,7 +352,7 @@ public class LsfxAnalysisClient {
|
|||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|
||||||
DeleteFilesResponse response = httpUtil.postFormData(url, params, headers,
|
DeleteFilesResponse response = httpUtil.postFormData(caller, url, params, headers,
|
||||||
DeleteFilesResponse.class);
|
DeleteFilesResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.ruoyi.lsfx.client;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import com.ruoyi.lsfx.domain.response.XinhuaEnterpriseQueryResult;
|
||||||
|
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||||
|
import com.ruoyi.lsfx.util.HttpUtil;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** 新华社工商信息同步接口客户端。 */
|
||||||
|
@Component
|
||||||
|
public class XinhuaEnterpriseClient {
|
||||||
|
|
||||||
|
private static final int PLATFORM_SUCCESS_CODE = 10000;
|
||||||
|
private static final int BUSINESS_SUCCESS_STATUS = 1;
|
||||||
|
private static final int BUSINESS_SUCCESS_REASON_CODE = 200;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private HttpUtil httpUtil;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Value("${xinhua-enterprise.api.url}")
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
|
@Value("${xinhua-enterprise.api.org-code:999000}")
|
||||||
|
private String orgCode;
|
||||||
|
|
||||||
|
@Value("${xinhua-enterprise.api.run-type:1}")
|
||||||
|
private String runType;
|
||||||
|
|
||||||
|
public XinhuaEnterpriseQueryResult query(CallerContext caller, String enterpriseName) {
|
||||||
|
String normalizedName = normalize(enterpriseName);
|
||||||
|
if (!StringUtils.hasText(normalizedName)) {
|
||||||
|
throw new IllegalArgumentException("企业名称不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> params = new LinkedHashMap<>();
|
||||||
|
params.put("entName", normalizedName);
|
||||||
|
params.put("serialNum", buildSerialNum());
|
||||||
|
params.put("runType", runType);
|
||||||
|
params.put("orgCode", orgCode);
|
||||||
|
|
||||||
|
String rawJson = httpUtil.postUrlEncodedFormForString(caller, apiUrl, params, null);
|
||||||
|
JsonNode root = parse(rawJson);
|
||||||
|
JsonNode data = requireSuccess(root);
|
||||||
|
JsonNode profile = data.path("mappingOutputFields");
|
||||||
|
if (!profile.isObject() || profile.isEmpty()) {
|
||||||
|
throw new LsfxApiException("新华社工商接口返回工商信息为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
String responseName = normalize(profile.path("enterpriseName").asText(null));
|
||||||
|
if (!normalizedName.equals(responseName)) {
|
||||||
|
throw new LsfxApiException("新华社工商接口返回企业名称与查询名称不一致");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(normalize(profile.path("creditCode").asText(null)))) {
|
||||||
|
throw new LsfxApiException("新华社工商接口未返回统一社会信用代码");
|
||||||
|
}
|
||||||
|
return new XinhuaEnterpriseQueryResult(rawJson, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode parse(String rawJson) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readTree(rawJson);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new LsfxApiException("新华社工商接口响应不是合法JSON", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode requireSuccess(JsonNode root) {
|
||||||
|
if (!root.path("success").asBoolean(false)) {
|
||||||
|
throw new LsfxApiException(message(root, "新华社工商接口平台调用失败"));
|
||||||
|
}
|
||||||
|
if (root.path("code").asInt(Integer.MIN_VALUE) != PLATFORM_SUCCESS_CODE) {
|
||||||
|
throw new LsfxApiException("新华社工商接口平台状态码异常: " + root.path("code").asText());
|
||||||
|
}
|
||||||
|
JsonNode data = root.path("data");
|
||||||
|
if (!data.isObject()) {
|
||||||
|
throw new LsfxApiException("新华社工商接口返回data为空");
|
||||||
|
}
|
||||||
|
if (data.path("status").asInt(Integer.MIN_VALUE) != BUSINESS_SUCCESS_STATUS) {
|
||||||
|
throw new LsfxApiException(message(root, "新华社工商接口业务状态异常"));
|
||||||
|
}
|
||||||
|
if (data.path("reasonCode").asInt(Integer.MIN_VALUE) != BUSINESS_SUCCESS_REASON_CODE) {
|
||||||
|
throw new LsfxApiException(message(root, "新华社工商接口业务原因码异常"));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String message(JsonNode root, String defaultMessage) {
|
||||||
|
String reason = normalize(root.path("data").path("reasonMessage").asText(null));
|
||||||
|
return StringUtils.hasText(reason) ? reason : defaultMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSerialNum() {
|
||||||
|
return "CCDI_GS_" + System.currentTimeMillis() + "_" + IdUtils.fastSimpleUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalize(String value) {
|
||||||
|
return value == null ? null : value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ public class LsfxConstants {
|
|||||||
|
|
||||||
/** 数据渠道编码 */
|
/** 数据渠道编码 */
|
||||||
public static final String DATA_CHANNEL_ZJRCU = "ZJRCU";
|
public static final String DATA_CHANNEL_ZJRCU = "ZJRCU";
|
||||||
|
public static final String DATA_CHANNEL_JZL = "JZL";
|
||||||
|
|
||||||
/** 分析类型 */
|
/** 分析类型 */
|
||||||
public static final String ANALYSIS_TYPE = "-1";
|
public static final String ANALYSIS_TYPE = "-1";
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package com.ruoyi.lsfx.controller;
|
package com.ruoyi.lsfx.controller;
|
||||||
|
|
||||||
import com.ruoyi.common.annotation.Anonymous;
|
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@Tag(name = "征信解析接口测试", description = "用于测试征信解析接口")
|
@Tag(name = "征信解析接口测试", description = "用于测试征信解析接口")
|
||||||
@Anonymous
|
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/lsfx/credit")
|
@RequestMapping("/lsfx/credit")
|
||||||
public class CreditParseController {
|
public class CreditParseController {
|
||||||
@@ -37,7 +39,8 @@ public class CreditParseController {
|
|||||||
String actualModel = StringUtils.isBlank(model) ? DEFAULT_MODEL : model;
|
String actualModel = StringUtils.isBlank(model) ? DEFAULT_MODEL : model;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CreditParseInvokeResponse response = creditParseClient.parse(actualModel, remotePath);
|
CreditParseInvokeResponse response = creditParseClient.parse(
|
||||||
|
CallerContext.from(SecurityUtils.getLoginUser()), actualModel, remotePath);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
} catch (LsfxApiException e) {
|
} catch (LsfxApiException e) {
|
||||||
return AjaxResult.error(e.getMessage());
|
return AjaxResult.error(e.getMessage());
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package com.ruoyi.lsfx.controller;
|
package com.ruoyi.lsfx.controller;
|
||||||
|
|
||||||
import com.ruoyi.common.annotation.Anonymous;
|
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||||
import com.ruoyi.lsfx.constants.LsfxConstants;
|
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.request.DeleteFilesRequest;
|
import com.ruoyi.lsfx.domain.request.DeleteFilesRequest;
|
||||||
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
||||||
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
||||||
@@ -15,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ import java.nio.file.StandardCopyOption;
|
|||||||
* 流水分析平台接口测试控制器
|
* 流水分析平台接口测试控制器
|
||||||
*/
|
*/
|
||||||
@Tag(name = "流水分析平台接口测试", description = "用于测试流水分析平台的7个接口")
|
@Tag(name = "流水分析平台接口测试", description = "用于测试流水分析平台的7个接口")
|
||||||
@Anonymous
|
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/lsfx/test")
|
@RequestMapping("/lsfx/test")
|
||||||
public class LsfxTestController {
|
public class LsfxTestController {
|
||||||
@@ -61,7 +63,7 @@ public class LsfxTestController {
|
|||||||
request.setDepartmentCode(LsfxConstants.DEFAULT_DEPARTMENT_CODE);
|
request.setDepartmentCode(LsfxConstants.DEFAULT_DEPARTMENT_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetTokenResponse response = lsfxAnalysisClient.getToken(request);
|
GetTokenResponse response = lsfxAnalysisClient.getToken(currentCaller(), request);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ public class LsfxTestController {
|
|||||||
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
File convertedFile = tempFile.toFile();
|
File convertedFile = tempFile.toFile();
|
||||||
UploadFileResponse response = lsfxAnalysisClient.uploadFile(groupId, convertedFile);
|
UploadFileResponse response = lsfxAnalysisClient.uploadFile(currentCaller(), groupId, convertedFile);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return AjaxResult.error("文件转换失败:" + e.getMessage());
|
return AjaxResult.error("文件转换失败:" + e.getMessage());
|
||||||
@@ -134,7 +136,7 @@ public class LsfxTestController {
|
|||||||
request.setDataChannelCode(LsfxConstants.DEFAULT_DATA_CHANNEL_CODE);
|
request.setDataChannelCode(LsfxConstants.DEFAULT_DATA_CHANNEL_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchInnerFlowResponse response = lsfxAnalysisClient.fetchInnerFlow(request);
|
FetchInnerFlowResponse response = lsfxAnalysisClient.fetchInnerFlow(currentCaller(), request);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +154,7 @@ public class LsfxTestController {
|
|||||||
return AjaxResult.error("参数不完整:inprogressList为必填");
|
return AjaxResult.error("参数不完整:inprogressList为必填");
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckParseStatusResponse response = lsfxAnalysisClient.checkParseStatus(groupId, inprogressList);
|
CheckParseStatusResponse response = lsfxAnalysisClient.checkParseStatus(currentCaller(), groupId, inprogressList);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +176,7 @@ public class LsfxTestController {
|
|||||||
return AjaxResult.error("参数不完整:pageSize为必填且大于0");
|
return AjaxResult.error("参数不完整:pageSize为必填且大于0");
|
||||||
}
|
}
|
||||||
|
|
||||||
GetBankStatementResponse response = lsfxAnalysisClient.getBankStatement(request);
|
GetBankStatementResponse response = lsfxAnalysisClient.getBankStatement(currentCaller(), request);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +196,7 @@ public class LsfxTestController {
|
|||||||
request.setGroupId(groupId);
|
request.setGroupId(groupId);
|
||||||
request.setLogId(logId);
|
request.setLogId(logId);
|
||||||
|
|
||||||
GetFileUploadStatusResponse response = lsfxAnalysisClient.getFileUploadStatus(request);
|
GetFileUploadStatusResponse response = lsfxAnalysisClient.getFileUploadStatus(currentCaller(), request);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +215,11 @@ public class LsfxTestController {
|
|||||||
return AjaxResult.error("参数不完整:userId为必填");
|
return AjaxResult.error("参数不完整:userId为必填");
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteFilesResponse response = lsfxAnalysisClient.deleteFiles(request);
|
DeleteFilesResponse response = lsfxAnalysisClient.deleteFiles(currentCaller(), request);
|
||||||
return AjaxResult.success(response);
|
return AjaxResult.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CallerContext currentCaller() {
|
||||||
|
return CallerContext.from(SecurityUtils.getLoginUser());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.ruoyi.lsfx.domain;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务外部接口调用发起人快照。
|
||||||
|
*/
|
||||||
|
public record CallerContext(Long userId, String username) {
|
||||||
|
|
||||||
|
public CallerContext {
|
||||||
|
if (!StringUtils.hasText(username)) {
|
||||||
|
throw new IllegalArgumentException("接口调用账号不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CallerContext from(LoginUser loginUser) {
|
||||||
|
if (loginUser == null) {
|
||||||
|
throw new IllegalArgumentException("登录用户不能为空");
|
||||||
|
}
|
||||||
|
return new CallerContext(loginUser.getUserId(), loginUser.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CallerContext of(Long userId, String username) {
|
||||||
|
return new CallerContext(userId, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CallerContext system() {
|
||||||
|
return new CallerContext(null, "system");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public class FetchInnerFlowRequest {
|
|||||||
/** 客户身份证号 */
|
/** 客户身份证号 */
|
||||||
private String customerNo;
|
private String customerNo;
|
||||||
|
|
||||||
/** 数据渠道编码(固定值:ZJRCU) */
|
/** 数据渠道编码(ZJRCU-行内,JZL-金综) */
|
||||||
private String dataChannelCode;
|
private String dataChannelCode;
|
||||||
|
|
||||||
/** 发起请求的时间(格式:yyyyMMdd) */
|
/** 发起请求的时间(格式:yyyyMMdd) */
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class GetBankStatementResponse {
|
|||||||
private String leName;
|
private String leName;
|
||||||
|
|
||||||
/** 企业银行账号 */
|
/** 企业银行账号 */
|
||||||
private String accountMaskNo;
|
private String accountNo;
|
||||||
|
|
||||||
/** 账号日期ID */
|
/** 账号日期ID */
|
||||||
private Integer accountingDateId;
|
private Integer accountingDateId;
|
||||||
@@ -102,7 +102,7 @@ public class GetBankStatementResponse {
|
|||||||
private String customerName;
|
private String customerName;
|
||||||
|
|
||||||
/** 对手方账号 */
|
/** 对手方账号 */
|
||||||
private String customerAccountMaskNo;
|
private String customerAccountNo;
|
||||||
|
|
||||||
/** 对手方银行 */
|
/** 对手方银行 */
|
||||||
private String customerBank;
|
private String customerBank;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.ruoyi.lsfx.domain.response;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/** 新华社工商信息同步查询结果。 */
|
||||||
|
public record XinhuaEnterpriseQueryResult(String rawJson, JsonNode profile) {
|
||||||
|
}
|
||||||
@@ -1,30 +1,48 @@
|
|||||||
package com.ruoyi.lsfx.util;
|
package com.ruoyi.lsfx.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||||
|
import com.ruoyi.system.domain.SysApiLog;
|
||||||
|
import com.ruoyi.system.service.ISysApiLogService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.MediaTypeFactory;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP请求工具类
|
* 业务外部HTTP请求工具,统一保存完整调用日志。
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class HttpUtil {
|
public class HttpUtil {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
|
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
|
||||||
|
private static final String CALL_SUCCESS = "0";
|
||||||
|
private static final String CALL_FAILED = "1";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RestTemplate restTemplate;
|
private RestTemplate restTemplate;
|
||||||
@@ -32,6 +50,9 @@ public class HttpUtil {
|
|||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysApiLogService apiLogService;
|
||||||
|
|
||||||
public static org.springframework.core.io.Resource namedFileResource(File file, String filename) {
|
public static org.springframework.core.io.Resource namedFileResource(File file, String filename) {
|
||||||
return new NamedFileSystemResource(file, filename);
|
return new NamedFileSystemResource(file, filename);
|
||||||
}
|
}
|
||||||
@@ -50,299 +71,245 @@ public class HttpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public <T> T get(CallerContext caller, String url, Map<String, Object> params,
|
||||||
* 发送GET请求(带查询参数和请求头)
|
Map<String, String> headers, Class<T> responseType) {
|
||||||
* @param url 请求URL
|
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
|
||||||
* @param params 查询参数
|
if (params != null) {
|
||||||
* @param headers 请求头
|
params.forEach((key, value) -> {
|
||||||
* @param responseType 响应类型
|
if (value != null) {
|
||||||
* @return 响应对象
|
builder.queryParam(key, value);
|
||||||
*/
|
}
|
||||||
public <T> T get(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
|
});
|
||||||
|
}
|
||||||
|
String fullUrl = builder.toUriString();
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
return execute(caller, fullUrl, HttpMethod.GET, httpHeaders, params,
|
||||||
|
new HttpEntity<>(httpHeaders), responseType, "API返回数据为空", "GET请求异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(CallerContext caller, String url, Map<String, String> headers, Class<T> responseType) {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
return execute(caller, url, HttpMethod.GET, httpHeaders, null,
|
||||||
|
new HttpEntity<>(httpHeaders), responseType, "API返回数据为空", "网络请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T postJson(CallerContext caller, String url, Object request,
|
||||||
|
Map<String, String> headers, Class<T> responseType) {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
return execute(caller, url, HttpMethod.POST, httpHeaders, request,
|
||||||
|
new HttpEntity<>(request, httpHeaders), responseType, "API返回数据为空", "网络请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T postFormData(CallerContext caller, String url, Map<String, Object> params,
|
||||||
|
Map<String, String> headers, Class<T> responseType) {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
if (params != null) {
|
||||||
|
params.forEach(body::add);
|
||||||
|
}
|
||||||
|
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
|
||||||
|
new HttpEntity<>(body, httpHeaders), responseType, "API返回数据为空", "网络请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T postUrlEncodedForm(CallerContext caller, String url, Map<String, Object> params,
|
||||||
|
Map<String, String> headers, Class<T> responseType) {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
MultiValueMap<String, String> body = toUrlEncodedBody(params);
|
||||||
|
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
|
||||||
|
new HttpEntity<>(body, httpHeaders), responseType, "API返回数据为空", "网络请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String postUrlEncodedFormForString(CallerContext caller, String url,
|
||||||
|
Map<String, Object> params, Map<String, String> headers) {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
MultiValueMap<String, String> body = toUrlEncodedBody(params);
|
||||||
|
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
|
||||||
|
new HttpEntity<>(body, httpHeaders), String.class, "API返回数据为空", "网络请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T uploadFile(CallerContext caller, String url, Map<String, Object> params,
|
||||||
|
Map<String, String> headers, Class<T> responseType) {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
if (params != null) {
|
||||||
|
params.forEach((key, value) -> {
|
||||||
|
if (value instanceof File file) {
|
||||||
|
body.add(key, new FileSystemResource(file));
|
||||||
|
} else {
|
||||||
|
body.add(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
|
||||||
|
new HttpEntity<>(body, httpHeaders), responseType, "文件上传返回数据为空", "文件上传请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T execute(CallerContext caller, String url, HttpMethod method, HttpHeaders requestHeaders,
|
||||||
|
Object requestParams, HttpEntity<?> requestEntity, Class<T> responseType,
|
||||||
|
String emptyResponseMessage, String requestFailureMessage) {
|
||||||
|
if (caller == null) {
|
||||||
|
throw new IllegalArgumentException("接口调用用户不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
SysApiLog apiLog = createBaseApiLog(caller, url, method, startTime);
|
||||||
try {
|
try {
|
||||||
// 构建URL with查询参数
|
captureRequest(apiLog, requestHeaders, requestParams);
|
||||||
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
|
|
||||||
if (params != null && !params.isEmpty()) {
|
|
||||||
params.forEach((key, value) -> {
|
|
||||||
if (value != null) {
|
|
||||||
builder.queryParam(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String fullUrl = builder.toUriString();
|
|
||||||
log.debug("【HTTP GET】请求URL: {}", fullUrl);
|
|
||||||
|
|
||||||
// 创建请求头
|
|
||||||
HttpHeaders httpHeaders = new HttpHeaders();
|
|
||||||
if (headers != null) {
|
|
||||||
headers.forEach(httpHeaders::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建请求实体
|
|
||||||
HttpEntity<String> entity = new HttpEntity<>(httpHeaders);
|
|
||||||
|
|
||||||
// 执行GET请求
|
|
||||||
ResponseEntity<String> response = restTemplate.exchange(
|
|
||||||
fullUrl,
|
|
||||||
HttpMethod.GET,
|
|
||||||
entity,
|
|
||||||
String.class
|
|
||||||
);
|
|
||||||
|
|
||||||
log.debug("【HTTP GET】响应状态: {}", response.getStatusCode());
|
|
||||||
log.debug("【HTTP GET】响应内容: {}", response.getBody());
|
|
||||||
|
|
||||||
// 解析响应
|
|
||||||
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
|
|
||||||
return objectMapper.readValue(response.getBody(), responseType);
|
|
||||||
} else {
|
|
||||||
throw new LsfxApiException("GET请求失败: " + response.getStatusCode());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("【HTTP GET】请求异常: url={}, error={}", url, e.getMessage(), e);
|
log.error("接口日志请求快照构建失败: method={}, url={}", method, url, e);
|
||||||
throw new LsfxApiException("GET请求异常: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送GET请求(带请求头)
|
|
||||||
* @param url 请求URL
|
|
||||||
* @param headers 请求头
|
|
||||||
* @param responseType 响应类型
|
|
||||||
* @return 响应对象
|
|
||||||
*/
|
|
||||||
public <T> T get(String url, Map<String, String> headers, Class<T> responseType) {
|
|
||||||
try {
|
try {
|
||||||
HttpHeaders httpHeaders = createHeaders(headers);
|
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
|
||||||
HttpEntity<Void> requestEntity = new HttpEntity<>(httpHeaders);
|
captureResponse(apiLog, response);
|
||||||
|
|
||||||
ResponseEntity<T> response = restTemplate.exchange(
|
|
||||||
url, HttpMethod.GET, requestEntity, responseType
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||||
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode());
|
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode().value());
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(response.getBody())) {
|
||||||
|
throw new LsfxApiException(emptyResponseMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
T body = response.getBody();
|
T result = parseResponse(response.getBody(), responseType);
|
||||||
if (body == null) {
|
apiLog.setCallStatus(CALL_SUCCESS);
|
||||||
throw new LsfxApiException("API返回数据为空");
|
return result;
|
||||||
}
|
} catch (RestClientResponseException e) {
|
||||||
|
apiLog.setResponseStatus(e.getStatusCode().value());
|
||||||
return body;
|
apiLog.setResponseHeaders(safeSerialize(e.getResponseHeaders()));
|
||||||
|
apiLog.setResponseBody(e.getResponseBodyAsString());
|
||||||
|
apiLog.setErrorMsg(formatException(e));
|
||||||
|
throw new LsfxApiException(requestFailureMessage + ": " + e.getMessage(), e);
|
||||||
|
} catch (LsfxApiException e) {
|
||||||
|
apiLog.setErrorMsg(formatException(e));
|
||||||
|
throw e;
|
||||||
} catch (RestClientException e) {
|
} catch (RestClientException e) {
|
||||||
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
|
apiLog.setErrorMsg(formatException(e));
|
||||||
|
throw new LsfxApiException(requestFailureMessage + ": " + e.getMessage(), e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
apiLog.setErrorMsg(formatException(e));
|
||||||
|
throw new LsfxApiException("响应解析失败: " + e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
apiLog.setCostTime(System.currentTimeMillis() - startTime);
|
||||||
|
persistApiLog(apiLog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private SysApiLog createBaseApiLog(CallerContext caller, String url, HttpMethod method, long startTime) {
|
||||||
* 发送POST请求(JSON格式,带请求头)
|
SysApiLog apiLog = new SysApiLog();
|
||||||
* @param url 请求URL
|
apiLog.setCallerUserId(caller.userId());
|
||||||
* @param request 请求对象
|
apiLog.setCallerUsername(caller.username());
|
||||||
* @param headers 请求头
|
apiLog.setApiUrl(url);
|
||||||
* @param responseType 响应类型
|
apiLog.setHttpMethod(method.name());
|
||||||
* @return 响应对象
|
apiLog.setCallStatus(CALL_FAILED);
|
||||||
*/
|
apiLog.setCallTime(new Date(startTime));
|
||||||
public <T> T postJson(String url, Object request, Map<String, String> headers, Class<T> responseType) {
|
return apiLog;
|
||||||
try {
|
|
||||||
HttpHeaders httpHeaders = createHeaders(headers);
|
|
||||||
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
|
|
||||||
HttpEntity<Object> requestEntity = new HttpEntity<>(request, httpHeaders);
|
|
||||||
|
|
||||||
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
T body = response.getBody();
|
|
||||||
if (body == null) {
|
|
||||||
throw new LsfxApiException("API返回数据为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
return body;
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void captureRequest(SysApiLog apiLog, HttpHeaders headers, Object params) {
|
||||||
* 发送POST请求(multipart/form-data格式,带请求头)
|
apiLog.setContentType(headers.getContentType() == null ? null : headers.getContentType().toString());
|
||||||
* 用于提交表单数据(非文件上传场景)
|
apiLog.setRequestHeaders(safeSerialize(headers));
|
||||||
* @param url 请求URL
|
apiLog.setRequestParams(safeSerialize(normalizeValue(params)));
|
||||||
* @param params 表单参数
|
|
||||||
* @param headers 请求头
|
|
||||||
* @param responseType 响应类型
|
|
||||||
* @return 响应对象
|
|
||||||
*/
|
|
||||||
public <T> T postFormData(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
|
|
||||||
try {
|
|
||||||
HttpHeaders httpHeaders = createHeaders(headers);
|
|
||||||
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
||||||
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
if (params != null) {
|
|
||||||
params.forEach(body::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, httpHeaders);
|
|
||||||
|
|
||||||
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
T responseBody = response.getBody();
|
|
||||||
if (responseBody == null) {
|
|
||||||
throw new LsfxApiException("API返回数据为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseBody;
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void captureResponse(SysApiLog apiLog, ResponseEntity<String> response) {
|
||||||
* 发送POST请求(application/x-www-form-urlencoded格式,带请求头)
|
apiLog.setResponseStatus(response.getStatusCode().value());
|
||||||
* @param url 请求URL
|
apiLog.setResponseHeaders(safeSerialize(response.getHeaders()));
|
||||||
* @param params 表单参数
|
apiLog.setResponseBody(response.getBody());
|
||||||
* @param headers 请求头
|
|
||||||
* @param responseType 响应类型
|
|
||||||
* @return 响应对象
|
|
||||||
*/
|
|
||||||
public <T> T postUrlEncodedForm(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
|
|
||||||
try {
|
|
||||||
HttpHeaders httpHeaders = createHeaders(headers);
|
|
||||||
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
|
|
||||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
|
||||||
if (params != null) {
|
|
||||||
params.forEach((key, value) -> {
|
|
||||||
if (value != null) {
|
|
||||||
body.add(key, value.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, httpHeaders);
|
|
||||||
|
|
||||||
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
T responseBody = response.getBody();
|
|
||||||
if (responseBody == null) {
|
|
||||||
throw new LsfxApiException("API返回数据为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseBody;
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@SuppressWarnings("unchecked")
|
||||||
* 发送POST请求(application/x-www-form-urlencoded格式)并返回原始JSON字符串
|
private <T> T parseResponse(String responseBody, Class<T> responseType) throws Exception {
|
||||||
* @param url 请求URL
|
if (String.class.equals(responseType)) {
|
||||||
* @param params 表单参数
|
return (T) responseBody;
|
||||||
* @param headers 请求头
|
|
||||||
* @return 原始响应内容
|
|
||||||
*/
|
|
||||||
public String postUrlEncodedFormForString(String url, Map<String, Object> params, Map<String, String> headers) {
|
|
||||||
try {
|
|
||||||
HttpHeaders httpHeaders = createHeaders(headers);
|
|
||||||
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
|
|
||||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
|
||||||
if (params != null) {
|
|
||||||
params.forEach((key, value) -> {
|
|
||||||
if (value != null) {
|
|
||||||
body.add(key, value.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, httpHeaders);
|
|
||||||
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
String responseBody = response.getBody();
|
|
||||||
if (responseBody == null) {
|
|
||||||
throw new LsfxApiException("API返回数据为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseBody;
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
return objectMapper.readValue(responseBody, responseType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private MultiValueMap<String, String> toUrlEncodedBody(Map<String, Object> params) {
|
||||||
* 上传文件(Multipart格式)
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
* @param url 请求URL
|
if (params != null) {
|
||||||
* @param params 参数(包含文件)
|
params.forEach((key, value) -> {
|
||||||
* @param headers 请求头
|
if (value != null) {
|
||||||
* @param responseType 响应类型
|
body.add(key, value.toString());
|
||||||
* @return 响应对象
|
}
|
||||||
*/
|
});
|
||||||
public <T> T uploadFile(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
|
|
||||||
try {
|
|
||||||
HttpHeaders httpHeaders = createHeaders(headers);
|
|
||||||
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
||||||
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
if (params != null) {
|
|
||||||
params.forEach((key, value) -> {
|
|
||||||
// 如果是File对象,包装为FileSystemResource
|
|
||||||
if (value instanceof File) {
|
|
||||||
File file = (File) value;
|
|
||||||
body.add(key, new FileSystemResource(file));
|
|
||||||
} else if (value instanceof org.springframework.core.io.Resource) {
|
|
||||||
body.add(key, value);
|
|
||||||
} else {
|
|
||||||
body.add(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, httpHeaders);
|
|
||||||
|
|
||||||
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
|
|
||||||
|
|
||||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
|
||||||
throw new LsfxApiException("文件上传失败,HTTP状态码: " + response.getStatusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
T responseBody = response.getBody();
|
|
||||||
if (responseBody == null) {
|
|
||||||
throw new LsfxApiException("文件上传返回数据为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseBody;
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
throw new LsfxApiException("文件上传请求失败: " + e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建请求头
|
|
||||||
* @param headers 请求头Map
|
|
||||||
* @return HttpHeaders对象
|
|
||||||
*/
|
|
||||||
private HttpHeaders createHeaders(Map<String, String> headers) {
|
private HttpHeaders createHeaders(Map<String, String> headers) {
|
||||||
HttpHeaders httpHeaders = new HttpHeaders();
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
if (headers != null && !headers.isEmpty()) {
|
if (headers != null) {
|
||||||
headers.forEach(httpHeaders::set);
|
headers.forEach(httpHeaders::set);
|
||||||
}
|
}
|
||||||
return httpHeaders;
|
return httpHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object normalizeValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof File file) {
|
||||||
|
return fileMetadata(file.getName(), file.length());
|
||||||
|
}
|
||||||
|
if (value instanceof FileSystemResource resource) {
|
||||||
|
return fileMetadata(resource.getFilename(), resource.getFile().length());
|
||||||
|
}
|
||||||
|
if (value instanceof org.springframework.core.io.Resource resource) {
|
||||||
|
return fileMetadata(resource.getFilename(), null);
|
||||||
|
}
|
||||||
|
if (value instanceof Map<?, ?> map) {
|
||||||
|
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||||
|
map.forEach((key, item) -> normalized.put(String.valueOf(key), normalizeValue(item)));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
if (value instanceof Iterable<?> iterable) {
|
||||||
|
List<Object> normalized = new ArrayList<>();
|
||||||
|
iterable.forEach(item -> normalized.add(normalizeValue(item)));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
if (value.getClass().isArray()) {
|
||||||
|
List<Object> normalized = new ArrayList<>();
|
||||||
|
for (int i = 0; i < Array.getLength(value); i++) {
|
||||||
|
normalized.add(normalizeValue(Array.get(value, i)));
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> fileMetadata(String filename, Long size) {
|
||||||
|
Map<String, Object> metadata = new LinkedHashMap<>();
|
||||||
|
metadata.put("filename", filename);
|
||||||
|
metadata.put("size", size);
|
||||||
|
metadata.put("contentType", MediaTypeFactory.getMediaType(filename == null ? "" : filename)
|
||||||
|
.map(MediaType::toString).orElse(null));
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeSerialize(Object value) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("接口日志序列化失败", e);
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatException(Exception exception) {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
exception.printStackTrace(new PrintWriter(writer));
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistApiLog(SysApiLog apiLog) {
|
||||||
|
try {
|
||||||
|
apiLogService.recordApiLog(apiLog);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("接口日志入库失败: method={}, url={}", apiLog.getHttpMethod(), apiLog.getApiUrl(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.ruoyi.lsfx.client;
|
||||||
|
|
||||||
|
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import com.ruoyi.lsfx.domain.response.UploadFileResponse;
|
||||||
|
import com.ruoyi.lsfx.util.HttpUtil;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class LsfxAnalysisClientTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpUtil httpUtil;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private LsfxAnalysisClient client;
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void uploadFile_shouldPassOriginalFilenameToMultipartResource() throws Exception {
|
||||||
|
ReflectionTestUtils.setField(client, "baseUrl", "http://lsfx");
|
||||||
|
ReflectionTestUtils.setField(client, "uploadFileEndpoint", "/upload");
|
||||||
|
ReflectionTestUtils.setField(client, "clientId", "client-1");
|
||||||
|
|
||||||
|
Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
|
||||||
|
Files.writeString(tempFile, "content");
|
||||||
|
|
||||||
|
UploadFileResponse response = new UploadFileResponse();
|
||||||
|
response.setData(new UploadFileResponse.UploadData());
|
||||||
|
|
||||||
|
ArgumentCaptor<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass(Map.class);
|
||||||
|
ArgumentCaptor<Map<String, String>> headersCaptor = ArgumentCaptor.forClass(Map.class);
|
||||||
|
CallerContext caller = CallerContext.of(7L, "tester");
|
||||||
|
when(httpUtil.uploadFile(eq(caller), eq("http://lsfx/upload"), paramsCaptor.capture(), headersCaptor.capture(), eq(UploadFileResponse.class)))
|
||||||
|
.thenReturn(response);
|
||||||
|
|
||||||
|
client.uploadFile(caller, 200, tempFile.toFile(), "银行流水A.xlsx");
|
||||||
|
|
||||||
|
assertEquals(200, paramsCaptor.getValue().get("groupId"));
|
||||||
|
Resource filePart = assertInstanceOf(Resource.class, paramsCaptor.getValue().get("files"));
|
||||||
|
assertEquals("银行流水A.xlsx", filePart.getFilename());
|
||||||
|
assertEquals("client-1", headersCaptor.getValue().get(LsfxConstants.HEADER_CLIENT_ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,14 @@ package com.ruoyi.lsfx.controller;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||||
import com.ruoyi.lsfx.util.HttpUtil;
|
import com.ruoyi.lsfx.util.HttpUtil;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -13,10 +17,14 @@ import org.mockito.InjectMocks;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
@@ -24,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertSame;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -34,12 +43,19 @@ import static org.mockito.Mockito.when;
|
|||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class CreditParseControllerTest {
|
class CreditParseControllerTest {
|
||||||
|
|
||||||
|
private static final CallerContext CALLER = CallerContext.of(7L, "tester");
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CreditParseClient client;
|
private CreditParseClient client;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private CreditParseController controller;
|
private CreditParseController controller;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void clearSecurityContext() {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_shouldRejectBlankRemotePath() {
|
void parse_shouldRejectBlankRemotePath() {
|
||||||
AjaxResult result = controller.parse(null, null);
|
AjaxResult result = controller.parse(null, null);
|
||||||
@@ -48,12 +64,13 @@ class CreditParseControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldUseDefaultModelWhenMissing() {
|
void shouldUseDefaultModelWhenMissing() {
|
||||||
|
setLoginUser(CALLER.userId(), CALLER.username());
|
||||||
CreditParseInvokeResponse response = new CreditParseInvokeResponse();
|
CreditParseInvokeResponse response = new CreditParseInvokeResponse();
|
||||||
response.setSuccess(true);
|
response.setSuccess(true);
|
||||||
response.setCode(10000);
|
response.setCode(10000);
|
||||||
|
|
||||||
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
||||||
when(client.parse(eq("LXCUSTALL"), eq(remotePath))).thenReturn(response);
|
when(client.parse(eq(CALLER), eq("LXCUSTALL"), eq(remotePath))).thenReturn(response);
|
||||||
|
|
||||||
AjaxResult result = controller.parse(remotePath, null);
|
AjaxResult result = controller.parse(remotePath, null);
|
||||||
|
|
||||||
@@ -63,7 +80,8 @@ class CreditParseControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnAjaxErrorWhenClientThrows() {
|
void shouldReturnAjaxErrorWhenClientThrows() {
|
||||||
when(client.parse(anyString(), anyString()))
|
setLoginUser(CALLER.userId(), CALLER.username());
|
||||||
|
when(client.parse(any(CallerContext.class), anyString(), anyString()))
|
||||||
.thenThrow(new LsfxApiException("超时"));
|
.thenThrow(new LsfxApiException("超时"));
|
||||||
|
|
||||||
AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", null);
|
AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", null);
|
||||||
@@ -71,6 +89,16 @@ class CreditParseControllerTest {
|
|||||||
assertEquals(500, result.get("code"));
|
assertEquals(500, result.get("code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoginUser(Long userId, String username) {
|
||||||
|
SysUser user = new SysUser();
|
||||||
|
user.setUserName(username);
|
||||||
|
LoginUser loginUser = new LoginUser(user, Set.of("*:*:*"));
|
||||||
|
loginUser.setUserId(userId);
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
void creditParseClient_shouldInitiateAndQueryResultWithSameSerialNum() throws Exception {
|
void creditParseClient_shouldInitiateAndQueryResultWithSameSerialNum() throws Exception {
|
||||||
@@ -88,18 +116,20 @@ class CreditParseControllerTest {
|
|||||||
String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"},\"lx_debt\":{\"uncle_bank_house_bal\":\"1\"},\"lx_publictype\":{\"civil_cnt\":1}}";
|
String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"},\"lx_debt\":{\"uncle_bank_house_bal\":\"1\"},\"lx_publictype\":{\"civil_cnt\":1}}";
|
||||||
|
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn(initiateSuccessResponse());
|
)).thenReturn(initiateSuccessResponse());
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn(resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE"));
|
)).thenReturn(resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE"));
|
||||||
|
|
||||||
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
||||||
CreditParseInvokeResponse actual = parseClient.parse(remotePath);
|
CreditParseInvokeResponse actual = parseClient.parse(CALLER, remotePath);
|
||||||
|
|
||||||
assertEquals(true, actual.getSuccess());
|
assertEquals(true, actual.getSuccess());
|
||||||
assertEquals(10000, actual.getCode());
|
assertEquals(10000, actual.getCode());
|
||||||
@@ -107,12 +137,14 @@ class CreditParseControllerTest {
|
|||||||
.getPayload().getLxHeader().get("query_cert_no"));
|
.getPayload().getLxHeader().get("query_cert_no"));
|
||||||
ArgumentCaptor<Map<String, Object>> initiateParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
ArgumentCaptor<Map<String, Object>> initiateParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
||||||
verify(httpUtil).postUrlEncodedFormForString(
|
verify(httpUtil).postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
initiateParamsCaptor.capture(),
|
initiateParamsCaptor.capture(),
|
||||||
isNull()
|
isNull()
|
||||||
);
|
);
|
||||||
ArgumentCaptor<Map<String, Object>> resultParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
ArgumentCaptor<Map<String, Object>> resultParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
||||||
verify(httpUtil).postUrlEncodedFormForString(
|
verify(httpUtil).postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
resultParamsCaptor.capture(),
|
resultParamsCaptor.capture(),
|
||||||
isNull()
|
isNull()
|
||||||
@@ -152,21 +184,24 @@ class CreditParseControllerTest {
|
|||||||
String resultResponse = resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE");
|
String resultResponse = resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE");
|
||||||
|
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn(initiateSuccessResponse());
|
)).thenReturn(initiateSuccessResponse());
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn(emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, resultResponse);
|
)).thenReturn(emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, resultResponse);
|
||||||
|
|
||||||
CreditParseInvokeResponse actual = parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html");
|
CreditParseInvokeResponse actual = parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html");
|
||||||
|
|
||||||
assertEquals("330101199001010011", actual.getData().getMappingOutputFields()
|
assertEquals("330101199001010011", actual.getData().getMappingOutputFields()
|
||||||
.getPayload().getLxHeader().get("query_cert_no"));
|
.getPayload().getLxHeader().get("query_cert_no"));
|
||||||
verify(httpUtil, times(5)).postUrlEncodedFormForString(
|
verify(httpUtil, times(5)).postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
@@ -188,18 +223,20 @@ class CreditParseControllerTest {
|
|||||||
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
||||||
|
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn(initiateSuccessResponse());
|
)).thenReturn(initiateSuccessResponse());
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn("{\"success\":true,\"code\":99999,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\"}}}");
|
)).thenReturn("{\"success\":true,\"code\":99999,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\"}}}");
|
||||||
|
|
||||||
LsfxApiException exception = assertThrows(LsfxApiException.class,
|
LsfxApiException exception = assertThrows(LsfxApiException.class,
|
||||||
() -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));
|
() -> parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html"));
|
||||||
|
|
||||||
assertTrue(exception.getMessage().contains("平台状态码异常"));
|
assertTrue(exception.getMessage().contains("平台状态码异常"));
|
||||||
}
|
}
|
||||||
@@ -218,18 +255,20 @@ class CreditParseControllerTest {
|
|||||||
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
||||||
|
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn(initiateSuccessResponse());
|
)).thenReturn(initiateSuccessResponse());
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"reasonMessage\":\"解析失败\",\"reasonCode\":500,\"status\":0,\"mappingOutputFields\":{\"message\":\"结果异常\"}}}");
|
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"reasonMessage\":\"解析失败\",\"reasonCode\":500,\"status\":0,\"mappingOutputFields\":{\"message\":\"结果异常\"}}}");
|
||||||
|
|
||||||
LsfxApiException exception = assertThrows(LsfxApiException.class,
|
LsfxApiException exception = assertThrows(LsfxApiException.class,
|
||||||
() -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));
|
() -> parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html"));
|
||||||
|
|
||||||
assertTrue(exception.getMessage().contains("解析失败"));
|
assertTrue(exception.getMessage().contains("解析失败"));
|
||||||
}
|
}
|
||||||
@@ -248,16 +287,18 @@ class CreditParseControllerTest {
|
|||||||
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
||||||
|
|
||||||
when(httpUtil.postUrlEncodedFormForString(
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"文件写入失败\"},\"reasonMessage\":\"文件写入失败\",\"reasonCode\":500,\"status\":0}}");
|
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"文件写入失败\"},\"reasonMessage\":\"文件写入失败\",\"reasonCode\":500,\"status\":0}}");
|
||||||
|
|
||||||
LsfxApiException exception = assertThrows(LsfxApiException.class,
|
LsfxApiException exception = assertThrows(LsfxApiException.class,
|
||||||
() -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));
|
() -> parseClient.parse(CALLER, "http://127.0.0.1:62318/profile/credit-html/a.html"));
|
||||||
|
|
||||||
assertTrue(exception.getMessage().contains("文件写入失败"));
|
assertTrue(exception.getMessage().contains("文件写入失败"));
|
||||||
verify(httpUtil, times(0)).postUrlEncodedFormForString(
|
verify(httpUtil, times(0)).postUrlEncodedFormForString(
|
||||||
|
eq(CALLER),
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull()
|
isNull()
|
||||||
|
|||||||
271
ccdi-lsfx/src/test/java/com/ruoyi/lsfx/util/HttpUtilTest.java
Normal file
271
ccdi-lsfx/src/test/java/com/ruoyi/lsfx/util/HttpUtilTest.java
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package com.ruoyi.lsfx.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||||
|
import com.ruoyi.system.domain.SysApiLog;
|
||||||
|
import com.ruoyi.system.service.ISysApiLogService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class HttpUtilTest {
|
||||||
|
|
||||||
|
private static final CallerContext CALLER = CallerContext.of(7L, "tester");
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ISysApiLogService apiLogService;
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
private HttpUtil httpUtil;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
httpUtil = new HttpUtil();
|
||||||
|
ReflectionTestUtils.setField(httpUtil, "restTemplate", restTemplate);
|
||||||
|
ReflectionTestUtils.setField(httpUtil, "objectMapper", new ObjectMapper());
|
||||||
|
ReflectionTestUtils.setField(httpUtil, "apiLogService", apiLogService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void uploadFile_shouldUseExplicitFilenameAndOnlyPersistFileMetadata() throws Exception {
|
||||||
|
Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
|
||||||
|
Files.writeString(tempFile, "binary-content-must-not-enter-log");
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpEntity<?>> requestCaptor = httpEntityCaptor();
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/upload"), eq(HttpMethod.POST),
|
||||||
|
requestCaptor.capture(), eq(String.class))).thenReturn(ResponseEntity.ok("ok"));
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("groupId", 200);
|
||||||
|
params.put("files", HttpUtil.namedFileResource(tempFile.toFile(), "银行流水A.xlsx"));
|
||||||
|
|
||||||
|
assertEquals("ok", httpUtil.uploadFile(CALLER, "http://lsfx/upload", params, null, String.class));
|
||||||
|
|
||||||
|
MultiValueMap<String, Object> body = (MultiValueMap<String, Object>) requestCaptor.getValue().getBody();
|
||||||
|
Resource resource = assertInstanceOf(Resource.class, body.getFirst("files"));
|
||||||
|
assertEquals("银行流水A.xlsx", resource.getFilename());
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
SysApiLog saved = logCaptor.getValue();
|
||||||
|
assertEquals(7L, saved.getCallerUserId());
|
||||||
|
assertEquals("tester", saved.getCallerUsername());
|
||||||
|
assertTrue(saved.getRequestParams().contains("银行流水A.xlsx"));
|
||||||
|
assertTrue(saved.getRequestParams().contains("\"size\":" + Files.size(tempFile)));
|
||||||
|
assertFalse(saved.getRequestParams().contains("binary-content-must-not-enter-log"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void non2xx_shouldPersistOriginalStatusHeadersAndBody() {
|
||||||
|
HttpHeaders responseHeaders = new HttpHeaders();
|
||||||
|
responseHeaders.add("X-Trace-Id", "trace-1");
|
||||||
|
HttpClientErrorException error = HttpClientErrorException.create(
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
"Bad Request",
|
||||||
|
responseHeaders,
|
||||||
|
"{\"error\":\"参数错误\"}".getBytes(StandardCharsets.UTF_8),
|
||||||
|
StandardCharsets.UTF_8
|
||||||
|
);
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/fail"), eq(HttpMethod.POST),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenThrow(error);
|
||||||
|
|
||||||
|
assertThrows(LsfxApiException.class, () ->
|
||||||
|
httpUtil.postJson(CALLER, "http://lsfx/fail", Map.of("token", "raw-token"), null, String.class));
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
SysApiLog saved = logCaptor.getValue();
|
||||||
|
assertEquals(400, saved.getResponseStatus());
|
||||||
|
assertEquals("{\"error\":\"参数错误\"}", saved.getResponseBody());
|
||||||
|
assertTrue(saved.getResponseHeaders().contains("trace-1"));
|
||||||
|
assertTrue(saved.getRequestParams().contains("raw-token"));
|
||||||
|
assertEquals("1", saved.getCallStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void logPersistenceFailure_shouldNotChangeSuccessfulBusinessResult() {
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/success"), eq(HttpMethod.GET),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok("ok"));
|
||||||
|
doThrow(new RuntimeException("db unavailable")).when(apiLogService).recordApiLog(any(SysApiLog.class));
|
||||||
|
|
||||||
|
assertEquals("ok", httpUtil.get(CALLER, "http://lsfx/success", (Map<String, String>) null, String.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void networkFailure_shouldPersistExceptionWithoutResponseStatus() {
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/network-error"), eq(HttpMethod.GET),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenThrow(new ResourceAccessException("connection refused"));
|
||||||
|
|
||||||
|
assertThrows(LsfxApiException.class, () ->
|
||||||
|
httpUtil.get(CALLER, "http://lsfx/network-error", (Map<String, String>) null, String.class));
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
SysApiLog saved = logCaptor.getValue();
|
||||||
|
assertEquals(null, saved.getResponseStatus());
|
||||||
|
assertTrue(saved.getErrorMsg().contains("connection refused"));
|
||||||
|
assertEquals("1", saved.getCallStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void emptyResponse_shouldPersistRawResponseBeforeFailing() {
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/empty"), eq(HttpMethod.GET),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok(" "));
|
||||||
|
|
||||||
|
assertThrows(LsfxApiException.class, () ->
|
||||||
|
httpUtil.get(CALLER, "http://lsfx/empty", (Map<String, String>) null, String.class));
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
SysApiLog saved = logCaptor.getValue();
|
||||||
|
assertEquals(200, saved.getResponseStatus());
|
||||||
|
assertEquals(" ", saved.getResponseBody());
|
||||||
|
assertTrue(saved.getErrorMsg().contains("API返回数据为空"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseFailure_shouldPersistOriginalResponseBody() {
|
||||||
|
String rawBody = "{\"value\":\"not-an-integer\",\"unknown\":true}";
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/parse-error"), eq(HttpMethod.GET),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok(rawBody));
|
||||||
|
|
||||||
|
assertThrows(LsfxApiException.class, () ->
|
||||||
|
httpUtil.get(CALLER, "http://lsfx/parse-error", (Map<String, String>) null, Integer.class));
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
SysApiLog saved = logCaptor.getValue();
|
||||||
|
assertEquals(rawBody, saved.getResponseBody());
|
||||||
|
assertEquals("1", saved.getCallStatus());
|
||||||
|
assertTrue(saved.getErrorMsg() != null && !saved.getErrorMsg().isBlank());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void successfulResponse_shouldPersistCompleteRawBodyIncludingUnknownFields() {
|
||||||
|
String rawBody = "{\"known\":1,\"unknown\":{\"nested\":true}}";
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/raw"), eq(HttpMethod.GET),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok(rawBody));
|
||||||
|
|
||||||
|
Map<?, ?> result = httpUtil.get(CALLER, "http://lsfx/raw", (Map<String, String>) null, Map.class);
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, ((Map<?, ?>) result.get("unknown")).get("nested"));
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
assertEquals(rawBody, logCaptor.getValue().getResponseBody());
|
||||||
|
assertEquals("0", logCaptor.getValue().getCallStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requestSnapshotFailure_shouldNotPreventExternalCallOrLogging() {
|
||||||
|
Iterable<String> brokenSnapshotValue = () -> new Iterator<>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
throw new IllegalStateException("snapshot failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return "unused";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/snapshot-error"), eq(HttpMethod.POST),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok("ok"));
|
||||||
|
|
||||||
|
assertEquals("ok", httpUtil.postJson(CALLER, "http://lsfx/snapshot-error",
|
||||||
|
brokenSnapshotValue, null, String.class));
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
assertEquals("0", logCaptor.getValue().getCallStatus());
|
||||||
|
verify(restTemplate).exchange(eq("http://lsfx/snapshot-error"), eq(HttpMethod.POST),
|
||||||
|
any(HttpEntity.class), eq(String.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void allPublicRequestTypes_shouldUseUnifiedLoggingPath() {
|
||||||
|
when(restTemplate.exchange(any(String.class), any(HttpMethod.class),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok("ok"));
|
||||||
|
|
||||||
|
Map<String, Object> params = Map.of("value", "完整参数");
|
||||||
|
Map<String, String> headers = Map.of("client-id", "client-1");
|
||||||
|
assertEquals("ok", httpUtil.get(CALLER, "http://lsfx/get", params, headers, String.class));
|
||||||
|
assertEquals("ok", httpUtil.get(CALLER, "http://lsfx/get", headers, String.class));
|
||||||
|
assertEquals("ok", httpUtil.postJson(CALLER, "http://lsfx/json", params, headers, String.class));
|
||||||
|
assertEquals("ok", httpUtil.postFormData(CALLER, "http://lsfx/form-data", params, headers, String.class));
|
||||||
|
assertEquals("ok", httpUtil.postUrlEncodedForm(CALLER, "http://lsfx/form", params, headers, String.class));
|
||||||
|
assertEquals("ok", httpUtil.postUrlEncodedFormForString(CALLER, "http://lsfx/form-string", params, headers));
|
||||||
|
assertEquals("ok", httpUtil.uploadFile(CALLER, "http://lsfx/upload", params, headers, String.class));
|
||||||
|
|
||||||
|
verify(apiLogService, times(7)).recordApiLog(any(SysApiLog.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingCaller_shouldFailBeforeHttpRequest() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
|
httpUtil.postJson(null, "http://lsfx/json", Map.of(), null, String.class));
|
||||||
|
verify(restTemplate, never()).exchange(any(String.class), any(HttpMethod.class),
|
||||||
|
any(HttpEntity.class), eq(String.class));
|
||||||
|
verify(apiLogService, never()).recordApiLog(any(SysApiLog.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void systemCaller_shouldPersistNullUserIdAndSystemUsername() {
|
||||||
|
when(restTemplate.exchange(eq("http://lsfx/system"), eq(HttpMethod.GET),
|
||||||
|
any(HttpEntity.class), eq(String.class))).thenReturn(ResponseEntity.ok("ok"));
|
||||||
|
|
||||||
|
assertEquals("ok", httpUtil.get(CallerContext.system(), "http://lsfx/system",
|
||||||
|
(Map<String, String>) null, String.class));
|
||||||
|
|
||||||
|
ArgumentCaptor<SysApiLog> logCaptor = ArgumentCaptor.forClass(SysApiLog.class);
|
||||||
|
verify(apiLogService).recordApiLog(logCaptor.capture());
|
||||||
|
assertEquals(null, logCaptor.getValue().getCallerUserId());
|
||||||
|
assertEquals("system", logCaptor.getValue().getCallerUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
private ArgumentCaptor<HttpEntity<?>> httpEntityCaptor() {
|
||||||
|
return (ArgumentCaptor) ArgumentCaptor.forClass(HttpEntity.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
mock-maker-subclass
|
||||||
@@ -30,6 +30,13 @@
|
|||||||
<version>${ruoyi.version}</version>
|
<version>${ruoyi.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 企业工商信息统一查询能力 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ruoyi</groupId>
|
||||||
|
<artifactId>ccdi-info-collection</artifactId>
|
||||||
|
<version>${ruoyi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- lombok -->
|
<!-- lombok -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.controller;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
@@ -18,7 +19,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -56,6 +56,19 @@ public class CcdiBankStatementController extends BaseController {
|
|||||||
return getDataTable(result.getRecords(), result.getTotal());
|
return getDataTable(result.getRecords(), result.getTotal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询对手方聚合列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/counterparty/list")
|
||||||
|
@Operation(summary = "分页查询对手方聚合列表")
|
||||||
|
public TableDataInfo counterpartyList(CcdiBankStatementQueryDTO queryDTO) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||||
|
Page<CcdiBankStatementCounterpartyVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
|
||||||
|
Page<CcdiBankStatementCounterpartyVO> result = bankStatementService.selectCounterpartyPage(page, queryDTO);
|
||||||
|
return getDataTable(result.getRecords(), result.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目级筛选项
|
* 查询项目级筛选项
|
||||||
*/
|
*/
|
||||||
@@ -72,9 +85,13 @@ public class CcdiBankStatementController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/detail/{bankStatementId}")
|
@GetMapping("/detail/{bankStatementId}")
|
||||||
@Operation(summary = "查询流水详情")
|
@Operation(summary = "查询流水详情")
|
||||||
public AjaxResult getDetail(@PathVariable Long bankStatementId) {
|
public AjaxResult getDetail(@PathVariable Long bankStatementId, String modelCode, String suspiciousType) {
|
||||||
projectAccessService.assertCanReadByBankStatementId(bankStatementId);
|
projectAccessService.assertCanReadByBankStatementId(bankStatementId);
|
||||||
CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(bankStatementId);
|
CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(
|
||||||
|
bankStatementId,
|
||||||
|
modelCode,
|
||||||
|
suspiciousType
|
||||||
|
);
|
||||||
return AjaxResult.success(detail);
|
return AjaxResult.success(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +100,6 @@ public class CcdiBankStatementController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
@Operation(summary = "导出流水明细")
|
@Operation(summary = "导出流水明细")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:export')")
|
|
||||||
public void export(HttpServletResponse response, CcdiBankStatementQueryDTO queryDTO) {
|
public void export(HttpServletResponse response, CcdiBankStatementQueryDTO queryDTO) {
|
||||||
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
List<CcdiBankStatementExcel> list = bankStatementService.selectStatementListForExport(queryDTO);
|
List<CcdiBankStatementExcel> list = bankStatementService.selectStatementListForExport(queryDTO);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import com.ruoyi.common.core.page.PageDomain;
|
|||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.core.page.TableSupport;
|
import com.ruoyi.common.core.page.TableSupport;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -65,7 +67,7 @@ public class CcdiFileUploadController extends BaseController {
|
|||||||
return AjaxResult.error("单次最多上传100个文件");
|
return AjaxResult.error("单次最多上传100个文件");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验文件大小和格式
|
// 校验文件数量、空文件和大小,文件名业务规则统一在 Service 层处理
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
return AjaxResult.error("文件不能为空");
|
return AjaxResult.error("文件不能为空");
|
||||||
@@ -73,20 +75,11 @@ public class CcdiFileUploadController extends BaseController {
|
|||||||
if (file.getSize() > 50 * 1024 * 1024) {
|
if (file.getSize() > 50 * 1024 * 1024) {
|
||||||
return AjaxResult.error("文件 " + file.getOriginalFilename() + " 超过50MB限制");
|
return AjaxResult.error("文件 " + file.getOriginalFilename() + " 超过50MB限制");
|
||||||
}
|
}
|
||||||
String fileName = file.getOriginalFilename();
|
|
||||||
if (fileName == null || fileName.trim().isEmpty()) {
|
|
||||||
return AjaxResult.error("文件名不能为空");
|
|
||||||
}
|
|
||||||
String lowerFileName = fileName.toLowerCase();
|
|
||||||
if (!lowerFileName.endsWith(".xlsx") && !lowerFileName.endsWith(".csv")
|
|
||||||
&& !lowerFileName.endsWith(".pdf")) {
|
|
||||||
return AjaxResult.error("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, XLSX 文件");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String username = SecurityUtils.getUsername();
|
CallerContext caller = CallerContext.from(SecurityUtils.getLoginUser());
|
||||||
String batchId = fileUploadService.batchUploadFiles(projectId, files, username);
|
String batchId = fileUploadService.batchUploadFiles(projectId, files, caller);
|
||||||
return AjaxResult.success("上传任务已提交", batchId);
|
return AjaxResult.success("上传任务已提交", batchId);
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
log.warn("线程池已满,拒绝上传请求: projectId={}, fileCount={}", projectId, files.length);
|
log.warn("线程池已满,拒绝上传请求: projectId={}, fileCount={}", projectId, files.length);
|
||||||
@@ -112,10 +105,10 @@ public class CcdiFileUploadController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交拉取本行信息任务
|
* 提交拉取行内/金综流水任务
|
||||||
*/
|
*/
|
||||||
@PostMapping("/pull-bank-info")
|
@PostMapping("/pull-bank-info")
|
||||||
@Operation(summary = "拉取本行信息", description = "按身份证号批量提交拉取本行信息任务")
|
@Operation(summary = "拉取行内/金综流水", description = "按证件号码批量提交拉取行内/金综流水任务")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||||
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
|
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
|
||||||
if (dto == null || dto.getProjectId() == null) {
|
if (dto == null || dto.getProjectId() == null) {
|
||||||
@@ -125,19 +118,27 @@ public class CcdiFileUploadController extends BaseController {
|
|||||||
if (CollectionUtils.isEmpty(dto.getIdCards())) {
|
if (CollectionUtils.isEmpty(dto.getIdCards())) {
|
||||||
return AjaxResult.error("身份证号不能为空");
|
return AjaxResult.error("身份证号不能为空");
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(dto.getStartDate()) || !StringUtils.hasText(dto.getEndDate())) {
|
if (!StringUtils.hasText(dto.getDataChannelCode())) {
|
||||||
|
return AjaxResult.error("流水来源不能为空");
|
||||||
|
}
|
||||||
|
String dataChannelCode = dto.getDataChannelCode().trim().toUpperCase();
|
||||||
|
if (!LsfxConstants.DATA_CHANNEL_ZJRCU.equals(dataChannelCode)
|
||||||
|
&& !LsfxConstants.DATA_CHANNEL_JZL.equals(dataChannelCode)) {
|
||||||
|
return AjaxResult.error("流水来源不支持");
|
||||||
|
}
|
||||||
|
if (LsfxConstants.DATA_CHANNEL_ZJRCU.equals(dataChannelCode)
|
||||||
|
&& (!StringUtils.hasText(dto.getStartDate()) || !StringUtils.hasText(dto.getEndDate()))) {
|
||||||
return AjaxResult.error("开始日期和结束日期不能为空");
|
return AjaxResult.error("开始日期和结束日期不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long userId = SecurityUtils.getUserId();
|
CallerContext caller = CallerContext.from(SecurityUtils.getLoginUser());
|
||||||
String username = SecurityUtils.getUsername();
|
|
||||||
String batchId = fileUploadService.submitPullBankInfo(
|
String batchId = fileUploadService.submitPullBankInfo(
|
||||||
dto.getProjectId(),
|
dto.getProjectId(),
|
||||||
dto.getIdCards(),
|
dto.getIdCards(),
|
||||||
|
dataChannelCode,
|
||||||
dto.getStartDate(),
|
dto.getStartDate(),
|
||||||
dto.getEndDate(),
|
dto.getEndDate(),
|
||||||
userId,
|
caller
|
||||||
username
|
|
||||||
);
|
);
|
||||||
return AjaxResult.success("拉取任务已提交", batchId);
|
return AjaxResult.success("拉取任务已提交", batchId);
|
||||||
}
|
}
|
||||||
@@ -185,8 +186,8 @@ public class CcdiFileUploadController extends BaseController {
|
|||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||||
public AjaxResult deleteFile(@PathVariable Long id) {
|
public AjaxResult deleteFile(@PathVariable Long id) {
|
||||||
projectAccessService.assertCanOperateByFileRecordId(id);
|
projectAccessService.assertCanOperateByFileRecordId(id);
|
||||||
Long userId = SecurityUtils.getUserId();
|
CallerContext caller = CallerContext.from(SecurityUtils.getLoginUser());
|
||||||
String message = fileUploadService.deleteFileUploadRecord(id, userId);
|
String message = fileUploadService.deleteFileUploadRecord(id, caller);
|
||||||
return AjaxResult.success(message);
|
return AjaxResult.success(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -43,7 +44,7 @@ public class CcdiProjectController extends BaseController {
|
|||||||
@Operation(summary = "创建项目")
|
@Operation(summary = "创建项目")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
||||||
public AjaxResult createProject(@Validated @RequestBody CcdiProjectSaveDTO dto) {
|
public AjaxResult createProject(@Validated @RequestBody CcdiProjectSaveDTO dto) {
|
||||||
CcdiProjectVO vo = projectService.createProject(dto);
|
CcdiProjectVO vo = projectService.createProject(dto, CallerContext.from(SecurityUtils.getLoginUser()));
|
||||||
return AjaxResult.success("项目创建成功", vo);
|
return AjaxResult.success("项目创建成功", vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ public class CcdiProjectController extends BaseController {
|
|||||||
@Operation(summary = "导入历史项目")
|
@Operation(summary = "导入历史项目")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
|
||||||
public AjaxResult importFromHistory(@Validated @RequestBody CcdiProjectImportHistoryDTO dto) {
|
public AjaxResult importFromHistory(@Validated @RequestBody CcdiProjectImportHistoryDTO dto) {
|
||||||
CcdiProjectVO vo = projectService.importFromHistory(dto, SecurityUtils.getUsername());
|
CcdiProjectVO vo = projectService.importFromHistory(dto, CallerContext.from(SecurityUtils.getLoginUser()));
|
||||||
return AjaxResult.success("项目创建成功", vo);
|
return AjaxResult.success("项目创建成功", vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.ProjectCounterpartyRefreshDTO;
|
||||||
|
import com.ruoyi.ccdi.project.service.IProjectCounterpartyEnterpriseService;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/ccdi/project/counterparty-enterprise")
|
||||||
|
@Tag(name = "项目对手方工商信息")
|
||||||
|
public class CcdiProjectCounterpartyEnterpriseController {
|
||||||
|
@Resource
|
||||||
|
private IProjectCounterpartyEnterpriseService service;
|
||||||
|
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@Operation(summary = "查询本地项目对手方工商详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult detail(@RequestParam Long projectId, @RequestParam String counterpartyName) {
|
||||||
|
return AjaxResult.success(service.getDetail(projectId, counterpartyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
@Operation(summary = "重新查询单个项目对手方工商信息")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:counterpartyEnterprise:refresh')")
|
||||||
|
public AjaxResult refresh(@Valid @RequestBody ProjectCounterpartyRefreshDTO dto) {
|
||||||
|
return AjaxResult.success(service.refresh(CallerContext.from(SecurityUtils.getLoginUser()),
|
||||||
|
dto.getProjectId(), dto.getCounterpartyName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/backfill")
|
||||||
|
@Operation(summary = "启动项目对手方工商信息历史补全")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:counterpartyEnterprise:backfill')")
|
||||||
|
public AjaxResult backfill() {
|
||||||
|
return AjaxResult.success("任务已提交", service.startBackfill(CallerContext.from(SecurityUtils.getLoginUser())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/backfill/{taskId}")
|
||||||
|
@Operation(summary = "查询项目对手方工商信息补全状态")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:counterpartyEnterprise:backfill')")
|
||||||
|
public AjaxResult backfillStatus(@PathVariable String taskId) {
|
||||||
|
return AjaxResult.success(service.getBackfillStatus(taskId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,14 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQuery
|
|||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskExclusionSaveDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectUnsubmittedAccountQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectUnsubmittedAccountFlowExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
||||||
@@ -23,6 +26,8 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountFlowVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountVO;
|
||||||
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
|
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
@@ -33,8 +38,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@@ -67,6 +74,18 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
return AjaxResult.success(dashboard);
|
return AjaxResult.success(dashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除单条可疑预警
|
||||||
|
*/
|
||||||
|
@PostMapping("/risk-exclusions")
|
||||||
|
@Operation(summary = "排除单条可疑预警")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult excludeRisk(@Validated @RequestBody CcdiProjectRiskExclusionSaveDTO dto) {
|
||||||
|
projectAccessService.assertCanOperate(dto.getProjectId());
|
||||||
|
overviewService.excludeRisk(dto);
|
||||||
|
return AjaxResult.success("排除成功");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询风险人员总览
|
* 查询风险人员总览
|
||||||
*/
|
*/
|
||||||
@@ -211,6 +230,30 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
return AjaxResult.success(pageVO);
|
return AjaxResult.success(pageVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询未提交账户
|
||||||
|
*/
|
||||||
|
@GetMapping("/unsubmitted-accounts")
|
||||||
|
@Operation(summary = "查询未提交账户")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getUnsubmittedAccounts(CcdiProjectUnsubmittedAccountQueryDTO queryDTO) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
CcdiProjectUnsubmittedAccountVO result = overviewService.getUnsubmittedAccounts(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询未提交账户流水
|
||||||
|
*/
|
||||||
|
@GetMapping("/unsubmitted-accounts/flows")
|
||||||
|
@Operation(summary = "查询未提交账户流水")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getUnsubmittedAccountFlows(CcdiProjectUnsubmittedAccountQueryDTO queryDTO) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
List<CcdiProjectUnsubmittedAccountFlowVO> rows = overviewService.getUnsubmittedAccountFlows(queryDTO);
|
||||||
|
return AjaxResult.success(rows);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出涉疑交易明细
|
* 导出涉疑交易明细
|
||||||
*/
|
*/
|
||||||
@@ -287,15 +330,35 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
util.exportExcel(response, rows, "外部人员风险模型命中人员");
|
util.exportExcel(response, rows, "外部人员风险模型命中人员");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出未提交账户流水
|
||||||
|
*/
|
||||||
|
@PostMapping("/unsubmitted-accounts/export")
|
||||||
|
@Operation(summary = "导出未提交账户流水")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportUnsubmittedAccountFlows(
|
||||||
|
HttpServletResponse response,
|
||||||
|
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
List<CcdiProjectUnsubmittedAccountFlowExcel> rows = overviewService.exportUnsubmittedAccountFlows(queryDTO);
|
||||||
|
ExcelUtil<CcdiProjectUnsubmittedAccountFlowExcel> util =
|
||||||
|
new ExcelUtil<>(CcdiProjectUnsubmittedAccountFlowExcel.class);
|
||||||
|
util.exportExcel(response, rows, "未提交账户流水");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出风险明细
|
* 导出风险明细
|
||||||
*/
|
*/
|
||||||
@PostMapping("/risk-details/export")
|
@PostMapping("/risk-details/export")
|
||||||
@Operation(summary = "导出风险明细")
|
@Operation(summary = "导出风险明细")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
public void exportRiskDetails(
|
||||||
projectAccessService.assertCanRead(projectId);
|
HttpServletResponse response,
|
||||||
overviewService.exportRiskDetails(response, projectId);
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||||
|
) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
overviewService.exportRiskDetails(response, queryDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryD
|
|||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectIncreaseLendingQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectIncreaseLendingExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||||
@@ -16,16 +18,21 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectIncreaseLendingListVO;
|
||||||
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
|
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@@ -138,4 +145,29 @@ public class CcdiProjectSpecialCheckController extends BaseController {
|
|||||||
CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO);
|
CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO);
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询新增贷款列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/increase-lending/list")
|
||||||
|
@Operation(summary = "查询新增贷款列表")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult getIncreaseLendingList(@Validated CcdiProjectIncreaseLendingQueryDTO queryDTO) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
CcdiProjectIncreaseLendingListVO result = specialCheckService.getIncreaseLendingList(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出新增贷款列表
|
||||||
|
*/
|
||||||
|
@PostMapping("/increase-lending/export")
|
||||||
|
@Operation(summary = "导出新增贷款列表")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportIncreaseLending(HttpServletResponse response, @Validated CcdiProjectIncreaseLendingQueryDTO queryDTO) {
|
||||||
|
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||||
|
List<CcdiProjectIncreaseLendingExcel> rows = specialCheckService.exportIncreaseLendingList(queryDTO);
|
||||||
|
ExcelUtil<CcdiProjectIncreaseLendingExcel> util = new ExcelUtil<>(CcdiProjectIncreaseLendingExcel.class);
|
||||||
|
util.exportExcel(response, rows, "新增贷款查询");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目新增贷款查询入参
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectIncreaseLendingQueryDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 柜员号 */
|
||||||
|
private String staffId;
|
||||||
|
|
||||||
|
/** 员工身份证号 */
|
||||||
|
private String staffIdCard;
|
||||||
|
|
||||||
|
/** 审核人柜员号 */
|
||||||
|
private String approver;
|
||||||
|
|
||||||
|
/** 发放日期起 */
|
||||||
|
private String loanStartDate;
|
||||||
|
|
||||||
|
/** 发放日期止 */
|
||||||
|
private String loanEndDate;
|
||||||
|
|
||||||
|
/** 页码 */
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
/** 每页条数 */
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目结果页排除可疑保存DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectRiskExclusionSaveDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 人员证件号 */
|
||||||
|
private String staffIdCard;
|
||||||
|
|
||||||
|
/** 规则编码 */
|
||||||
|
@NotBlank(message = "规则编码不能为空")
|
||||||
|
private String ruleCode;
|
||||||
|
|
||||||
|
/** 排除类型:STATEMENT/OBJECT */
|
||||||
|
@NotBlank(message = "排除类型不能为空")
|
||||||
|
private String exclusionType;
|
||||||
|
|
||||||
|
/** 流水ID */
|
||||||
|
private Long bankStatementId;
|
||||||
|
|
||||||
|
/** 排除原因 */
|
||||||
|
@NotBlank(message = "排除原因不能为空")
|
||||||
|
@Size(max = 1000, message = "排除原因不能超过1000个字符")
|
||||||
|
private String excludeReason;
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ public class CcdiProjectSuspiciousTransactionQueryDTO {
|
|||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
private Long projectId;
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 模型编码 */
|
||||||
|
private String modelCode;
|
||||||
|
|
||||||
/** 涉疑类型 */
|
/** 涉疑类型 */
|
||||||
private String suspiciousType;
|
private String suspiciousType;
|
||||||
|
|
||||||
@@ -19,4 +22,7 @@ public class CcdiProjectSuspiciousTransactionQueryDTO {
|
|||||||
|
|
||||||
/** 每页数量 */
|
/** 每页数量 */
|
||||||
private Integer pageSize;
|
private Integer pageSize;
|
||||||
|
|
||||||
|
/** 是否包含外部人员预警分支 */
|
||||||
|
private Boolean includeExternalPerson;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未提交账户查询参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectUnsubmittedAccountQueryDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主体名称
|
||||||
|
*/
|
||||||
|
private String subjectName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未提交账号
|
||||||
|
*/
|
||||||
|
private String accountNo;
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import lombok.Data;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拉取本行信息提交参数
|
* 拉取行内/金综流水提交参数
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class CcdiPullBankInfoSubmitDTO {
|
public class CcdiPullBankInfoSubmitDTO {
|
||||||
@@ -16,6 +16,9 @@ public class CcdiPullBankInfoSubmitDTO {
|
|||||||
/** 身份证号列表 */
|
/** 身份证号列表 */
|
||||||
private List<String> idCards;
|
private List<String> idCards;
|
||||||
|
|
||||||
|
/** 数据渠道编码:ZJRCU-行内,JZL-金综 */
|
||||||
|
private String dataChannelCode;
|
||||||
|
|
||||||
/** 开始日期 */
|
/** 开始日期 */
|
||||||
private String startDate;
|
private String startDate;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ProjectCounterpartyRefreshDTO {
|
||||||
|
@NotNull(message = "项目ID不能为空")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
@NotBlank(message = "对手方名称不能为空")
|
||||||
|
private String counterpartyName;
|
||||||
|
}
|
||||||
@@ -194,8 +194,8 @@ public class CcdiBankStatement implements Serializable {
|
|||||||
BeanUtils.copyProperties(item, entity);
|
BeanUtils.copyProperties(item, entity);
|
||||||
|
|
||||||
// 4. 手动映射字段名不一致的情况
|
// 4. 手动映射字段名不一致的情况
|
||||||
entity.setLeAccountNo(item.getAccountMaskNo());
|
entity.setLeAccountNo(item.getAccountNo());
|
||||||
entity.setCustomerAccountNo(item.getCustomerAccountMaskNo());
|
entity.setCustomerAccountNo(item.getCustomerAccountNo());
|
||||||
entity.setLeAccountName(item.getLeName());
|
entity.setLeAccountName(item.getLeName());
|
||||||
entity.setAmountDr(item.getDrAmount());
|
entity.setAmountDr(item.getDrAmount());
|
||||||
entity.setAmountCr(item.getCrAmount());
|
entity.setAmountCr(item.getCrAmount());
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 项目对手方工商信息。 */
|
||||||
|
@Data
|
||||||
|
@TableName("ccdi_project_counterparty_enterprise")
|
||||||
|
public class CcdiProjectCounterpartyEnterprise {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long counterpartyEnterpriseId;
|
||||||
|
private Long projectId;
|
||||||
|
private String counterpartyName;
|
||||||
|
private String socialCreditCode;
|
||||||
|
private String enterpriseName;
|
||||||
|
private BigDecimal registeredCapital;
|
||||||
|
private String registeredCapitalUnit;
|
||||||
|
private LocalDate registerDate;
|
||||||
|
private LocalDate establishDate;
|
||||||
|
private String industryCode;
|
||||||
|
private String industryName;
|
||||||
|
private String organizationTypeCode;
|
||||||
|
private String organizationTypeName;
|
||||||
|
private String regionCode;
|
||||||
|
private String regionName;
|
||||||
|
private String registerAddress;
|
||||||
|
private Integer employeeCount;
|
||||||
|
private String legalRepresentative;
|
||||||
|
private Long cacheInfoId;
|
||||||
|
private Date syncTime;
|
||||||
|
private String createBy;
|
||||||
|
private Date createTime;
|
||||||
|
private String updateBy;
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/** 项目对手方股东信息。 */
|
||||||
|
@Data
|
||||||
|
@TableName("ccdi_project_counterparty_shareholder")
|
||||||
|
public class CcdiProjectCounterpartyShareholder {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long shareholderId;
|
||||||
|
private Long counterpartyEnterpriseId;
|
||||||
|
@TableField("shareholder_seq")
|
||||||
|
private Integer sequenceNo;
|
||||||
|
private String shareholderName;
|
||||||
|
private BigDecimal stockPercent;
|
||||||
|
private BigDecimal subscribedCapital;
|
||||||
|
private String capitalUnit;
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目结果页排除可疑记录
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("ccdi_project_risk_exclusion")
|
||||||
|
public class CcdiProjectRiskExclusion implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 人员证件号 */
|
||||||
|
private String staffIdCard;
|
||||||
|
|
||||||
|
/** 规则编码 */
|
||||||
|
private String ruleCode;
|
||||||
|
|
||||||
|
/** 排除类型:STATEMENT/OBJECT */
|
||||||
|
private String exclusionType;
|
||||||
|
|
||||||
|
/** 流水ID */
|
||||||
|
private Long bankStatementId;
|
||||||
|
|
||||||
|
/** 排除原因 */
|
||||||
|
private String excludeReason;
|
||||||
|
|
||||||
|
/** 创建者 */
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/** 更新者 */
|
||||||
|
private String updateBy;
|
||||||
|
|
||||||
|
/** 更新时间 */
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/** 备注 */
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.ccdi.project.domain.event;
|
package com.ruoyi.ccdi.project.domain.event;
|
||||||
|
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||||
|
import com.ruoyi.lsfx.domain.CallerContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 历史项目导入提交事件
|
* 历史项目导入提交事件
|
||||||
@@ -10,14 +11,14 @@ public class CcdiProjectHistoryImportSubmittedEvent {
|
|||||||
private final Long targetProjectId;
|
private final Long targetProjectId;
|
||||||
private final Integer targetLsfxProjectId;
|
private final Integer targetLsfxProjectId;
|
||||||
private final CcdiProjectImportHistoryDTO dto;
|
private final CcdiProjectImportHistoryDTO dto;
|
||||||
private final String operator;
|
private final CallerContext caller;
|
||||||
|
|
||||||
public CcdiProjectHistoryImportSubmittedEvent(Long targetProjectId, Integer targetLsfxProjectId,
|
public CcdiProjectHistoryImportSubmittedEvent(Long targetProjectId, Integer targetLsfxProjectId,
|
||||||
CcdiProjectImportHistoryDTO dto, String operator) {
|
CcdiProjectImportHistoryDTO dto, CallerContext caller) {
|
||||||
this.targetProjectId = targetProjectId;
|
this.targetProjectId = targetProjectId;
|
||||||
this.targetLsfxProjectId = targetLsfxProjectId;
|
this.targetLsfxProjectId = targetLsfxProjectId;
|
||||||
this.dto = dto;
|
this.dto = dto;
|
||||||
this.operator = operator;
|
this.caller = caller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getTargetProjectId() {
|
public Long getTargetProjectId() {
|
||||||
@@ -32,7 +33,7 @@ public class CcdiProjectHistoryImportSubmittedEvent {
|
|||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOperator() {
|
public CallerContext getCaller() {
|
||||||
return operator;
|
return caller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.ccdi.project.domain.excel;
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
import com.ruoyi.common.annotation.Excel;
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,4 +27,10 @@ public class CcdiProjectAbnormalAccountExcel {
|
|||||||
|
|
||||||
@Excel(name = "状态")
|
@Excel(name = "状态")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Excel(name = "命中原因")
|
||||||
|
private String reasonDetail;
|
||||||
|
|
||||||
|
@Excel(name = "涉及金额")
|
||||||
|
private BigDecimal involvedAmount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增贷款查询导出对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectIncreaseLendingExcel {
|
||||||
|
|
||||||
|
/** 柜员号 */
|
||||||
|
@Excel(name = "柜员号")
|
||||||
|
private String staffId;
|
||||||
|
|
||||||
|
/** 员工姓名 */
|
||||||
|
@Excel(name = "员工姓名")
|
||||||
|
private String staffName;
|
||||||
|
|
||||||
|
/** 员工身份证 */
|
||||||
|
@Excel(name = "员工身份证")
|
||||||
|
private String staffIdCard;
|
||||||
|
|
||||||
|
/** 部门 */
|
||||||
|
@Excel(name = "部门")
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/** 合同编号 */
|
||||||
|
@Excel(name = "合同编号")
|
||||||
|
private String contractNo;
|
||||||
|
|
||||||
|
/** 放款机构号 */
|
||||||
|
@Excel(name = "放款机构号")
|
||||||
|
private String lendingOrgNo;
|
||||||
|
|
||||||
|
/** 借款人 */
|
||||||
|
@Excel(name = "借款人")
|
||||||
|
private String borrowerName;
|
||||||
|
|
||||||
|
/** 借款人证件号 */
|
||||||
|
@Excel(name = "借款人证件号")
|
||||||
|
private String borrowerCertNo;
|
||||||
|
|
||||||
|
/** 贷款产品 */
|
||||||
|
@Excel(name = "贷款产品")
|
||||||
|
private String loanProduct;
|
||||||
|
|
||||||
|
/** 合同金额 */
|
||||||
|
@Excel(name = "合同金额")
|
||||||
|
private BigDecimal contractAmount;
|
||||||
|
|
||||||
|
/** 当前余额 */
|
||||||
|
@Excel(name = "当前余额")
|
||||||
|
private BigDecimal loanBalance;
|
||||||
|
|
||||||
|
/** 发放日期 */
|
||||||
|
@Excel(name = "发放日期")
|
||||||
|
private String loanStartDate;
|
||||||
|
|
||||||
|
/** 到期日期 */
|
||||||
|
@Excel(name = "到期日期")
|
||||||
|
private String loanEndDate;
|
||||||
|
|
||||||
|
/** 合同状态 */
|
||||||
|
@Excel(name = "合同状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 五级分类 */
|
||||||
|
@Excel(name = "五级分类")
|
||||||
|
private String fiveClassification;
|
||||||
|
|
||||||
|
/** 客户经理ID */
|
||||||
|
@Excel(name = "客户经理ID")
|
||||||
|
private String customerManagerId;
|
||||||
|
|
||||||
|
/** 客户经理 */
|
||||||
|
@Excel(name = "客户经理")
|
||||||
|
private String customerManagerName;
|
||||||
|
|
||||||
|
/** 审批人 */
|
||||||
|
@Excel(name = "审批人")
|
||||||
|
private String approver;
|
||||||
|
}
|
||||||
@@ -35,9 +35,9 @@ public class CcdiProjectSuspiciousTransactionExcel {
|
|||||||
@Excel(name = "交易类型")
|
@Excel(name = "交易类型")
|
||||||
private String cashType;
|
private String cashType;
|
||||||
|
|
||||||
@Excel(name = "异常标签")
|
|
||||||
private String hitTags;
|
|
||||||
|
|
||||||
@Excel(name = "交易金额")
|
@Excel(name = "交易金额")
|
||||||
private BigDecimal displayAmount;
|
private BigDecimal displayAmount;
|
||||||
|
|
||||||
|
@Excel(name = "异常标签")
|
||||||
|
private String hitTags;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未提交账户流水导出对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectUnsubmittedAccountFlowExcel {
|
||||||
|
|
||||||
|
@Excel(name = "主体名称")
|
||||||
|
private String subjectName;
|
||||||
|
|
||||||
|
@Excel(name = "未提交账号")
|
||||||
|
private String accountNo;
|
||||||
|
|
||||||
|
@Excel(name = "交易时间")
|
||||||
|
private String trxDate;
|
||||||
|
|
||||||
|
@Excel(name = "本方账号")
|
||||||
|
private String leAccountNo;
|
||||||
|
|
||||||
|
@Excel(name = "本方主体")
|
||||||
|
private String leAccountName;
|
||||||
|
|
||||||
|
@Excel(name = "对方名称")
|
||||||
|
private String customerAccountName;
|
||||||
|
|
||||||
|
@Excel(name = "对方账号")
|
||||||
|
private String customerAccountNo;
|
||||||
|
|
||||||
|
@Excel(name = "摘要")
|
||||||
|
private String userMemo;
|
||||||
|
|
||||||
|
@Excel(name = "交易类型")
|
||||||
|
private String cashType;
|
||||||
|
|
||||||
|
@Excel(name = "流入金额")
|
||||||
|
private BigDecimal amountCr;
|
||||||
|
|
||||||
|
@Excel(name = "流出金额")
|
||||||
|
private BigDecimal amountDr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流水对手方聚合VO
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiBankStatementCounterpartyVO {
|
||||||
|
|
||||||
|
/** 对方名称 */
|
||||||
|
private String customerAccountName;
|
||||||
|
|
||||||
|
/** 对方名称是否为空 */
|
||||||
|
private Boolean nameEmpty;
|
||||||
|
|
||||||
|
/** 关联账号数 */
|
||||||
|
private Integer accountCount;
|
||||||
|
|
||||||
|
/** 流入合计 */
|
||||||
|
private BigDecimal inAmount;
|
||||||
|
|
||||||
|
/** 流入笔数 */
|
||||||
|
private Long inCount;
|
||||||
|
|
||||||
|
/** 流出合计 */
|
||||||
|
private BigDecimal outAmount;
|
||||||
|
|
||||||
|
/** 流出笔数 */
|
||||||
|
private Long outCount;
|
||||||
|
|
||||||
|
/** 总交易金额 */
|
||||||
|
private BigDecimal totalAmount;
|
||||||
|
|
||||||
|
/** 总笔数 */
|
||||||
|
private Long totalCount;
|
||||||
|
|
||||||
|
/** 最近交易时间 */
|
||||||
|
private String latestTrxDate;
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class CcdiBankStatementHitTagVO {
|
public class CcdiBankStatementHitTagVO {
|
||||||
|
|
||||||
|
/** 模型编码 */
|
||||||
|
private String modelCode;
|
||||||
|
|
||||||
/** 规则编码 */
|
/** 规则编码 */
|
||||||
private String ruleCode;
|
private String ruleCode;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ruoyi.ccdi.project.domain.vo;
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,4 +20,8 @@ public class CcdiProjectAbnormalAccountItemVO {
|
|||||||
private String abnormalTime;
|
private String abnormalTime;
|
||||||
|
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
private String reasonDetail;
|
||||||
|
|
||||||
|
private BigDecimal involvedAmount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ public class CcdiProjectFamilyAssetLiabilityListItemVO {
|
|||||||
/** 家庭总年收入 */
|
/** 家庭总年收入 */
|
||||||
private BigDecimal totalIncome;
|
private BigDecimal totalIncome;
|
||||||
|
|
||||||
|
/** 本人年收入 */
|
||||||
|
private BigDecimal selfIncome;
|
||||||
|
|
||||||
|
/** 配偶年收入 */
|
||||||
|
private BigDecimal spouseIncome;
|
||||||
|
|
||||||
|
/** 本人入职年限 */
|
||||||
|
private Integer employmentYears;
|
||||||
|
|
||||||
/** 家庭总资产 */
|
/** 家庭总资产 */
|
||||||
private BigDecimal totalAsset;
|
private BigDecimal totalAsset;
|
||||||
|
|
||||||
@@ -33,6 +42,12 @@ public class CcdiProjectFamilyAssetLiabilityListItemVO {
|
|||||||
/** 收入负债对比金额 */
|
/** 收入负债对比金额 */
|
||||||
private BigDecimal comparisonAmount;
|
private BigDecimal comparisonAmount;
|
||||||
|
|
||||||
|
/** 可解释收入 */
|
||||||
|
private BigDecimal explainableIncome;
|
||||||
|
|
||||||
|
/** 资产收入倍数 */
|
||||||
|
private BigDecimal assetIncomeRatio;
|
||||||
|
|
||||||
/** 风险等级编码 */
|
/** 风险等级编码 */
|
||||||
private String riskLevelCode;
|
private String riskLevelCode;
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ public class CcdiProjectFamilyIncomeDetailVO {
|
|||||||
/** 配偶年收入 */
|
/** 配偶年收入 */
|
||||||
private BigDecimal spouseIncome;
|
private BigDecimal spouseIncome;
|
||||||
|
|
||||||
|
/** 本人入职年限 */
|
||||||
|
private Integer employmentYears;
|
||||||
|
|
||||||
/** 家庭总年收入 */
|
/** 家庭总年收入 */
|
||||||
private BigDecimal totalIncome;
|
private BigDecimal totalIncome;
|
||||||
|
|
||||||
|
/** 可解释收入 */
|
||||||
|
private BigDecimal explainableIncome;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目新增贷款查询列表项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectIncreaseLendingListItemVO {
|
||||||
|
|
||||||
|
/** 员工ID/柜员号 */
|
||||||
|
private String staffId;
|
||||||
|
|
||||||
|
/** 员工姓名 */
|
||||||
|
private String staffName;
|
||||||
|
|
||||||
|
/** 员工身份证号 */
|
||||||
|
private String staffIdCard;
|
||||||
|
|
||||||
|
/** 所属部门 */
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/** 合同编号 */
|
||||||
|
private String contractNo;
|
||||||
|
|
||||||
|
/** 放款机构号 */
|
||||||
|
private String lendingOrgNo;
|
||||||
|
|
||||||
|
/** 借款人名称 */
|
||||||
|
private String borrowerName;
|
||||||
|
|
||||||
|
/** 借款人证件号码 */
|
||||||
|
private String borrowerCertNo;
|
||||||
|
|
||||||
|
/** 贷款产品 */
|
||||||
|
private String loanProduct;
|
||||||
|
|
||||||
|
/** 合同金额 */
|
||||||
|
private BigDecimal contractAmount;
|
||||||
|
|
||||||
|
/** 当前贷款余额 */
|
||||||
|
private BigDecimal loanBalance;
|
||||||
|
|
||||||
|
/** 贷款发放日期 */
|
||||||
|
private String loanStartDate;
|
||||||
|
|
||||||
|
/** 贷款到期日期 */
|
||||||
|
private String loanEndDate;
|
||||||
|
|
||||||
|
/** 合同状态 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 五级分类 */
|
||||||
|
private String fiveClassification;
|
||||||
|
|
||||||
|
/** 客户经理ID */
|
||||||
|
private String customerManagerId;
|
||||||
|
|
||||||
|
/** 客户经理姓名 */
|
||||||
|
private String customerManagerName;
|
||||||
|
|
||||||
|
/** 审批人 */
|
||||||
|
private String approver;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目新增贷款查询列表结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectIncreaseLendingListVO {
|
||||||
|
|
||||||
|
/** 列表数据 */
|
||||||
|
private List<CcdiProjectIncreaseLendingListItemVO> rows;
|
||||||
|
|
||||||
|
/** 总数 */
|
||||||
|
private Long total;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.ruoyi.ccdi.project.domain.CcdiProject;
|
|||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectUnsubmittedAccountFlowExcel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -37,4 +38,6 @@ public class CcdiProjectOverviewReportVO {
|
|||||||
private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>();
|
private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>();
|
||||||
|
|
||||||
private List<CcdiProjectAbnormalAccountExcel> abnormalAccounts = new ArrayList<>();
|
private List<CcdiProjectAbnormalAccountExcel> abnormalAccounts = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<CcdiProjectUnsubmittedAccountFlowExcel> unsubmittedAccountFlows = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class CcdiProjectPersonAnalysisObjectRecordVO {
|
|||||||
|
|
||||||
private String modelCode;
|
private String modelCode;
|
||||||
|
|
||||||
|
private String ruleCode;
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
private String subtitle;
|
private String subtitle;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 涉疑交易明细行
|
* 涉疑交易明细行
|
||||||
@@ -14,6 +16,14 @@ public class CcdiProjectSuspiciousTransactionItemVO {
|
|||||||
|
|
||||||
private String trxDate;
|
private String trxDate;
|
||||||
|
|
||||||
|
private String leAccountNo;
|
||||||
|
|
||||||
|
private String leAccountName;
|
||||||
|
|
||||||
|
private String customerAccountName;
|
||||||
|
|
||||||
|
private String customerAccountNo;
|
||||||
|
|
||||||
private String suspiciousPersonName;
|
private String suspiciousPersonName;
|
||||||
|
|
||||||
private String relatedPersonName;
|
private String relatedPersonName;
|
||||||
@@ -35,4 +45,6 @@ public class CcdiProjectSuspiciousTransactionItemVO {
|
|||||||
private Boolean hasNameListHit;
|
private Boolean hasNameListHit;
|
||||||
|
|
||||||
private String nameListHitType;
|
private String nameListHitType;
|
||||||
|
|
||||||
|
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user