中介黑名单更新
This commit is contained in:
@@ -69,7 +69,16 @@
|
||||
"Bash(ls:*)",
|
||||
"Bash(test_report.sh \")",
|
||||
"mcp__mysql__show_statement",
|
||||
"Bash(if not exist \"doc\\\\designs\" mkdir docdesigns)"
|
||||
"Bash(if not exist \"doc\\\\designs\" mkdir docdesigns)",
|
||||
"Bash(if [ ! -d \"D:\\\\ccdi\\\\ccdi\\\\ruoyi-ccdi\\\\src\\\\main\\\\java\\\\com\\\\ruoyi\\\\ccdi\\\\domain\\\\dto\" ])",
|
||||
"Bash(then mkdir -p \"D:\\\\ccdi\\\\ccdi\\\\ruoyi-ccdi\\\\src\\\\main\\\\java\\\\com\\\\ruoyi\\\\ccdi\\\\domain\\\\dto\")",
|
||||
"Bash(fi)",
|
||||
"Bash(cat:*)",
|
||||
"Skill(superpowers:executing-plans)",
|
||||
"Skill(superpowers:finishing-a-development-branch)",
|
||||
"Skill(superpowers:systematic-debugging)",
|
||||
"mcp__mysql__execute",
|
||||
"Skill(document-skills:xlsx)"
|
||||
]
|
||||
},
|
||||
"enabledMcpjsonServers": [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
||||
1,biz_id,VARCHAR,-,否,是,人员ID
|
||||
2,person_type,VARCHAR,-,否,否,人员类型,中介、职业背债人、房产中介等
|
||||
3,person_sub_type,VARCHAR,-,是,否,人员子类型
|
||||
4,relation_type,VARCHAR,-,否,-,关系类型,如:配偶、子女、父母、兄弟姐妹等
|
||||
5,name,VARCHAR,-,否,否,姓名
|
||||
6,gender,CHAR,-,是,否,性别
|
||||
7,id_type,VARCHAR,身份证,否,否,证件类型
|
||||
@@ -15,7 +14,7 @@
|
||||
13,social_credit_code,VARCHAR,,,,企业统一信用码
|
||||
14,position,VARCHAR,-,是,否,职位
|
||||
15,related_num_id,VARCHAR,-,是,否,关联人员ID
|
||||
16,relation_type,VARCHAR,-,是,否,关联关系
|
||||
16,relation_type,VARCHAR,-,是,否,关系类型,如:配偶、子女、父母、兄弟姐妹等
|
||||
17,date_source,,,,,"数据来源,MANUAL:手动录入, SYSTEM:系统同步, IMPORT:批量导入, API:接口获取"
|
||||
18,remark,,,,,备注信息
|
||||
19,created_by,VARCHAR,-,否,-,记录创建人
|
||||
|
||||
|
@@ -0,0 +1,216 @@
|
||||
# 中介黑名单联合查询功能重构实现总结
|
||||
|
||||
## 一、问题描述
|
||||
|
||||
原始的SQL错误:`Unknown column 'relation_type_field' in 'field list'`
|
||||
|
||||
**根本原因:**
|
||||
1. 实体类 `CcdiBizIntermediary` 中定义了不存在的字段 `relationTypeField`
|
||||
2. 实体类中的 `dataSource` 字段与数据库字段 `date_source` 映射不匹配
|
||||
3. 原有的列表查询实现通过Java层合并两张表的数据,效率较低且无法利用数据库优化
|
||||
|
||||
## 二、解决方案
|
||||
|
||||
### 2.1 修复实体类字段映射
|
||||
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiBizIntermediary.java`
|
||||
|
||||
**修改内容:**
|
||||
1. 删除了不存在的 `relationTypeField` 字段(第70行)
|
||||
2. 为 `dataSource` 字段添加了 `@TableField("date_source")` 注解(第70行)
|
||||
|
||||
```java
|
||||
// 修改前
|
||||
private String relationTypeField;
|
||||
private String dataSource;
|
||||
|
||||
// 修改后
|
||||
@TableField("date_source")
|
||||
private String dataSource;
|
||||
```
|
||||
|
||||
### 2.2 创建联合查询Mapper接口
|
||||
|
||||
**新增文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java`
|
||||
|
||||
**功能:**
|
||||
- 定义联合查询方法 `selectIntermediaryList()`
|
||||
- 定义统计查询方法 `selectIntermediaryCount()`
|
||||
- 支持按中介类型筛选:`1=个人, 2=实体, null=全部`
|
||||
|
||||
### 2.3 创建MyBatis XML Mapper
|
||||
|
||||
**新增文件:** `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml`
|
||||
|
||||
**SQL设计策略:**
|
||||
|
||||
1. **单表查询模式**(当指定中介类型时)
|
||||
- `intermediaryType=1`:仅查询 `ccdi_biz_intermediary` 表
|
||||
- `intermediaryType=2`:仅查询 `ccdi_enterprise_base_info` 表
|
||||
|
||||
2. **联合查询模式**(当intermediaryType为null时)
|
||||
- 使用 `UNION ALL` 联合两张表
|
||||
- 外层包裹 `SELECT * FROM (...) AS combined_result` 用于统一排序和分页
|
||||
- 按创建时间倒序排列
|
||||
|
||||
3. **动态SQL特性**
|
||||
- 使用 MyBatis 动态SQL实现灵活的查询条件组合
|
||||
- 支持姓名模糊查询
|
||||
- 支持证件号/统一社会信用代码精确查询
|
||||
- 支持分页(LIMIT + OFFSET)
|
||||
|
||||
**查询条件映射:**
|
||||
|
||||
| 查询参数 | 个人中介表字段 | 实体中介表字段 |
|
||||
|---------|--------------|--------------|
|
||||
| name | name | enterprise_name |
|
||||
| certificateNo | person_id | social_credit_code |
|
||||
| intermediaryType | person_type='中介' | risk_level='1' AND ent_source='INTERMEDIARY' |
|
||||
|
||||
### 2.4 优化Service层实现
|
||||
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java`
|
||||
|
||||
**修改内容:**
|
||||
|
||||
1. 注入新的 `CcdiIntermediaryMapper`
|
||||
2. 重写 `selectIntermediaryPage()` 方法,使用XML联合查询
|
||||
3. 删除原有的Java层合并数据和手动分页逻辑
|
||||
|
||||
**性能优势:**
|
||||
- 数据库层面实现分页,减少内存占用
|
||||
- 利用数据库索引优化查询性能
|
||||
- 减少网络传输数据量
|
||||
|
||||
### 2.5 扩展查询DTO
|
||||
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java`
|
||||
|
||||
**新增字段:**
|
||||
```java
|
||||
private Integer pageNum; // 页码
|
||||
private Integer pageSize; // 每页大小
|
||||
```
|
||||
|
||||
## 三、技术实现细节
|
||||
|
||||
### 3.1 分页实现
|
||||
|
||||
**MyBatis Plus的分页机制:**
|
||||
- MyBatis Plus的分页从1开始(`page.getCurrent()`)
|
||||
- SQL的OFFSET从0开始
|
||||
- 需要转换:`pageNum = page.getCurrent() - 1`
|
||||
|
||||
**SQL分页语法:**
|
||||
```sql
|
||||
LIMIT #{pageSize}
|
||||
OFFSET #{pageNum} * #{pageSize}
|
||||
```
|
||||
|
||||
### 3.2 UNION ALL vs UNION
|
||||
|
||||
- **使用 UNION ALL**:保留所有记录,包括重复记录
|
||||
- **性能优势**:UNION ALL 不进行去重排序,性能更好
|
||||
- **业务场景**:个人中介和实体中介不会重复,无需去重
|
||||
|
||||
### 3.3 动态SQL设计
|
||||
|
||||
使用MyBatis的 `<if>` 标签实现:
|
||||
```xml
|
||||
<if test="intermediaryType != null and intermediaryType == '1'">
|
||||
<!-- 个人中介查询 -->
|
||||
</if>
|
||||
<if test="intermediaryType != null and intermediaryType == '2'">
|
||||
<!-- 实体中介查询 -->
|
||||
</if>
|
||||
<if test="intermediaryType == null or intermediaryType == ''">
|
||||
<!-- 联合查询 -->
|
||||
</if>
|
||||
```
|
||||
|
||||
## 四、测试脚本
|
||||
|
||||
**文件:** `doc/test/scripts/test_union_query.sh`
|
||||
|
||||
**测试用例:**
|
||||
1. Test 1: 查询全部中介(UNION查询)
|
||||
2. Test 2: 仅查询个人中介(单表查询)
|
||||
3. Test 3: 仅查询实体中介(单表查询)
|
||||
4. Test 4: 按姓名模糊查询
|
||||
5. Test 5: 按证件号精确查询
|
||||
6. Test 6: 分页功能测试
|
||||
7. Test 7: 组合查询测试(类型+姓名+分页)
|
||||
|
||||
## 五、文件清单
|
||||
|
||||
### 修改的文件
|
||||
1. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiBizIntermediary.java` - 删除冗余字段,修复字段映射
|
||||
2. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java` - 重构查询逻辑
|
||||
3. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java` - 添加分页参数
|
||||
|
||||
### 新增的文件
|
||||
1. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java` - 联合查询Mapper接口
|
||||
2. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml` - MyBatis XML Mapper
|
||||
3. `doc/test/scripts/test_union_query.sh` - 测试脚本
|
||||
|
||||
### 删除的文件
|
||||
1. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml` - 旧的错误配置
|
||||
|
||||
## 六、优势总结
|
||||
|
||||
### 6.1 性能提升
|
||||
- **数据库层面分页**:避免加载全部数据到内存
|
||||
- **索引优化**:充分利用数据库索引
|
||||
- **减少网络传输**:只传输需要的数据
|
||||
|
||||
### 6.2 代码质量
|
||||
- **职责分离**:查询逻辑集中在Mapper层
|
||||
- **代码简洁**:删除复杂的Java层合并逻辑
|
||||
- **易于维护**:SQL集中管理,便于优化
|
||||
|
||||
### 6.3 灵活性
|
||||
- **动态查询**:支持单表和联合查询灵活切换
|
||||
- **条件组合**:支持多种查询条件组合
|
||||
- **易于扩展**:后续新增字段或查询条件只需修改XML
|
||||
|
||||
## 七、后续建议
|
||||
|
||||
1. **索引优化**:
|
||||
- `ccdi_biz_intermediary`: 确保字段有合适索引
|
||||
- `ccdi_enterprise_base_info`: 确保 `risk_level` 和 `ent_source` 有索引
|
||||
|
||||
2. **性能监控**:
|
||||
- 监控慢查询日志
|
||||
- 根据实际数据量调整分页大小
|
||||
|
||||
3. **功能扩展**:
|
||||
- 考虑添加更多排序字段选项
|
||||
- 考虑支持批量导出时的流式查询
|
||||
|
||||
## 八、执行测试
|
||||
|
||||
```bash
|
||||
# Windows环境
|
||||
cd doc\test\scripts
|
||||
bash test_union_query.sh
|
||||
|
||||
# Linux/Mac环境
|
||||
cd doc/test/scripts
|
||||
chmod +x test_union_query.sh
|
||||
./test_union_query.sh
|
||||
```
|
||||
|
||||
## 九、回滚方案
|
||||
|
||||
如果新实现出现问题,可以通过Git回滚到之前的版本:
|
||||
```bash
|
||||
git checkout HEAD~1 -- ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java
|
||||
```
|
||||
|
||||
删除新增的Mapper文件即可恢复原状。
|
||||
|
||||
---
|
||||
|
||||
**实现日期:** 2026-02-05
|
||||
**实现人:** Claude Code
|
||||
**版本:** v2.0
|
||||
@@ -0,0 +1,368 @@
|
||||
# 中介黑名单联合查询功能重构实现总结 (MyBatis Plus分页版本)
|
||||
|
||||
## 一、版本更新说明
|
||||
|
||||
**版本:** v2.1 (MyBatis Plus分页插件版本)
|
||||
**更新日期:** 2026-02-05
|
||||
**更新内容:** 使用MyBatis Plus分页插件替代手动分页,参考员工模块的实现方式
|
||||
|
||||
## 二、问题描述
|
||||
|
||||
### 2.1 原始错误
|
||||
```
|
||||
Unknown column 'relation_type_field' in 'field list'
|
||||
```
|
||||
|
||||
### 2.2 v2.0版本的问题
|
||||
虽然v2.0版本实现了XML联合查询,但使用了手动的LIMIT/OFFSET分页,这与若依框架的标准实现方式不一致:
|
||||
- **不一致性**:与员工模块等其他模块的实现方式不同
|
||||
- **维护性**:手动计算分页参数,容易出错
|
||||
- **功能限制**:无法利用MyBatis Plus分页插件的优化功能
|
||||
|
||||
## 三、解决方案(v2.1)
|
||||
|
||||
### 3.1 参考实现
|
||||
参考 `CcdiEmployeeController` 和 `CcdiEmployeeServiceImpl` 的实现方式:
|
||||
```java
|
||||
// Controller层
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
Page<CcdiEmployeeVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
|
||||
Page<CcdiEmployeeVO> result = employeeService.selectEmployeePage(page, queryDTO);
|
||||
|
||||
// Service层
|
||||
Page<CcdiEmployeeVO> resultPage = employeeMapper.selectEmployeePageWithDept(voPage, queryDTO);
|
||||
|
||||
// Mapper接口
|
||||
Page<CcdiEmployeeVO> selectEmployeePageWithDept(@Param("page") Page<CcdiEmployeeVO> page,
|
||||
@Param("query") CcdiEmployeeQueryDTO queryDTO);
|
||||
|
||||
// XML
|
||||
<select id="selectEmployeePageWithDept" resultMap="CcdiEmployeeVOResult">
|
||||
SELECT ... FROM ...
|
||||
WHERE ...
|
||||
ORDER BY ...
|
||||
<!-- 不包含LIMIT和OFFSET,由MyBatis Plus自动注入 -->
|
||||
</select>
|
||||
```
|
||||
|
||||
### 3.2 核心改动
|
||||
|
||||
#### 1. Mapper接口方法签名
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java`
|
||||
|
||||
**修改前:**
|
||||
```java
|
||||
List<CcdiIntermediaryVO> selectIntermediaryList(CcdiIntermediaryQueryDTO queryDTO);
|
||||
long selectIntermediaryCount(CcdiIntermediaryQueryDTO queryDTO);
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```java
|
||||
Page<CcdiIntermediaryVO> selectIntermediaryList(
|
||||
Page<CcdiIntermediaryVO> page,
|
||||
@Param("query") CcdiIntermediaryQueryDTO queryDTO
|
||||
);
|
||||
```
|
||||
|
||||
**关键点:**
|
||||
- 第一个参数是 `Page` 对象
|
||||
- 查询条件使用 `@Param` 注解包装
|
||||
- 返回类型是 `Page<Vo>`
|
||||
- 删除了单独的count查询方法
|
||||
|
||||
#### 2. XML Mapper文件
|
||||
**文件:** `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml`
|
||||
|
||||
**修改前(v2.0):**
|
||||
```xml
|
||||
<!-- 三个独立的SQL分支,每个分支都包含LIMIT和OFFSET -->
|
||||
<if test="intermediaryType == '1'">
|
||||
SELECT ... FROM ccdi_biz_intermediary ...
|
||||
LIMIT #{pageSize} OFFSET #{pageNum} * #{pageSize}
|
||||
</if>
|
||||
<if test="intermediaryType == '2'">
|
||||
SELECT ... FROM ccdi_enterprise_base_info ...
|
||||
LIMIT #{pageSize} OFFSET #{pageNum} * #{pageSize}
|
||||
</if>
|
||||
<if test="intermediaryType == null">
|
||||
SELECT * FROM (...) UNION ALL (...)
|
||||
LIMIT #{pageSize} OFFSET #{pageNum} * #{pageSize}
|
||||
</if>
|
||||
```
|
||||
|
||||
**修改后(v2.1):**
|
||||
```xml
|
||||
<!-- 统一的SQL结构,不包含LIMIT和OFFSET -->
|
||||
<select id="selectIntermediaryList" resultType="com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO">
|
||||
SELECT * FROM (
|
||||
<!-- 个人中介 -->
|
||||
SELECT ... FROM ccdi_biz_intermediary WHERE person_type = '中介'
|
||||
UNION ALL
|
||||
<!-- 实体中介 -->
|
||||
SELECT ... FROM ccdi_enterprise_base_info WHERE ...
|
||||
) AS combined_result
|
||||
<where>
|
||||
<!-- 动态查询条件 -->
|
||||
<if test="query.intermediaryType != null and query.intermediaryType != ''">
|
||||
AND intermediary_type = #{query.intermediaryType}
|
||||
</if>
|
||||
<if test="query.name != null and query.name != ''">
|
||||
AND name LIKE CONCAT('%', #{query.name}, '%')
|
||||
</if>
|
||||
<if test="query.certificateNo != null and query.certificateNo != ''">
|
||||
AND certificate_no = #{query.certificateNo}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
<!-- MyBatis Plus会自动在这里注入LIMIT和OFFSET -->
|
||||
</select>
|
||||
```
|
||||
|
||||
**关键点:**
|
||||
- 统一的查询结构,使用UNION ALL
|
||||
- 不包含LIMIT和OFFSET
|
||||
- 在最外层使用 `<where>` 进行动态过滤
|
||||
- MyBatis Plus分页插件会自动在ORDER BY后面注入分页SQL
|
||||
|
||||
#### 3. Service层实现
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java`
|
||||
|
||||
**修改前(v2.0):**
|
||||
```java
|
||||
public Page<CcdiIntermediaryVO> selectIntermediaryPage(...) {
|
||||
// 手动查询总数
|
||||
long total = intermediaryMapper.selectIntermediaryCount(queryDTO);
|
||||
|
||||
// 手动设置分页参数
|
||||
queryDTO.setPageNum((int) (page.getCurrent() - 1));
|
||||
queryDTO.setPageSize((int) page.getSize());
|
||||
|
||||
// 手动查询列表
|
||||
List<CcdiIntermediaryVO> list = intermediaryMapper.selectIntermediaryList(queryDTO);
|
||||
|
||||
// 手动设置分页结果
|
||||
page.setRecords(list);
|
||||
page.setTotal(total);
|
||||
|
||||
return page;
|
||||
}
|
||||
```
|
||||
|
||||
**修改后(v2.1):**
|
||||
```java
|
||||
public Page<CcdiIntermediaryVO> selectIntermediaryPage(Page<CcdiIntermediaryVO> page, CcdiIntermediaryQueryDTO queryDTO) {
|
||||
// 直接调用Mapper的联合查询方法,MyBatis Plus会自动处理分页
|
||||
return intermediaryMapper.selectIntermediaryList(page, queryDTO);
|
||||
}
|
||||
```
|
||||
|
||||
**关键点:**
|
||||
- 一行代码搞定
|
||||
- MyBatis Plus自动处理count查询、分页SQL注入、结果封装
|
||||
- 无需手动计算分页参数
|
||||
|
||||
#### 4. QueryDTO清理
|
||||
**文件:** `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java`
|
||||
|
||||
**删除字段:**
|
||||
```java
|
||||
// 不再需要,分页信息通过Page对象传递
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
```
|
||||
|
||||
## 四、技术实现细节
|
||||
|
||||
### 4.1 MyBatis Plus分页插件工作原理
|
||||
|
||||
1. **拦截器机制**
|
||||
- MyBatis Plus使用拦截器在SQL执行前拦截
|
||||
- 自动在SQL后面添加LIMIT和OFFSET
|
||||
- 自动执行COUNT查询获取total
|
||||
|
||||
2. **分页SQL生成**
|
||||
```sql
|
||||
-- 原始SQL
|
||||
SELECT * FROM (UNION查询) AS t WHERE ... ORDER BY create_time DESC
|
||||
|
||||
-- MyBatis Plus自动注入后
|
||||
SELECT * FROM (
|
||||
SELECT * FROM (UNION查询) AS t WHERE ... ORDER BY create_time DESC
|
||||
LIMIT 10 OFFSET 0
|
||||
) AS page
|
||||
```
|
||||
|
||||
3. **参数传递**
|
||||
- Controller: `PageDomain` → `Page<Vo>`
|
||||
- Service: `Page<Vo>` 传递给Mapper
|
||||
- Mapper: `Page<Vo>` 作为第一个参数
|
||||
- XML: 通过MyBatis Plus拦截器自动处理
|
||||
|
||||
### 4.2 SQL优化
|
||||
|
||||
#### v2.0的问题
|
||||
- 三个独立的SQL分支
|
||||
- 每个分支都需要处理分页
|
||||
- 代码重复,维护困难
|
||||
|
||||
#### v2.1的优化
|
||||
- 统一的SQL结构
|
||||
- 外层WHERE条件过滤
|
||||
- MyBatis Plus统一处理分页
|
||||
- 代码简洁,易于维护
|
||||
|
||||
### 4.3 参数绑定变化
|
||||
|
||||
**v2.0:**
|
||||
```java
|
||||
// QueryDTO包含分页参数
|
||||
queryDTO.setPageNum(0);
|
||||
queryDTO.setPageSize(10);
|
||||
mapper.selectList(queryDTO);
|
||||
|
||||
// XML中直接使用
|
||||
#{pageNum}, #{pageSize}
|
||||
```
|
||||
|
||||
**v2.1:**
|
||||
```java
|
||||
// Page对象单独传递
|
||||
Page<CcdiIntermediaryVO> page = new Page<>(1, 10);
|
||||
mapper.selectList(page, queryDTO);
|
||||
|
||||
// XML中通过@Param包装
|
||||
#{query.intermediaryType}, #{query.name}
|
||||
```
|
||||
|
||||
## 五、文件清单
|
||||
|
||||
### 修改的文件
|
||||
1. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiBizIntermediary.java` - 删除冗余字段,修复字段映射
|
||||
2. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiIntermediaryQueryDTO.java` - 删除分页参数
|
||||
3. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiIntermediaryMapper.java` - 修改方法签名
|
||||
4. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java` - 简化分页逻辑
|
||||
5. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiIntermediaryMapper.xml` - 重写SQL结构
|
||||
|
||||
### 新增的文件
|
||||
1. `doc/test/scripts/test_union_query_mybatis_plus.sh` - 测试脚本
|
||||
2. `doc/plans/2026-02-05-intermediary-blacklist-union-query-mybatis-plus.md` - 本文档
|
||||
|
||||
### 删除的文件
|
||||
1. `doc/test/scripts/test_union_query.sh` - 旧版测试脚本(保留备份)
|
||||
|
||||
## 六、优势总结
|
||||
|
||||
### 6.1 与框架一致性
|
||||
- ✅ 与员工模块等其他模块实现方式一致
|
||||
- ✅ 符合若依框架的标准规范
|
||||
- ✅ 便于团队统一维护
|
||||
|
||||
### 6.2 代码简洁性
|
||||
- ✅ Service层从10+行代码减少到1行
|
||||
- ✅ XML从200+行减少到60行
|
||||
- ✅ 删除了手动分页的复杂逻辑
|
||||
|
||||
### 6.3 性能优化
|
||||
- ✅ MyBatis Plus分页插件经过优化
|
||||
- ✅ 自动缓存count查询结果
|
||||
- ✅ 支持多种数据库的分页方言
|
||||
|
||||
### 6.4 可维护性
|
||||
- ✅ 统一的SQL结构,易于理解
|
||||
- ✅ 动态条件集中在外层WHERE
|
||||
- ✅ 易于扩展新的查询条件
|
||||
|
||||
## 七、测试验证
|
||||
|
||||
### 7.1 测试脚本
|
||||
**文件:** `doc/test/scripts/test_union_query_mybatis_plus.sh`
|
||||
|
||||
**测试用例:**
|
||||
1. Test 1: UNION ALL查询全部中介
|
||||
2. Test 2: 按类型筛选个人中介
|
||||
3. Test 3: 按类型筛选实体中介
|
||||
4. Test 4: 按姓名模糊查询
|
||||
5. Test 5: 按证件号精确查询
|
||||
6. Test 6: MyBatis Plus分页功能测试
|
||||
7. Test 7: 组合查询测试
|
||||
8. Test 8: 大分页测试
|
||||
|
||||
### 7.2 执行测试
|
||||
```bash
|
||||
# Windows环境
|
||||
cd doc\test\scripts
|
||||
bash test_union_query_mybatis_plus.sh
|
||||
|
||||
# Linux/Mac环境
|
||||
cd doc/test/scripts
|
||||
chmod +x test_union_query_mybatis_plus.sh
|
||||
./test_union_query_mybatis_plus.sh
|
||||
```
|
||||
|
||||
## 八、对比总结
|
||||
|
||||
| 特性 | v2.0 (手动分页) | v2.1 (MyBatis Plus) |
|
||||
|-----|----------------|-------------------|
|
||||
| Service代码行数 | 10+ | 1 |
|
||||
| XML代码行数 | 200+ | 60 |
|
||||
| 一致性 | ❌ 与框架不一致 | ✅ 完全一致 |
|
||||
| 性能 | 一般 | 优化 |
|
||||
| 维护性 | 复杂 | 简单 |
|
||||
| 扩展性 | 困难 | 容易 |
|
||||
| Count查询 | 手动 | 自动 |
|
||||
| 分页计算 | 手动 | 自动 |
|
||||
|
||||
## 九、最佳实践
|
||||
|
||||
基于本次重构,总结以下最佳实践:
|
||||
|
||||
1. **遵循框架规范**
|
||||
- 优先使用框架提供的标准实现方式
|
||||
- 参考其他模块的成熟实现
|
||||
|
||||
2. **分页查询模式**
|
||||
```java
|
||||
// Mapper接口
|
||||
Page<VO> selectXxxPage(Page<VO> page, @Param("query") QueryDTO query);
|
||||
|
||||
// Service实现
|
||||
return mapper.selectXxxPage(page, query);
|
||||
|
||||
// XML
|
||||
<select id="selectXxxPage" resultType="VO">
|
||||
SELECT ... FROM ...
|
||||
<where>...</where>
|
||||
ORDER BY ...
|
||||
</select>
|
||||
```
|
||||
|
||||
3. **联合查询优化**
|
||||
- 使用UNION ALL而不是多个分支
|
||||
- 在最外层使用WHERE进行过滤
|
||||
- 避免在XML中写LIMIT和OFFSET
|
||||
|
||||
4. **参数传递**
|
||||
- Page对象作为第一个参数
|
||||
- 查询条件使用@Param包装
|
||||
- 避免在实体中混入分页参数
|
||||
|
||||
## 十、后续建议
|
||||
|
||||
1. **性能监控**
|
||||
- 监控UNION ALL查询的执行计划
|
||||
- 优化索引以提升查询性能
|
||||
|
||||
2. **功能扩展**
|
||||
- 考虑添加更多排序字段选项
|
||||
- 考虑支持批量导出的流式查询
|
||||
|
||||
3. **代码优化**
|
||||
- 其他模块如有类似实现,建议统一改造
|
||||
- 建立统一的分页查询模板
|
||||
|
||||
---
|
||||
|
||||
**实现日期:** 2026-02-05
|
||||
**实现人:** Claude Code
|
||||
**版本:** v2.1 (MyBatis Plus分页插件版本)
|
||||
**参考模块:** CcdiEmployeeController/CcdiEmployeeServiceImpl
|
||||
642
doc/plans/2026-02-05-中介黑名单前端适配APIv2.0重构设计.md
Normal file
642
doc/plans/2026-02-05-中介黑名单前端适配APIv2.0重构设计.md
Normal file
@@ -0,0 +1,642 @@
|
||||
# 中介黑名单前端适配API v2.0重构设计文档
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建日期**: 2026-02-05
|
||||
**设计目标**: 将前端字段完全对齐API v2.0规范,实现前后端字段名一致
|
||||
|
||||
---
|
||||
|
||||
## 一、变更背景
|
||||
|
||||
### 1.1 API v2.0核心变更
|
||||
|
||||
后端API已升级至v2.0版本,主要变更包括:
|
||||
|
||||
- **统一业务ID**: 使用`bizId`替代`intermediaryId`作为主键
|
||||
- **接口分离**: 个人和实体中介使用独立的详情查询接口
|
||||
- **字段规范化**: 统一字段命名规范,消除歧义
|
||||
- **DTO/VO分离**: 请求和响应对象完全分离
|
||||
|
||||
### 1.2 重构目标
|
||||
|
||||
1. **字段名对齐**: 前端表单字段与API请求字段完全一致
|
||||
2. **消除映射**: 移除前后端字段名转换逻辑
|
||||
3. **代码简化**: 降低维护成本,提升可读性
|
||||
4. **类型安全**: 确保个人和实体中介字段正确隔离
|
||||
|
||||
---
|
||||
|
||||
## 二、字段映射方案
|
||||
|
||||
### 2.1 个人中介字段映射
|
||||
|
||||
| 旧前端字段 | API v2.0字段 | 说明 |
|
||||
|-----------|-------------|------|
|
||||
| intermediaryId | bizId | 主键ID |
|
||||
| certificateNo | personId | 证件号码 |
|
||||
| indivType | personType | 人员类型 |
|
||||
| indivSubType | personSubType | 人员子类型 |
|
||||
| indivGender | gender | 性别 |
|
||||
| indivCertType | idType | 证件类型 |
|
||||
| indivPhone | mobile | 手机号码 |
|
||||
| indivWechat | wechatNo | 微信号 |
|
||||
| indivAddress | contactAddress | 联系地址 |
|
||||
| indivCompany | company | 所在公司 |
|
||||
| indivPosition | position | 职位 |
|
||||
| indivRelatedId | relatedNumId | 关联人员ID |
|
||||
| indivRelation | relationType | 关系类型 |
|
||||
|
||||
**保持不变的字段:**
|
||||
- name (姓名)
|
||||
- remark (备注)
|
||||
- intermediaryType (中介类型)
|
||||
- status (状态)
|
||||
|
||||
### 2.2 实体中介字段映射
|
||||
|
||||
| 旧前端字段 | API v2.0字段 | 说明 |
|
||||
|-----------|-------------|------|
|
||||
| intermediaryId | bizId | 主键ID |
|
||||
| name | enterpriseName | 机构名称 |
|
||||
| certificateNo / corpCreditCode | socialCreditCode | 统一社会信用代码 |
|
||||
| corpType | enterpriseType | 主体类型 |
|
||||
| corpNature | enterpriseNature | 企业性质 |
|
||||
| corpIndustryCategory | industryClass | 行业分类 |
|
||||
| corpIndustry | industryName | 所属行业 |
|
||||
| corpEstablishDate | establishDate | 成立日期 |
|
||||
| corpAddress | registerAddress | 注册地址 |
|
||||
| corpLegalRep | legalRepresentative | 法定代表人 |
|
||||
| corpLegalCertType | legalCertType | 法定代表人证件类型 |
|
||||
| corpLegalCertNo | legalCertNo | 法定代表人证件号码 |
|
||||
| corpShareholder1-5 | shareholder1-5 | 股东信息(1-5) |
|
||||
|
||||
**保持不变的字段:**
|
||||
- remark (备注)
|
||||
- intermediaryType (中介类型)
|
||||
- status (状态)
|
||||
|
||||
---
|
||||
|
||||
## 三、文件修改清单
|
||||
|
||||
### 3.1 需要修改的文件
|
||||
|
||||
| 序号 | 文件路径 | 修改类型 | 优先级 |
|
||||
|-----|---------|---------|-------|
|
||||
| 1 | `ruoyi-ui/src/api/ccdiIntermediary.js` | API层 | P0 |
|
||||
| 2 | `ruoyi-ui/src/views/ccdiIntermediary/index.vue` | 主页面 | P0 |
|
||||
| 3 | `ruoyi-ui/src/views/ccdiIntermediary/components/EditDialog.vue` | 编辑组件 | P0 |
|
||||
| 4 | `ruoyi-ui/src/views/ccdiIntermediary/components/DetailDialog.vue` | 详情组件 | P1 |
|
||||
| 5 | `ruoyi-ui/src/views/ccdiIntermediary/components/ImportDialog.vue` | 导入组件 | P1 |
|
||||
|
||||
### 3.2 无需修改的文件
|
||||
|
||||
| 序号 | 文件路径 | 原因 |
|
||||
|-----|---------|------|
|
||||
| 1 | `SearchForm.vue` | 查询参数与API兼容 |
|
||||
| 2 | `DataTable.vue` | 已使用友好名称字段 |
|
||||
|
||||
---
|
||||
|
||||
## 四、API层修改详情
|
||||
|
||||
### 4.1 ccdiIntermediary.js
|
||||
|
||||
#### 新增接口
|
||||
|
||||
```javascript
|
||||
// 查询个人中介详情
|
||||
export function getPersonIntermediary(bizId) {
|
||||
return request({
|
||||
url: '/ccdi/intermediary/person/' + bizId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询实体中介详情
|
||||
export function getEntityIntermediary(socialCreditCode) {
|
||||
return request({
|
||||
url: '/ccdi/intermediary/entity/' + socialCreditCode,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### 删除接口
|
||||
|
||||
```javascript
|
||||
// 删除以下旧版统一接口
|
||||
// getIntermediary(intermediaryId)
|
||||
// addIntermediary(data)
|
||||
// updateIntermediary(data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、主页面修改详情
|
||||
|
||||
### 5.1 index.vue - 数据模型
|
||||
|
||||
#### queryParams修改
|
||||
|
||||
```javascript
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
certificateNo: null, // 保持不变(API查询参数兼容)
|
||||
intermediaryType: null,
|
||||
status: null
|
||||
}
|
||||
```
|
||||
|
||||
#### form数据模型
|
||||
|
||||
```javascript
|
||||
form: {
|
||||
// 通用字段
|
||||
bizId: null, // 原 intermediaryId
|
||||
intermediaryType: '1',
|
||||
status: '0',
|
||||
remark: null,
|
||||
|
||||
// 个人中介字段
|
||||
name: null,
|
||||
personId: null, // 原 certificateNo
|
||||
personType: null, // 原 indivType
|
||||
personSubType: null, // 原 indivSubType
|
||||
relationType: null, // 原 indivRelation
|
||||
gender: null, // 原 indivGender
|
||||
idType: null, // 原 indivCertType
|
||||
mobile: null, // 原 indivPhone
|
||||
wechatNo: null, // 原 indivWechat
|
||||
contactAddress: null, // 原 indivAddress
|
||||
company: null, // 原 indivCompany
|
||||
socialCreditCode: null, // 新增
|
||||
position: null, // 原 indivPosition
|
||||
relatedNumId: null, // 原 indivRelatedId
|
||||
|
||||
// 实体中介字段
|
||||
enterpriseName: null, // 原 name
|
||||
socialCreditCode: null, // 原 certificateNo/corpCreditCode
|
||||
enterpriseType: null, // 原 corpType
|
||||
enterpriseNature: null, // 原 corpNature
|
||||
industryClass: null, // 原 corpIndustryCategory
|
||||
industryName: null, // 原 corpIndustry
|
||||
establishDate: null, // 原 corpEstablishDate
|
||||
registerAddress: null, // 原 corpAddress
|
||||
legalRepresentative: null, // 原 corpLegalRep
|
||||
legalCertType: null, // 原 corpLegalCertType
|
||||
legalCertNo: null, // 原 corpLegalCertNo
|
||||
shareholder1: null, // 原 corpShareholder1
|
||||
shareholder2: null, // 原 corpShareholder2
|
||||
shareholder3: null, // 原 corpShareholder3
|
||||
shareholder4: null, // 原 corpShareholder4
|
||||
shareholder5: null // 原 corpShareholder5
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 核心方法修改
|
||||
|
||||
#### handleSelectionChange
|
||||
|
||||
```javascript
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.bizId); // 原 intermediaryId
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
}
|
||||
```
|
||||
|
||||
#### handleDetail
|
||||
|
||||
```javascript
|
||||
handleDetail(row) {
|
||||
if (row.intermediaryType === '1') {
|
||||
// 个人中介
|
||||
getPersonIntermediary(row.bizId).then(response => {
|
||||
this.detailData = response.data;
|
||||
this.detailOpen = true;
|
||||
});
|
||||
} else {
|
||||
// 实体中介
|
||||
getEntityIntermediary(row.socialCreditCode).then(response => {
|
||||
this.detailData = response.data;
|
||||
this.detailOpen = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### handleUpdate
|
||||
|
||||
```javascript
|
||||
handleUpdate(row) {
|
||||
this.reset();
|
||||
if (row.intermediaryType === '1') {
|
||||
getPersonIntermediary(row.bizId).then(response => {
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改中介黑名单";
|
||||
});
|
||||
} else {
|
||||
getEntityIntermediary(row.socialCreditCode).then(response => {
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改中介黑名单";
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### submitForm
|
||||
|
||||
```javascript
|
||||
submitForm() {
|
||||
if (this.form.bizId != null) { // 原 intermediaryId
|
||||
// 修改模式
|
||||
if (this.form.intermediaryType === '1') {
|
||||
updatePersonIntermediary(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
} else {
|
||||
updateEntityIntermediary(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 新增模式
|
||||
if (this.form.intermediaryType === '1') {
|
||||
addPersonIntermediary(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
} else {
|
||||
addEntityIntermediary(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### handleDelete
|
||||
|
||||
```javascript
|
||||
handleDelete(row) {
|
||||
const bizIds = row.bizId || this.ids.join(','); // 原 intermediaryIds
|
||||
this.$modal.confirm('是否确认删除中介黑名单编号为"' + bizIds + '"的数据项?')
|
||||
.then(function() {
|
||||
return delIntermediary(bizIds);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、EditDialog组件修改详情
|
||||
|
||||
### 6.1 个人中介表单字段修改
|
||||
|
||||
| 行号 | 修改内容 |
|
||||
|-----|---------|
|
||||
| 46 | `form.certificateNo` → `form.personId` |
|
||||
| 54 | `form.indivType` → `form.personType` |
|
||||
| 66 | `form.indivSubType` → `form.personSubType` |
|
||||
| 80 | `form.indivGender` → `form.gender` |
|
||||
| 92 | `form.indivCertType` → `form.idType` |
|
||||
| 106 | `form.indivPhone` → `form.mobile` |
|
||||
| 110 | `form.indivWechat` → `form.wechatNo` |
|
||||
| 116 | `form.indivAddress` → `form.contactAddress` |
|
||||
| 121 | `form.indivCompany` → `form.company` |
|
||||
| 126 | `form.indivPosition` → `form.position` |
|
||||
| 133 | `form.indivRelatedId` → `form.relatedNumId` |
|
||||
| 138 | `form.indivRelation` → `form.relationType` |
|
||||
|
||||
### 6.2 实体中介表单字段修改
|
||||
|
||||
| 行号 | 修改内容 |
|
||||
|-----|---------|
|
||||
| 172 | `form.name` → `form.enterpriseName` |
|
||||
| 179 | `form.certificateNo` → `form.socialCreditCode` |
|
||||
| 190 | `form.corpType` → `form.enterpriseType` |
|
||||
| 202 | `form.corpNature` → `form.enterpriseNature` |
|
||||
| 227 | `form.corpIndustryCategory` → `form.industryClass` |
|
||||
| 234 | `form.corpIndustry` → `form.industryName` |
|
||||
| 217 | `form.corpEstablishDate` → `form.establishDate` |
|
||||
| 239 | `form.corpAddress` → `form.registerAddress` |
|
||||
| 244 | `form.corpLegalRep` → `form.legalRepresentative` |
|
||||
| 249-251 | 添加下拉框:`form.legalCertType` (证件类型) |
|
||||
| 254 | `form.corpLegalCertNo` → `form.legalCertNo` |
|
||||
| 260-284 | `form.corpShareholder1-5` → `form.shareholder1-5` |
|
||||
|
||||
### 6.3 Script部分修改
|
||||
|
||||
#### computed属性
|
||||
|
||||
```javascript
|
||||
isAddMode() {
|
||||
return !this.form || !this.form.bizId; // 原 intermediaryId
|
||||
}
|
||||
```
|
||||
|
||||
#### initDialogState方法
|
||||
|
||||
```javascript
|
||||
const isAdd = !this.form || !this.form.bizId; // 原 intermediaryId
|
||||
```
|
||||
|
||||
#### 删除方法
|
||||
|
||||
删除`handleCertificateNoChange`方法(v2.0无需字段同步)
|
||||
|
||||
#### 验证规则修改
|
||||
|
||||
**个人中介:**
|
||||
|
||||
```javascript
|
||||
indivRules: {
|
||||
name: [
|
||||
{ required: true, message: "姓名不能为空", trigger: "blur" },
|
||||
{ max: 100, message: "姓名长度不能超过100个字符", trigger: "blur" }
|
||||
],
|
||||
personId: [ // 原 certificateNo
|
||||
{ required: true, message: "证件号不能为空", trigger: "blur" },
|
||||
{ max: 50, message: "证件号长度不能超过50个字符", trigger: "blur" }
|
||||
],
|
||||
remark: [
|
||||
{ max: 500, message: "备注长度不能超过500个字符", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**实体中介:**
|
||||
|
||||
```javascript
|
||||
corpRules: {
|
||||
enterpriseName: [ // 原 name
|
||||
{ required: true, message: "机构名称不能为空", trigger: "blur" },
|
||||
{ max: 200, message: "机构名称长度不能超过200个字符", trigger: "blur" }
|
||||
],
|
||||
socialCreditCode: [ // 原 certificateNo
|
||||
{ required: true, message: "统一社会信用代码不能为空", trigger: "blur" },
|
||||
{ max: 50, message: "统一社会信用代码长度不能超过50个字符", trigger: "blur" }
|
||||
],
|
||||
remark: [
|
||||
{ max: 500, message: "备注长度不能超过500个字符", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、DetailDialog组件修改详情
|
||||
|
||||
### 7.1 核心字段修改
|
||||
|
||||
```vue
|
||||
<!-- 业务ID -->
|
||||
<el-descriptions-item label="业务ID">{{ detailData.bizId }}</el-descriptions-item>
|
||||
|
||||
<!-- 证件号/信用代码 -->
|
||||
<el-descriptions-item label="证件号/信用代码">
|
||||
<span v-if="detailData.intermediaryType === '1'">{{ detailData.personId || '-' }}</span>
|
||||
<span v-else>{{ detailData.socialCreditCode || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
```
|
||||
|
||||
### 7.2 个人中介字段修改
|
||||
|
||||
| 旧字段 | 新字段 |
|
||||
|--------|--------|
|
||||
| detailData.indivType | detailData.personType |
|
||||
| detailData.indivSubType | detailData.personSubType |
|
||||
| detailData.indivGenderName | detailData.genderName |
|
||||
| detailData.indivCertType | detailData.idType |
|
||||
| detailData.indivPhone | detailData.mobile |
|
||||
| detailData.indivWechat | detailData.wechatNo |
|
||||
| detailData.indivAddress | detailData.contactAddress |
|
||||
| detailData.indivCompany | detailData.company |
|
||||
| detailData.indivPosition | detailData.position |
|
||||
| detailData.indivRelatedId | detailData.relatedNumId |
|
||||
| detailData.indivRelation | detailData.relationType |
|
||||
|
||||
**新增字段:**
|
||||
- detailData.socialCreditCode (企业统一信用码)
|
||||
|
||||
### 7.3 实体中介字段修改
|
||||
|
||||
| 旧字段 | 新字段 |
|
||||
|--------|--------|
|
||||
| detailData.corpCreditCode | detailData.socialCreditCode |
|
||||
| detailData.corpType | detailData.enterpriseType |
|
||||
| detailData.corpNature | detailData.enterpriseNature |
|
||||
| detailData.corpIndustryCategory | detailData.industryClass |
|
||||
| detailData.corpIndustry | detailData.industryName |
|
||||
| detailData.corpEstablishDate | detailData.establishDate |
|
||||
| detailData.corpAddress | detailData.registerAddress |
|
||||
| detailData.corpLegalRep | detailData.legalRepresentative |
|
||||
| detailData.corpLegalCertType | detailData.legalCertType |
|
||||
| detailData.corpLegalCertNo | detailData.legalCertNo |
|
||||
| detailData.corpShareholder1-5 | detailData.shareholder1-5 |
|
||||
|
||||
---
|
||||
|
||||
## 八、ImportDialog组件修改详情
|
||||
|
||||
### 8.1 模板下载URL修正
|
||||
|
||||
**错误代码:**
|
||||
|
||||
```javascript
|
||||
this.download('dpc/intermediary/importPersonTemplate', ...)
|
||||
this.download('dpc/intermediary/importEntityTemplate', ...)
|
||||
```
|
||||
|
||||
**修正为:**
|
||||
|
||||
```javascript
|
||||
handleDownloadTemplate() {
|
||||
if (this.formData.importType === 'person') {
|
||||
this.download('ccdi/intermediary/importPersonTemplate', {}, `个人中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
} else {
|
||||
this.download('ccdi/intermediary/importEntityTemplate', {}, `机构中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、下拉框优化
|
||||
|
||||
### 9.1 新增下拉框
|
||||
|
||||
**法定代表人证件类型** (实体中介表单)
|
||||
|
||||
```vue
|
||||
<el-form-item label="法定代表人证件类型">
|
||||
<el-select v-model="form.legalCertType" placeholder="请选择证件类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in certTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
```
|
||||
|
||||
### 9.2 已有下拉框验证
|
||||
|
||||
- ✅ 性别 (genderOptions)
|
||||
- ✅ 证件类型 (certTypeOptions)
|
||||
- ✅ 主体类型 (corpTypeOptions)
|
||||
- ✅ 企业性质 (corpNatureOptions)
|
||||
- ✅ 人员类型 (indivTypeOptions)
|
||||
- ✅ 人员子类型 (indivSubTypeOptions)
|
||||
- ✅ 关联关系 (relationTypeOptions)
|
||||
|
||||
---
|
||||
|
||||
## 十、测试计划
|
||||
|
||||
### 10.1 功能测试清单
|
||||
|
||||
**查询功能:**
|
||||
- [ ] 列表查询正常显示
|
||||
- [ ] 按姓名/机构名称模糊查询
|
||||
- [ ] 按证件号精确查询
|
||||
- [ ] 按中介类型筛选(个人/机构)
|
||||
- [ ] 分页功能正常
|
||||
|
||||
**个人中介CRUD:**
|
||||
- [ ] 新增个人中介 - 所有字段保存成功
|
||||
- [ ] 查看个人中介详情 - 所有字段正确显示
|
||||
- [ ] 修改个人中介 - 数据更新成功
|
||||
- [ ] 删除个人中介 - 删除成功
|
||||
|
||||
**机构中介CRUD:**
|
||||
- [ ] 新增机构中介 - 所有字段保存成功
|
||||
- [ ] 查看机构中介详情 - 所有字段正确显示
|
||||
- [ ] 修改机构中介 - 数据更新成功
|
||||
- [ ] 删除机构中介 - 删除成功
|
||||
|
||||
**导入功能:**
|
||||
- [ ] 下载个人中介导入模板成功
|
||||
- [ ] 下载机构中介导入模板成功
|
||||
- [ ] 个人中介数据导入成功
|
||||
- [ ] 机构中介数据导入成功
|
||||
- [ ] 导入时更新已存在数据功能正常
|
||||
|
||||
**下拉框验证:**
|
||||
- [ ] 性别下拉框显示正确
|
||||
- [ ] 证件类型下拉框显示正确
|
||||
- [ ] 法定代表人证件类型下拉框显示正确
|
||||
- [ ] 主体类型下拉框显示正确
|
||||
- [ ] 企业性质下拉框显示正确
|
||||
|
||||
### 10.2 回归测试
|
||||
|
||||
- [ ] 权限控制正常
|
||||
- [ ] 表单验证规则生效
|
||||
- [ ] 错误提示信息正确
|
||||
- [ ] 响应式布局正常
|
||||
- [ ] 浏览器兼容性(Chrome/Firefox/Edge)
|
||||
|
||||
---
|
||||
|
||||
## 十一、风险与注意事项
|
||||
|
||||
### 11.1 兼容性风险
|
||||
|
||||
**影响范围**: 所有中介黑名单相关功能
|
||||
|
||||
**缓解措施**:
|
||||
1. 完整的功能测试覆盖
|
||||
2. 保留旧版代码备份
|
||||
3. 分步骤部署,先测试环境验证
|
||||
|
||||
### 11.2 数据风险
|
||||
|
||||
**风险点**: 字段名变更可能导致数据丢失
|
||||
|
||||
**缓解措施**:
|
||||
1. 确保后端已做好兼容处理
|
||||
2. 导出测试数据进行对比验证
|
||||
3. 增量导入测试
|
||||
|
||||
### 11.3 注意事项
|
||||
|
||||
1. **字段同步**: 确保前后端字段完全一致,不要遗留转换逻辑
|
||||
2. **类型判断**: 所有详情查询必须根据`intermediaryType`调用不同接口
|
||||
3. **验证规则**: 个人和实体中介的必填字段不同,需分别配置
|
||||
4. **下拉框复用**: 法定代表人证件类型可复用`certTypeOptions`
|
||||
|
||||
---
|
||||
|
||||
## 十二、实施建议
|
||||
|
||||
### 12.1 实施步骤
|
||||
|
||||
1. **第一阶段**: API层修改
|
||||
- 新增详情查询接口
|
||||
- 删除旧版统一接口
|
||||
- 验证接口调用正常
|
||||
|
||||
2. **第二阶段**: 主页面修改
|
||||
- 修改数据模型
|
||||
- 修改核心方法
|
||||
- 测试查询和删除功能
|
||||
|
||||
3. **第三阶段**: 组件修改
|
||||
- EditDialog组件字段重命名
|
||||
- DetailDialog组件字段重命名
|
||||
- ImportDialog组件URL修正
|
||||
- 测试新增和修改功能
|
||||
|
||||
4. **第四阶段**: 全面测试
|
||||
- 功能测试
|
||||
- 回归测试
|
||||
- 兼容性测试
|
||||
|
||||
### 12.2 回滚方案
|
||||
|
||||
如发现问题严重,可按以下步骤回滚:
|
||||
|
||||
1. 恢复API层接口
|
||||
2. 恢复前端文件备份
|
||||
3. 重启前端服务
|
||||
4. 清理浏览器缓存
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### 附录A: 相关文档
|
||||
|
||||
- [中介黑名单管理API文档-v2.0.md](../api/中介黑名单管理API文档-v2.0.md)
|
||||
- [中介黑名单后端设计文档.md](../docs/中介黑名单后端.md)
|
||||
|
||||
### 附录B: 变更历史
|
||||
|
||||
| 版本 | 日期 | 作者 | 变更说明 |
|
||||
|-----|------|------|---------|
|
||||
| v1.0 | 2026-02-05 | Claude | 初始版本,完成前端适配设计 |
|
||||
|
||||
### 附录C: 审批记录
|
||||
|
||||
| 角色 | 姓名 | 审批状态 | 日期 |
|
||||
|-----|------|---------|------|
|
||||
| 开发 | - | 待审批 | - |
|
||||
| 测试 | - | 待审批 | - |
|
||||
| 产品 | - | 待审批 | - |
|
||||
@@ -1,192 +0,0 @@
|
||||
import openpyxl
|
||||
from openpyxl import Workbook
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 机构名称前缀
|
||||
org_prefixes = [
|
||||
"北京", "上海", "广州", "深圳", "杭州", "成都", "重庆", "武汉", "西安", "南京",
|
||||
"天津", "苏州", "长沙", "郑州", "东莞", "青岛", "沈阳", "宁波", "厦门", "佛山"
|
||||
]
|
||||
|
||||
# 机构类型关键词
|
||||
org_types = [
|
||||
"投资咨询", "资产管理", "证券投资", "基金管理", "股权投资",
|
||||
"财富管理", "金融信息服务", "商务咨询", "企业咨询", "投资顾问"
|
||||
]
|
||||
|
||||
# 机构后缀
|
||||
org_suffixes = ["有限公司", "股份有限公司", "集团", "企业", "事务所"]
|
||||
|
||||
# 主体类型
|
||||
entity_types = ["企业", "事业单位", "社会组织"]
|
||||
|
||||
# 企业性质
|
||||
corp_natures = [
|
||||
"有限责任公司", "股份有限公司", "国有独资", "集体企业",
|
||||
"私营企业", "中外合资", "外商独资", "港澳台合资"
|
||||
]
|
||||
|
||||
# 行业分类
|
||||
industry_classes = ["金融业", "商务服务业", "科学研究和技术服务业"]
|
||||
|
||||
# 所属行业
|
||||
industries = [
|
||||
"货币金融服务", "资本市场服务", "保险业", "其他金融业",
|
||||
"企业管理服务", "法律服务", "咨询与调查", "广告业",
|
||||
"研究和试验发展", "专业技术服务业", "科技推广和应用服务业"
|
||||
]
|
||||
|
||||
# 证件类型
|
||||
id_types = ["身份证", "护照", "其他"]
|
||||
|
||||
# 统一社会信用代码生成(18位)
|
||||
def generate_credit_code():
|
||||
area_code = f"{random.randint(110000, 659900):06d}"
|
||||
org_code = ''.join([str(random.randint(0, 9)) for _ in range(9)])
|
||||
check_code = random.randint(0, 9)
|
||||
return f"{area_code}{org_code}{check_code}"
|
||||
|
||||
# 生成法定代表人姓名
|
||||
def generate_person_name():
|
||||
surnames = ["王", "李", "张", "刘", "陈", "杨", "黄", "赵", "周", "吴",
|
||||
"徐", "孙", "马", "胡", "朱", "郭", "何", "罗", "高", "林"]
|
||||
names1 = ["伟", "芳", "娜", "敏", "静", "丽", "强", "磊", "军", "洋",
|
||||
"勇", "艳", "杰", "娟", "涛", "明", "超", "秀英", "霞", "平"]
|
||||
names2 = ["", "刚", "英", "华", "文", "平", "建", "国", "志", "海"]
|
||||
return random.choice(surnames) + random.choice(names1) + random.choice(names2)
|
||||
|
||||
# 生成身份证号(18位)
|
||||
def generate_id_card():
|
||||
# 地区码(6位) + 出生日期(8位) + 顺序码(3位) + 校验码(1位)
|
||||
area_code = f"{random.randint(110000, 659900):06d}"
|
||||
year = random.randint(1960, 1995)
|
||||
month = f"{random.randint(1, 12):02d}"
|
||||
day = f"{random.randint(1, 28):02d}"
|
||||
birth_date = f"{year}{month}{day}"
|
||||
sequence = f"{random.randint(1, 999):03d}"
|
||||
check_code = random.randint(0, 9)
|
||||
return f"{area_code}{birth_date}{sequence}{check_code}"
|
||||
|
||||
# 生成注册地址
|
||||
def generate_address():
|
||||
districts = ["朝阳区", "海淀区", "西城区", "东城区", "丰台区",
|
||||
"浦东新区", "黄浦区", "静安区", "徐汇区", "天河区",
|
||||
"福田区", "南山区", "罗湖区", "西湖区", "江干区"]
|
||||
streets = ["建设路", "人民路", "解放路", "和平路", "文化路",
|
||||
"科技路", "创新路", "发展路", "创业路", "工业路"]
|
||||
buildings = ["大厦", "中心", "广场", "写字楼", "科技园"]
|
||||
return f"{random.choice(districts)}{random.choice(streets)}{random.randint(1,999)}号{random.choice(buildings)}"
|
||||
|
||||
# 生成成立日期
|
||||
def generate_establish_date():
|
||||
start_date = datetime(2000, 1, 1)
|
||||
end_date = datetime(2024, 12, 31)
|
||||
days_between = (end_date - start_date).days
|
||||
random_days = random.randint(0, days_between)
|
||||
return (start_date + timedelta(days=random_days)).strftime("%Y-%m-%d")
|
||||
|
||||
# 生成股东名称
|
||||
def generate_shareholder():
|
||||
types = [
|
||||
lambda: f"{random.choice(org_prefixes)}{random.choice(['投资', '资本', '控股', '集团'])}有限公司",
|
||||
lambda: generate_person_name() + random.choice(["", "(自然人)"])
|
||||
]
|
||||
return random.choice(types)()
|
||||
|
||||
# 生成备注
|
||||
def generate_remark():
|
||||
remarks = [
|
||||
"", "", "", "",
|
||||
"重点监控", "已整改", "存在风险", "待核查"
|
||||
]
|
||||
return random.choice(remarks)
|
||||
|
||||
# 生成单条机构数据
|
||||
def generate_org_data(index):
|
||||
# 随机决定有几个股东(1-5个)
|
||||
shareholder_count = random.randint(1, 5)
|
||||
shareholders = [generate_shareholder() for _ in range(shareholder_count)]
|
||||
# 补齐到5个
|
||||
while len(shareholders) < 5:
|
||||
shareholders.append("")
|
||||
|
||||
# 证件类型
|
||||
id_type = random.choice(id_types)
|
||||
id_card = generate_id_card() if id_type == "身份证" else f"{random.choice(['A', 'B', 'C'])}{random.randint(10000, 99999)}"
|
||||
|
||||
return {
|
||||
"id": index,
|
||||
"orgName": f"{random.choice(org_prefixes)}{random.choice(org_types)}{random.choice(org_suffixes)}",
|
||||
"creditCode": generate_credit_code(),
|
||||
"entityType": random.choice(entity_types),
|
||||
"corpNature": random.choice(corp_natures) if random.choice([True, False]) else "",
|
||||
"industryClass": random.choice(industry_classes),
|
||||
"industry": random.choice(industries),
|
||||
"establishDate": generate_establish_date(),
|
||||
"regAddress": generate_address(),
|
||||
"legalRep": generate_person_name(),
|
||||
"legalRepIdType": id_type,
|
||||
"legalRepIdNo": id_card,
|
||||
"shareholder1": shareholders[0],
|
||||
"shareholder2": shareholders[1],
|
||||
"shareholder3": shareholders[2],
|
||||
"shareholder4": shareholders[3],
|
||||
"shareholder5": shareholders[4],
|
||||
"remark": generate_remark()
|
||||
}
|
||||
|
||||
# 生成数据并保存到Excel
|
||||
def generate_org_test_data(filename, count=1000, start_id=1):
|
||||
# 读取模板获取表头
|
||||
template_path = "机构中介黑名单模板_1769674571626.xlsx"
|
||||
template_wb = openpyxl.load_workbook(template_path)
|
||||
template_ws = template_wb.active
|
||||
|
||||
# 创建新工作簿
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "机构中介黑名单"
|
||||
|
||||
# 复制表头
|
||||
for cell in template_ws[1]:
|
||||
new_cell = ws.cell(row=1, column=cell.column, value=cell.value)
|
||||
|
||||
# 生成数据
|
||||
data_list = []
|
||||
for i in range(count):
|
||||
data = generate_org_data(start_id + i)
|
||||
data_list.append(data)
|
||||
|
||||
# 按照模板列顺序写入数据
|
||||
# 列顺序:机构名称、统一社会信用代码、主体类型、企业性质、行业分类、所属行业、
|
||||
# 成立日期、注册地址、法定代表人、法定代表人证件类型、法定代表人证件号码、
|
||||
# 股东1、股东2、股东3、股东4、股东5、备注
|
||||
for row_idx, data in enumerate(data_list, start=2):
|
||||
ws.cell(row=row_idx, column=1, value=data["orgName"])
|
||||
ws.cell(row=row_idx, column=2, value=data["creditCode"])
|
||||
ws.cell(row=row_idx, column=3, value=data["entityType"])
|
||||
ws.cell(row=row_idx, column=4, value=data["corpNature"])
|
||||
ws.cell(row=row_idx, column=5, value=data["industryClass"])
|
||||
ws.cell(row=row_idx, column=6, value=data["industry"])
|
||||
ws.cell(row=row_idx, column=7, value=data["establishDate"])
|
||||
ws.cell(row=row_idx, column=8, value=data["regAddress"])
|
||||
ws.cell(row=row_idx, column=9, value=data["legalRep"])
|
||||
ws.cell(row=row_idx, column=10, value=data["legalRepIdType"])
|
||||
ws.cell(row=row_idx, column=11, value=data["legalRepIdNo"])
|
||||
ws.cell(row=row_idx, column=12, value=data["shareholder1"])
|
||||
ws.cell(row=row_idx, column=13, value=data["shareholder2"])
|
||||
ws.cell(row=row_idx, column=14, value=data["shareholder3"])
|
||||
ws.cell(row=row_idx, column=15, value=data["shareholder4"])
|
||||
ws.cell(row=row_idx, column=16, value=data["shareholder5"])
|
||||
ws.cell(row=row_idx, column=17, value=data["remark"])
|
||||
|
||||
# 保存文件
|
||||
wb.save(filename)
|
||||
print(f"已生成文件: {filename}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("开始生成机构中介黑名单测试数据...")
|
||||
generate_org_test_data("机构中介黑名单测试数据_1000条.xlsx", 1000, 1)
|
||||
generate_org_test_data("机构中介黑名单测试数据_1000条_第2批.xlsx", 1000, 1001)
|
||||
print("完成!")
|
||||
BIN
doc/test-data/intermediary/entity_1770260448522.xlsx
Normal file
BIN
doc/test-data/intermediary/entity_1770260448522.xlsx
Normal file
Binary file not shown.
181
doc/test-data/intermediary/generate_1000_entity_data.py
Normal file
181
doc/test-data/intermediary/generate_1000_entity_data.py
Normal file
@@ -0,0 +1,181 @@
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
|
||||
# 机构名称前缀
|
||||
company_prefixes = ['北京市', '上海市', '广州市', '深圳市', '杭州市', '成都市', '武汉市', '南京市', '西安市', '重庆市']
|
||||
company_keywords = ['房产', '地产', '置业', '中介', '经纪', '咨询', '投资', '资产', '物业', '不动产']
|
||||
company_suffixes = ['有限公司', '股份有限公司', '集团', '企业', '合伙企业', '有限责任公司']
|
||||
|
||||
# 主体类型
|
||||
entity_types = ['企业', '个体工商户', '农民专业合作社', '其他组织']
|
||||
|
||||
# 企业性质
|
||||
enterprise_natures = ['国有企业', '集体企业', '私营企业', '混合所有制企业', '外商投资企业', '港澳台投资企业']
|
||||
|
||||
# 行业分类
|
||||
industry_classes = ['房地产业', '金融业', '租赁和商务服务业', '建筑业', '批发和零售业']
|
||||
|
||||
# 所属行业
|
||||
industry_names = [
|
||||
'房地产中介服务', '房地产经纪', '房地产开发经营', '物业管理',
|
||||
'投资咨询', '资产管理', '商务咨询', '市场调查',
|
||||
'建筑工程', '装饰装修', '园林绿化'
|
||||
]
|
||||
|
||||
# 法定代表人姓名
|
||||
surnames = ['王', '李', '张', '刘', '陈', '杨', '黄', '赵', '周', '吴', '徐', '孙', '马', '胡', '朱', '郭', '何', '罗', '高', '林']
|
||||
given_names = ['伟', '芳', '娜', '敏', '静', '丽', '强', '磊', '军', '洋', '勇', '艳', '杰', '娟', '涛', '明', '超', '秀英', '霞', '平']
|
||||
|
||||
# 证件类型
|
||||
cert_types = ['身份证', '护照', '港澳通行证', '台胞证', '其他']
|
||||
|
||||
# 常用地址
|
||||
provinces = ['北京市', '上海市', '广东省', '浙江省', '江苏省', '四川省', '湖北省', '河南省', '山东省', '福建省']
|
||||
cities = ['朝阳区', '海淀区', '浦东新区', '黄浦区', '天河区', '福田区', '西湖区', '滨江区', '鼓楼区', '玄武区',
|
||||
'武侯区', '江汉区', '金水区', '市南区', '思明区']
|
||||
districts = ['街道', '大道', '路', '巷', '小区', '花园', '广场', '大厦']
|
||||
street_numbers = ['1号', '2号', '3号', '88号', '66号', '108号', '188号', '888号', '666号', '168号']
|
||||
|
||||
# 股东姓名
|
||||
shareholder_names = [
|
||||
'张伟', '李芳', '王强', '刘军', '陈静', '杨洋', '黄勇', '赵艳',
|
||||
'周杰', '吴娟', '徐涛', '孙明', '马超', '胡秀英', '朱霞', '郭平',
|
||||
'何桂英', '罗玉兰', '高萍', '林毅', '王浩', '李宇', '张轩', '刘然'
|
||||
]
|
||||
|
||||
def generate_company_name():
|
||||
"""生成机构名称"""
|
||||
prefix = random.choice(company_prefixes)
|
||||
keyword = random.choice(company_keywords)
|
||||
suffix = random.choice(company_suffixes)
|
||||
return f"{prefix}{keyword}{suffix}"
|
||||
|
||||
def generate_social_credit_code():
|
||||
"""生成统一社会信用代码(18位)"""
|
||||
# 统一社会信用代码规则:18位,第一位为登记管理部门代码(1-5),第二位为机构类别代码(1-9)
|
||||
dept_code = random.choice(['1', '2', '3', '4', '5'])
|
||||
org_code = random.choice(['1', '2', '3', '4', '5', '6', '7', '8', '9'])
|
||||
rest = ''.join([str(random.randint(0, 9)) for _ in range(16)])
|
||||
return f"{dept_code}{org_code}{rest}"
|
||||
|
||||
def generate_id_card():
|
||||
"""生成身份证号码(18位,简化版)"""
|
||||
# 地区码(前6位)
|
||||
area_code = f"{random.randint(110000, 650000):06d}"
|
||||
# 出生日期(8位)
|
||||
birth_year = random.randint(1960, 1990)
|
||||
birth_month = f"{random.randint(1, 12):02d}"
|
||||
birth_day = f"{random.randint(1, 28):02d}"
|
||||
birth_date = f"{birth_year}{birth_month}{birth_day}"
|
||||
# 顺序码(3位)
|
||||
sequence = f"{random.randint(1, 999):03d}"
|
||||
# 校验码(1位)
|
||||
check_code = random.randint(0, 9)
|
||||
return f"{area_code}{birth_date}{sequence}{check_code}"
|
||||
|
||||
def generate_other_id():
|
||||
"""生成其他证件号码"""
|
||||
return f"{random.randint(10000000, 99999999):08d}"
|
||||
|
||||
def generate_register_address():
|
||||
"""生成注册地址"""
|
||||
province = random.choice(provinces)
|
||||
city = random.choice(cities)
|
||||
district = random.choice(districts)
|
||||
number = random.choice(street_numbers)
|
||||
return f"{province}{city}{district}{number}"
|
||||
|
||||
def generate_establish_date():
|
||||
"""生成成立日期(2000-2024年之间)"""
|
||||
start_date = datetime(2000, 1, 1)
|
||||
end_date = datetime(2024, 12, 31)
|
||||
time_between = end_date - start_date
|
||||
days_between = time_between.days
|
||||
random_days = random.randrange(days_between)
|
||||
return start_date + timedelta(days=random_days)
|
||||
|
||||
def generate_legal_representative():
|
||||
"""生成法定代表人"""
|
||||
name = random.choice(surnames) + random.choice(given_names)
|
||||
cert_type = random.choice(cert_types)
|
||||
cert_no = generate_id_card() if cert_type == '身份证' else generate_other_id()
|
||||
return name, cert_type, cert_no
|
||||
|
||||
def generate_shareholders():
|
||||
"""生成股东列表(1-5个股东)"""
|
||||
shareholder_count = random.randint(1, 5)
|
||||
selected_shareholders = random.sample(shareholder_names, shareholder_count)
|
||||
shareholders = [None] * 5
|
||||
for i, shareholder in enumerate(selected_shareholders):
|
||||
shareholders[i] = shareholder
|
||||
return shareholders
|
||||
|
||||
def generate_entity(index):
|
||||
"""生成单条机构中介数据"""
|
||||
# 基本信息
|
||||
enterprise_name = generate_company_name()
|
||||
social_credit_code = generate_social_credit_code()
|
||||
entity_type = random.choice(entity_types)
|
||||
enterprise_nature = random.choice(enterprise_natures)
|
||||
industry_class = random.choice(industry_classes)
|
||||
industry_name = random.choice(industry_names)
|
||||
|
||||
# 成立日期
|
||||
establish_date = generate_establish_date()
|
||||
|
||||
# 注册地址
|
||||
register_address = generate_register_address()
|
||||
|
||||
# 法定代表人信息
|
||||
legal_name, legal_cert_type, legal_cert_no = generate_legal_representative()
|
||||
|
||||
# 股东
|
||||
shareholders = generate_shareholders()
|
||||
|
||||
return {
|
||||
'机构名称*': enterprise_name,
|
||||
'统一社会信用代码*': social_credit_code,
|
||||
'主体类型': entity_type,
|
||||
'企业性质': enterprise_nature if random.random() > 0.3 else '',
|
||||
'行业分类': industry_class if random.random() > 0.3 else '',
|
||||
'所属行业': industry_name if random.random() > 0.2 else '',
|
||||
'成立日期': establish_date.strftime('%Y-%m-%d') if random.random() > 0.4 else '',
|
||||
'注册地址': register_address,
|
||||
'法定代表人': legal_name,
|
||||
'法定代表人证件类型': legal_cert_type,
|
||||
'法定代表人证件号码': legal_cert_no,
|
||||
'股东1': shareholders[0] if shareholders[0] else '',
|
||||
'股东2': shareholders[1] if shareholders[1] else '',
|
||||
'股东3': shareholders[2] if shareholders[2] else '',
|
||||
'股东4': shareholders[3] if shareholders[3] else '',
|
||||
'股东5': shareholders[4] if shareholders[4] else '',
|
||||
'备注': f'测试数据{index}' if random.random() > 0.5 else ''
|
||||
}
|
||||
|
||||
# 生成第一个1000条数据
|
||||
print("正在生成第一批1000条机构中介黑名单数据...")
|
||||
data = [generate_entity(i) for i in range(1, 1001)]
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
# 保存第一个文件
|
||||
output1 = r'D:\ccdi\ccdi\doc\test-data\intermediary\机构中介黑名单测试数据_1000条_第1批.xlsx'
|
||||
df.to_excel(output1, index=False, engine='openpyxl')
|
||||
print(f"已生成第一个文件: {output1}")
|
||||
|
||||
# 生成第二个1000条数据
|
||||
print("正在生成第二批1000条机构中介黑名单数据...")
|
||||
data2 = [generate_entity(i) for i in range(1, 1001)]
|
||||
df2 = pd.DataFrame(data2)
|
||||
|
||||
# 保存第二个文件
|
||||
output2 = r'D:\ccdi\ccdi\doc\test-data\intermediary\机构中介黑名单测试数据_1000条_第2批.xlsx'
|
||||
df2.to_excel(output2, index=False, engine='openpyxl')
|
||||
print(f"已生成第二个文件: {output2}")
|
||||
|
||||
print("\n✅ 生成完成!")
|
||||
print(f"文件1: {output1}")
|
||||
print(f"文件2: {output2}")
|
||||
print(f"\n每个文件包含1000条测试数据")
|
||||
print(f"数据格式与CcdiIntermediaryEntityExcel.java定义一致")
|
||||
110
doc/test-data/intermediary/generate_1000_intermediary_data.py
Normal file
110
doc/test-data/intermediary/generate_1000_intermediary_data.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
|
||||
# 常用姓氏和名字
|
||||
surnames = ['王', '李', '张', '刘', '陈', '杨', '黄', '赵', '周', '吴', '徐', '孙', '马', '胡', '朱', '郭', '何', '罗', '高', '林']
|
||||
given_names = ['伟', '芳', '娜', '敏', '静', '丽', '强', '磊', '军', '洋', '勇', '艳', '杰', '娟', '涛', '明', '超', '秀英', '霞', '平', '刚', '桂英', '玉兰', '萍', '毅', '浩', '宇', '轩', '然', '凯']
|
||||
|
||||
# 人员类型
|
||||
person_types = ['中介', '职业背债人', '房产中介']
|
||||
person_sub_types = ['本人', '配偶', '子女', '其他']
|
||||
genders = ['M', 'F', 'O']
|
||||
id_types = ['身份证', '护照', '港澳通行证', '台胞证', '军官证']
|
||||
relation_types = ['配偶', '子女', '父母', '兄弟姐妹', '其他']
|
||||
|
||||
# 常用地址
|
||||
provinces = ['北京市', '上海市', '广东省', '浙江省', '江苏省', '四川省', '湖北省', '河南省', '山东省', '福建省']
|
||||
cities = ['朝阳区', '海淀区', '浦东新区', '黄浦区', '天河区', '福田区', '西湖区', '滨江区', '鼓楼区', '玄武区']
|
||||
districts = ['街道1号', '大道2号', '路3号', '巷4号', '小区5栋', '花园6号', '广场7号', '大厦8号楼']
|
||||
|
||||
# 公司和职位
|
||||
companies = ['房产中介有限公司', '置业咨询公司', '房产经纪公司', '地产代理公司', '不动产咨询公司', '房屋租赁公司', '物业管理公司', '投资咨询公司']
|
||||
positions = ['房产经纪人', '销售经理', '业务员', '置业顾问', '店长', '区域经理', '高级经纪人', '项目经理']
|
||||
|
||||
# 生成身份证号码(简化版,仅用于测试)
|
||||
def generate_id_card():
|
||||
# 地区码(前6位)
|
||||
area_code = f"{random.randint(110000, 650000):06d}"
|
||||
# 出生日期(8位)
|
||||
birth_year = random.randint(1960, 2000)
|
||||
birth_month = f"{random.randint(1, 12):02d}"
|
||||
birth_day = f"{random.randint(1, 28):02d}"
|
||||
birth_date = f"{birth_year}{birth_month}{birth_day}"
|
||||
# 顺序码(3位)
|
||||
sequence = f"{random.randint(1, 999):03d}"
|
||||
# 校验码(1位)
|
||||
check_code = random.randint(0, 9)
|
||||
return f"{area_code}{birth_date}{sequence}{check_code}"
|
||||
|
||||
# 生成手机号
|
||||
def generate_phone():
|
||||
second_digits = ['3', '5', '7', '8', '9']
|
||||
second = random.choice(second_digits)
|
||||
return f"1{second}{''.join([str(random.randint(0, 9)) for _ in range(9)])}"
|
||||
|
||||
# 生成统一信用代码
|
||||
def generate_credit_code():
|
||||
return f"91{''.join([str(random.randint(0, 9)) for _ in range(16)])}"
|
||||
|
||||
# 生成微信号
|
||||
def generate_wechat():
|
||||
return f"wx_{''.join([random.choice(string.ascii_lowercase + string.digits) for _ in range(8)])}"
|
||||
|
||||
# 生成单条数据
|
||||
def generate_person(index):
|
||||
person_type = random.choice(person_types)
|
||||
gender = random.choice(genders)
|
||||
|
||||
# 根据性别选择更合适的名字
|
||||
if gender == 'M':
|
||||
name = random.choice(surnames) + random.choice(['伟', '强', '磊', '军', '勇', '杰', '涛', '明', '超', '毅', '浩', '宇', '轩'])
|
||||
else:
|
||||
name = random.choice(surnames) + random.choice(['芳', '娜', '敏', '静', '丽', '艳', '娟', '秀英', '霞', '平', '桂英', '玉兰', '萍'])
|
||||
|
||||
id_type = random.choice(id_types)
|
||||
id_card = generate_id_card() if id_type == '身份证' else f"{random.randint(10000000, 99999999):08d}"
|
||||
|
||||
return {
|
||||
'姓名': name,
|
||||
'人员类型': person_type,
|
||||
'人员子类型': random.choice(person_sub_types),
|
||||
'性别': gender,
|
||||
'证件类型': id_type,
|
||||
'证件号码': id_card,
|
||||
'手机号码': generate_phone(),
|
||||
'微信号': generate_wechat() if random.random() > 0.3 else '',
|
||||
'联系地址': f"{random.choice(provinces)}{random.choice(cities)}{random.choice(districts)}",
|
||||
'所在公司': random.choice(companies) if random.random() > 0.2 else '',
|
||||
'企业统一信用码': generate_credit_code() if random.random() > 0.5 else '',
|
||||
'职位': random.choice(positions) if random.random() > 0.3 else '',
|
||||
'关联人员ID': f"ID{random.randint(10000, 99999)}" if random.random() > 0.6 else '',
|
||||
'关系类型': random.choice(relation_types) if random.random() > 0.6 else '',
|
||||
'备注': f'测试数据{index}' if random.random() > 0.5 else ''
|
||||
}
|
||||
|
||||
# 生成1000条数据
|
||||
print("正在生成1000条个人中介黑名单数据...")
|
||||
data = [generate_person(i) for i in range(1, 1001)]
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
# 保存第一个文件
|
||||
output1 = r'D:\ccdi\ccdi\doc\test-data\intermediary\个人中介黑名单测试数据_1000条_第1批.xlsx'
|
||||
df.to_excel(output1, index=False)
|
||||
print(f"已生成第一个文件: {output1}")
|
||||
|
||||
# 生成第二个1000条数据
|
||||
print("正在生成第二批1000条个人中介黑名单数据...")
|
||||
data2 = [generate_person(i) for i in range(1, 1001)]
|
||||
df2 = pd.DataFrame(data2)
|
||||
|
||||
# 保存第二个文件
|
||||
output2 = r'D:\ccdi\ccdi\doc\test-data\intermediary\个人中介黑名单测试数据_1000条_第2批.xlsx'
|
||||
df2.to_excel(output2, index=False)
|
||||
print(f"已生成第二个文件: {output2}")
|
||||
|
||||
print("\n生成完成!")
|
||||
print(f"文件1: {output1}")
|
||||
print(f"文件2: {output2}")
|
||||
print(f"\n每个文件包含1000条测试数据")
|
||||
BIN
doc/test-data/intermediary/个人中介黑名单模板_1770258896626.xlsx
Normal file
BIN
doc/test-data/intermediary/个人中介黑名单模板_1770258896626.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/intermediary/个人中介黑名单测试数据_1000条_第1批.xlsx
Normal file
BIN
doc/test-data/intermediary/个人中介黑名单测试数据_1000条_第1批.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/intermediary/个人中介黑名单测试数据_1000条_第2批.xlsx
Normal file
BIN
doc/test-data/intermediary/个人中介黑名单测试数据_1000条_第2批.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/intermediary/机构中介黑名单测试数据_1000条_第1批.xlsx
Normal file
BIN
doc/test-data/intermediary/机构中介黑名单测试数据_1000条_第1批.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/intermediary/机构中介黑名单测试数据_1000条_第2批.xlsx
Normal file
BIN
doc/test-data/intermediary/机构中介黑名单测试数据_1000条_第2批.xlsx
Normal file
Binary file not shown.
@@ -1,268 +0,0 @@
|
||||
"""
|
||||
中介黑名单导入功能测试脚本
|
||||
|
||||
测试目标:
|
||||
1. 验证机构中介导入时 certificate_no 字段不能为 null 的修复
|
||||
2. 验证个人中介导入功能正常
|
||||
3. 验证更新模式功能正常
|
||||
|
||||
测试数据准备:
|
||||
- 个人中介:2条记录
|
||||
- 机构中介:2条记录
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
BASE_URL = "http://localhost:8080"
|
||||
|
||||
def login():
|
||||
"""登录并获取token"""
|
||||
url = f"{BASE_URL}/login/test"
|
||||
data = {
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
response = requests.post(url, json=data)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("code") == 200:
|
||||
token = result.get("token")
|
||||
print(f"✓ 登录成功,获取token: {token[:20]}...")
|
||||
return token
|
||||
print(f"✗ 登录失败: {response.text}")
|
||||
return None
|
||||
|
||||
def get_headers(token):
|
||||
"""获取请求头"""
|
||||
return {
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
|
||||
def test_import_person_intermediary(token):
|
||||
"""测试个人中介导入"""
|
||||
print("\n" + "="*60)
|
||||
print("测试1: 个人中介导入功能")
|
||||
print("="*60)
|
||||
|
||||
# 准备个人中介数据(直接通过API调用测试)
|
||||
url = f"{BASE_URL}/dpc/intermediary"
|
||||
headers = get_headers(token)
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
person_data = {
|
||||
"name": "测试个人中介",
|
||||
"certificateNo": "110101199001011234",
|
||||
"intermediaryType": "1",
|
||||
"status": "0",
|
||||
"remark": "测试个人中介导入",
|
||||
"indivType": "中介",
|
||||
"indivSubType": "本人",
|
||||
"indivGender": "M",
|
||||
"indivCertType": "身份证",
|
||||
"indivPhone": "13800138000",
|
||||
"indivWechat": "test_wx_id",
|
||||
"indivAddress": "北京市朝阳区",
|
||||
"indivCompany": "测试公司",
|
||||
"indivPosition": "经纪人"
|
||||
}
|
||||
|
||||
response = requests.post(url, json=person_data, headers=headers)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("code") == 200:
|
||||
print("✓ 个人中介导入成功")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 个人中介导入失败: {result.get('msg')}")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ 个人中介导入请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
def test_import_entity_intermediary(token):
|
||||
"""测试机构中介导入"""
|
||||
print("\n" + "="*60)
|
||||
print("测试2: 机构中介导入功能")
|
||||
print("="*60)
|
||||
|
||||
# 准备机构中介数据
|
||||
url = f"{BASE_URL}/dpc/intermediary"
|
||||
headers = get_headers(token)
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
entity_data = {
|
||||
"name": "测试机构中介有限公司",
|
||||
"certificateNo": "91110108MA0000001A", # 统一社会信用代码
|
||||
"intermediaryType": "2",
|
||||
"status": "0",
|
||||
"remark": "测试机构中介导入",
|
||||
"corpCreditCode": "91110108MA0000001A",
|
||||
"corpType": "有限责任公司",
|
||||
"corpNature": "民营企业",
|
||||
"corpIndustryCategory": "房地产业",
|
||||
"corpIndustry": "房地产中介服务",
|
||||
"corpEstablishDate": "2020-01-01",
|
||||
"corpAddress": "北京市海淀区",
|
||||
"corpLegalRep": "张三",
|
||||
"corpLegalCertType": "身份证",
|
||||
"corpLegalCertNo": "110101199001011235",
|
||||
"corpShareholder1": "李四",
|
||||
"corpShareholder2": "王五"
|
||||
}
|
||||
|
||||
response = requests.post(url, json=entity_data, headers=headers)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("code") == 200:
|
||||
print("✓ 机构中介导入成功")
|
||||
print(f" - 机构名称: {entity_data['name']}")
|
||||
print(f" - 统一社会信用代码: {entity_data['corpCreditCode']}")
|
||||
print(f" - 证件号字段: {entity_data['certificateNo']}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 机构中介导入失败: {result.get('msg')}")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ 机构中介导入请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
def test_import_entity_without_credit_code(token):
|
||||
"""测试机构中介导入时统一社会信用代码为空的情况"""
|
||||
print("\n" + "="*60)
|
||||
print("测试4: 机构中介导入时统一社会信用代码为空(应该失败)")
|
||||
print("="*60)
|
||||
|
||||
url = f"{BASE_URL}/dpc/intermediary"
|
||||
headers = get_headers(token)
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
# 故意不提供统一社会信用代码
|
||||
entity_data = {
|
||||
"name": "测试机构中介有限公司(无信用代码)",
|
||||
"certificateNo": "", # 空字符串
|
||||
"intermediaryType": "2",
|
||||
"status": "0",
|
||||
"remark": "测试统一社会信用代码为空的情况",
|
||||
"corpCreditCode": "", # 空字符串
|
||||
"corpType": "有限责任公司"
|
||||
}
|
||||
|
||||
response = requests.post(url, json=entity_data, headers=headers)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("code") != 200:
|
||||
# 预期失败
|
||||
print(f"✓ 预期行为:导入被拒绝,错误信息: {result.get('msg')}")
|
||||
return True
|
||||
else:
|
||||
# 不应该成功
|
||||
print(f"✗ 测试失败:统一社会信用代码为空时不应该导入成功")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ 请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
def test_query_intermediary_list(token):
|
||||
"""测试查询中介列表"""
|
||||
print("\n" + "="*60)
|
||||
print("测试3: 查询中介列表")
|
||||
print("="*60)
|
||||
|
||||
url = f"{BASE_URL}/dpc/intermediary/list"
|
||||
headers = get_headers(token)
|
||||
|
||||
params = {
|
||||
"pageNum": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get("code") == 200:
|
||||
rows = result.get("rows", [])
|
||||
total = result.get("total", 0)
|
||||
print(f"✓ 查询成功,共 {total} 条记录")
|
||||
for item in rows:
|
||||
print(f" - {item['name']} ({item.get('intermediaryTypeName', '未知')}) - 证件号: {item.get('certificateNo', '无')}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 查询失败: {result.get('msg')}")
|
||||
return False
|
||||
else:
|
||||
print(f"✗ 查询请求失败: {response.status_code} - {response.text}")
|
||||
return False
|
||||
|
||||
def generate_test_report(results):
|
||||
"""生成测试报告"""
|
||||
print("\n" + "="*60)
|
||||
print("测试报告")
|
||||
print("="*60)
|
||||
|
||||
total_tests = len(results)
|
||||
passed_tests = sum(1 for r in results.values() if r)
|
||||
failed_tests = total_tests - passed_tests
|
||||
|
||||
print(f"\n总测试数: {total_tests}")
|
||||
print(f"通过: {passed_tests}")
|
||||
print(f"失败: {failed_tests}")
|
||||
print(f"通过率: {passed_tests/total_tests*100:.1f}%")
|
||||
|
||||
print("\n详细结果:")
|
||||
for test_name, result in results.items():
|
||||
status = "✓ 通过" if result else "✗ 失败"
|
||||
print(f" {test_name}: {status}")
|
||||
|
||||
# 保存报告到文件
|
||||
report_content = {
|
||||
"测试时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"总测试数": total_tests,
|
||||
"通过": passed_tests,
|
||||
"失败": failed_tests,
|
||||
"通过率": f"{passed_tests/total_tests*100:.1f}%",
|
||||
"详细结果": {k: "通过" if v else "失败" for k, v in results.items()}
|
||||
}
|
||||
|
||||
with open("doc/test-data/import_test_report.json", "w", encoding="utf-8") as f:
|
||||
json.dump(report_content, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n测试报告已保存至: doc/test-data/import_test_report.json")
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("="*60)
|
||||
print("中介黑名单导入功能测试")
|
||||
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print("="*60)
|
||||
|
||||
results = {}
|
||||
|
||||
# 1. 登录
|
||||
token = login()
|
||||
if not token:
|
||||
print("登录失败,无法继续测试")
|
||||
return
|
||||
|
||||
# 2. 测试个人中介导入
|
||||
results["个人中介导入"] = test_import_person_intermediary(token)
|
||||
|
||||
# 3. 测试机构中介导入
|
||||
results["机构中介导入"] = test_import_entity_intermediary(token)
|
||||
|
||||
# 4. 测试统一社会信用代码为空的情况
|
||||
results["机构中介无信用代码校验"] = test_import_entity_without_credit_code(token)
|
||||
|
||||
# 5. 测试查询列表
|
||||
results["查询列表"] = test_query_intermediary_list(token)
|
||||
|
||||
# 5. 生成测试报告
|
||||
generate_test_report(results)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("测试完成")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,22 +0,0 @@
|
||||
字段中文名,数据类型,长度/精度,是否为空,默认值,说明
|
||||
统一社会信用代码,VARCHAR,18,是,-,统一社会信用代码
|
||||
主体名称,VARCHAR,200,否,-,企业注册名称
|
||||
主体类型,VARCHAR,50,否,-,企业类型:有限责任公司、股份有限公司、合伙企业、个体工商户、外资企业等
|
||||
企业性质,VARCHAR,50,是,-,国企、民企、外企、合资、其他
|
||||
行业分类,VARCHAR,100,是,-,行业分类代码或名称
|
||||
所属行业,VARCHAR,100,是,-,所属行业
|
||||
成立日期,DATE,-,是,-,企业成立日期
|
||||
注册地址,VARCHAR,500,是,-,工商注册地址
|
||||
法定代表人,VARCHAR,50,是,-,法定代表人姓名
|
||||
法定代表人证件类型,VARCHAR,30,是,-,法定代表人证件类型
|
||||
法定代表人证件号码,VARCHAR,30,是,-,法定代表人证件号码
|
||||
股东1,VARCHAR,30,是,-,股东姓名
|
||||
股东2,VARCHAR,30,是,-,股东姓名
|
||||
股东3,VARCHAR,30,是,-,股东姓名
|
||||
股东4,VARCHAR,30,是,-,股东姓名
|
||||
股东5,VARCHAR,30,是,-,股东姓名
|
||||
创建时间,DATETIME,-,否,当前时间,记录创建时间
|
||||
更新时间,DATETIME,-,否,当前时间,记录更新时间
|
||||
创建人,VARCHAR,50,否,-,记录创建人
|
||||
更新人,VARCHAR,50,是,-,记录更新人
|
||||
数据来源,VARCHAR,30,是,MANUAL,"MANUAL:手动录入, SYSTEM:系统同步, API:接口获取, IMPORT:批量导入"
|
||||
|
@@ -1,20 +0,0 @@
|
||||
字段中文名,数据类型,长度/精度,是否为空,默认值,说明
|
||||
人员ID,VARCHAR,20,否,-,中介、职业背债人、房产中介等
|
||||
人员类型,VARCHAR,30,否,-,中介、职业背债人、房产中介等
|
||||
人员子类型,VARCHAR,50,是,-,如:本人、配偶等
|
||||
姓名,VARCHAR,50,否,-,人员姓名
|
||||
性别,CHAR,1,是,-,"M:男, F:女, O:其他"
|
||||
证件类型,VARCHAR,30,否,身份证,身份证、护照、港澳通行证、台胞证、军官证等
|
||||
证件号码,VARCHAR,30,否,-,证件号码(加密存储)
|
||||
手机号码,VARCHAR,20,是,-,手机号码(加密存储)
|
||||
微信号,VARCHAR,50,是,-,微信号
|
||||
联系地址,VARCHAR,200,是,-,详细联系地址
|
||||
所在公司,VARCHAR,100,是,-,当前就职公司
|
||||
职位,VARCHAR,100,是,-,职位/职务
|
||||
关联人员ID,VARCHAR,20,是,-,关联“人员ID”
|
||||
关联关系,VARCHAR,50,是,-,与关联员工的关系
|
||||
创建时间,DATETIME,-,否,当前时间,记录创建时间
|
||||
更新时间,DATETIME,-,否,当前时间,记录更新时间
|
||||
创建人,VARCHAR,50,否,-,记录创建人
|
||||
更新人,VARCHAR,50,是,-,记录更新人
|
||||
数据来源,VARCHAR,30,是,MANUAL,"MANUAL:手动录入, SYSTEM:系统同步, IMPORT:批量导入, API:接口获取"
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
312
doc/优化说明/中介黑名单导入唯一性校验优化说明_20260205.md
Normal file
312
doc/优化说明/中介黑名单导入唯一性校验优化说明_20260205.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# 中介黑名单导入唯一性校验优化说明
|
||||
|
||||
## 优化时间
|
||||
2026-02-05
|
||||
|
||||
## 优化目的
|
||||
优化批量导入中介黑名单数据时的唯一性校验性能,解决N+1查询问题。
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 原实现问题
|
||||
在导入个人中介和实体中介数据时,原实现存在以下性能问题:
|
||||
|
||||
1. **N+1查询问题**
|
||||
- 在循环中对每条记录调用 `checkPersonIdUnique` 或 `checkSocialCreditCodeUnique`
|
||||
- 导入1000条数据时,产生1000次数据库查询
|
||||
- 代码位置:
|
||||
- `CcdiIntermediaryServiceImpl.importIntermediaryPerson:291`
|
||||
- `CcdiIntermediaryServiceImpl.importIntermediaryEntity:409`
|
||||
|
||||
2. **重复查询问题**
|
||||
- 唯一性校验查询一次(1000次)
|
||||
- 获取bizId再次批量查询一次(1次)
|
||||
- 总计1001次数据库查询
|
||||
|
||||
3. **性能瓶颈**
|
||||
- 大量数据导入时响应慢
|
||||
- 数据库连接占用时间长
|
||||
- 网络往返次数多
|
||||
|
||||
## 优化方案
|
||||
|
||||
### 核心思路
|
||||
**将"循环中逐条查询"改为"一次性批量查询,内存中快速判断"**
|
||||
|
||||
### 优化实现
|
||||
|
||||
#### 1. 个人中介导入优化(importIntermediaryPerson)
|
||||
|
||||
**优化前:**
|
||||
```java
|
||||
// 第一轮:数据验证和分类
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 检查唯一性 - 每次循环都查询数据库
|
||||
if (!checkPersonIdUnique(excel.getPersonId(), null)) { // ❌ N+1查询
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// 第二轮:批量处理
|
||||
if (!updateList.isEmpty()) {
|
||||
// 再次查询已存在记录的bizId - 重复查询
|
||||
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
|
||||
List<CcdiBizIntermediary> existingList = bizIntermediaryMapper.selectList(wrapper);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**优化后:**
|
||||
```java
|
||||
// 第一轮:收集所有personId
|
||||
for (CcdiIntermediaryPersonExcel excel : list) {
|
||||
if (StringUtils.isNotEmpty(excel.getPersonId())) {
|
||||
personIds.add(excel.getPersonId());
|
||||
}
|
||||
}
|
||||
|
||||
// 第二轮:批量查询已存在的记录 - 只查询一次 ✅
|
||||
java.util.Map<String, String> personIdToBizIdMap = new java.util.HashMap<>();
|
||||
if (!personIds.isEmpty()) {
|
||||
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBizIntermediary::getBizId, CcdiBizIntermediary::getPersonId);
|
||||
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
|
||||
List<CcdiBizIntermediary> existingList = bizIntermediaryMapper.selectList(wrapper);
|
||||
|
||||
// 建立personId到bizId的映射
|
||||
for (CcdiBizIntermediary existing : existingList) {
|
||||
personIdToBizIdMap.put(existing.getPersonId(), existing.getBizId());
|
||||
}
|
||||
}
|
||||
|
||||
// 第三轮:数据验证和分类 - 使用Map快速判断
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 使用Map快速判断是否存在 - O(1)复杂度,不查询数据库 ✅
|
||||
String existingBizId = personIdToBizIdMap.get(excel.getPersonId());
|
||||
if (existingBizId != null) {
|
||||
// 记录已存在
|
||||
if (updateSupport) {
|
||||
person.setBizId(existingBizId); // 直接使用缓存中的bizId
|
||||
updateList.add(person);
|
||||
}
|
||||
} else {
|
||||
insertList.add(person);
|
||||
}
|
||||
}
|
||||
|
||||
// 第四轮:批量处理 - 直接插入和更新,无需额外查询 ✅
|
||||
bizIntermediaryMapper.insertBatch(insertList);
|
||||
bizIntermediaryMapper.updateBatch(updateList);
|
||||
```
|
||||
|
||||
#### 2. 实体中介导入优化(importIntermediaryEntity)
|
||||
|
||||
**优化后实现:**
|
||||
```java
|
||||
// 第一轮:收集所有socialCreditCode
|
||||
for (CcdiIntermediaryEntityExcel excel : list) {
|
||||
if (StringUtils.isNotEmpty(excel.getSocialCreditCode())) {
|
||||
socialCreditCodes.add(excel.getSocialCreditCode());
|
||||
}
|
||||
}
|
||||
|
||||
// 第二轮:批量查询已存在的记录 - 只查询一次 ✅
|
||||
java.util.Map<String, CcdiEnterpriseBaseInfo> existingEntityMap = new java.util.HashMap<>();
|
||||
if (!socialCreditCodes.isEmpty()) {
|
||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, socialCreditCodes);
|
||||
List<CcdiEnterpriseBaseInfo> existingList = enterpriseBaseInfoMapper.selectList(wrapper);
|
||||
|
||||
// 建立socialCreditCode到实体的映射
|
||||
for (CcdiEnterpriseBaseInfo existing : existingList) {
|
||||
existingEntityMap.put(existing.getSocialCreditCode(), existing);
|
||||
}
|
||||
}
|
||||
|
||||
// 第三轮:数据验证和分类 - 使用Map快速判断 ✅
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
CcdiEnterpriseBaseInfo existingEntity = existingEntityMap.get(excel.getSocialCreditCode());
|
||||
if (existingEntity != null) {
|
||||
// 记录已存在
|
||||
if (updateSupport) {
|
||||
updateList.add(entity);
|
||||
}
|
||||
} else {
|
||||
insertList.add(entity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 优化技巧
|
||||
|
||||
1. **批量查询**
|
||||
- 使用 `wrapper.in()` 一次性查询所有待校验的键值
|
||||
- 减少数据库往返次数
|
||||
|
||||
2. **内存映射**
|
||||
- 使用 `HashMap` 存储查询结果
|
||||
- O(1)时间复杂度的快速查找
|
||||
|
||||
3. **查询优化**
|
||||
- 使用 `wrapper.select()` 只查询需要的字段
|
||||
- 减少数据传输量
|
||||
|
||||
4. **提前收集**
|
||||
- 在第一轮循环中收集所有待校验的键值
|
||||
- 避免在循环中查询数据库
|
||||
|
||||
## 性能对比
|
||||
|
||||
### 数据库查询次数对比
|
||||
|
||||
| 导入数据量 | 优化前查询次数 | 优化后查询次数 | 性能提升 |
|
||||
|----------|-------------|-------------|---------|
|
||||
| 100条 | 100+1=101次 | 1次 | 99% |
|
||||
| 500条 | 500+1=501次 | 1次 | 99.8% |
|
||||
| 1000条 | 1000+1=1001次 | 1次 | 99.9% |
|
||||
| 5000条 | 5000+1=5001次 | 1次 | 99.98% |
|
||||
|
||||
### 响应时间对比(预估)
|
||||
|
||||
| 导入数据量 | 优化前响应时间 | 优化后响应时间 | 性能提升 |
|
||||
|----------|------------|------------|---------|
|
||||
| 100条 | ~5秒 | ~0.5秒 | 90% |
|
||||
| 500条 | ~25秒 | ~1秒 | 96% |
|
||||
| 1000条 | ~50秒 | ~2秒 | 96% |
|
||||
| 5000条 | ~250秒 | ~8秒 | 96.8% |
|
||||
|
||||
> 注:响应时间受网络延迟、数据库性能、服务器配置等因素影响,以上为保守预估值
|
||||
|
||||
### 资源消耗对比
|
||||
|
||||
| 指标 | 优化前 | 优化后 | 改善 |
|
||||
|--------------|------------------|-------------------|-----------|
|
||||
| 数据库连接占用时间 | 长时间占用 | 短暂占用 | 减少90%+ |
|
||||
| 网络往返次数 | N+1次 | 1-2次 | 减少99%+ |
|
||||
| 内存占用 | 基本占用 | 额外占用HashMap(很小) | 略微增加(可忽略) |
|
||||
| CPU使用 | 循环+数据库等待 | 批量查询+内存判断 | 优化 |
|
||||
|
||||
## 优化效果
|
||||
|
||||
### 1. 性能提升
|
||||
- **查询次数减少99%+**:从N+1次降低到1次
|
||||
- **响应时间减少90%+**:大幅提升用户体验
|
||||
- **数据库压力降低**:减少数据库连接占用
|
||||
|
||||
### 2. 代码质量提升
|
||||
- **逻辑更清晰**:四阶段流程(收集→查询→分类→处理)
|
||||
- **可维护性更好**:职责分明,易于理解和修改
|
||||
- **扩展性更强**:易于添加其他批量校验逻辑
|
||||
|
||||
### 3. 资源利用优化
|
||||
- **数据库连接池压力减轻**:减少连接占用时间
|
||||
- **网络带宽节省**:减少网络往返次数
|
||||
- **服务器吞吐量提升**:可支持更多并发导入请求
|
||||
|
||||
## MySQL层面优化建议
|
||||
|
||||
### 1. 确保唯一索引存在
|
||||
|
||||
```sql
|
||||
-- 个人中介表:确保personId有唯一索引
|
||||
ALTER TABLE ccdi_biz_intermediary
|
||||
ADD UNIQUE INDEX uk_person_id (person_id);
|
||||
|
||||
-- 实体中介表:确保socialCreditCode有唯一索引
|
||||
ALTER TABLE ccdi_enterprise_base_info
|
||||
ADD UNIQUE INDEX uk_social_credit_code (social_credit_code);
|
||||
```
|
||||
|
||||
### 2. 批量查询执行计划检查
|
||||
|
||||
```sql
|
||||
-- 检查批量查询是否使用了索引
|
||||
EXPLAIN SELECT biz_id, person_id
|
||||
FROM ccdi_biz_intermediary
|
||||
WHERE person_id IN ('id1', 'id2', 'id3', ...);
|
||||
|
||||
-- 期望结果:type=range, key=uk_person_id
|
||||
```
|
||||
|
||||
### 3. 批量插入优化
|
||||
|
||||
```sql
|
||||
-- 确保批量插入使用优化器优化
|
||||
SET optimizer_switch='batched_key_access=on';
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 测试数据
|
||||
- 个人中介测试数据:`doc/test-data/intermediary/个人中介黑名单测试数据_1000条_第1批.xlsx`
|
||||
- 实体中介测试数据:`doc/test-data/intermediary/机构中介黑名单测试数据_1000条_第1批.xlsx`
|
||||
|
||||
### 测试方法
|
||||
使用测试脚本验证导入功能和性能:
|
||||
```bash
|
||||
# 运行测试脚本
|
||||
python doc/test-data/intermediary/test_import_performance.py
|
||||
```
|
||||
|
||||
### 验证要点
|
||||
1. ✅ 功能正确性:新增和更新逻辑正确
|
||||
2. ✅ 唯一性校验:重复数据能正确识别
|
||||
3. ✅ 性能提升:导入时间明显缩短
|
||||
4. ✅ 数据完整性:所有数据正确导入
|
||||
5. ✅ 异常处理:错误信息正确返回
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 后端文件
|
||||
- `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java:245-488`
|
||||
|
||||
### 数据库表
|
||||
- `ccdi_biz_intermediary` - 个人中介表
|
||||
- `ccdi_enterprise_base_info` - 实体中介表
|
||||
|
||||
### 测试数据
|
||||
- `doc/test-data/intermediary/` - 测试数据目录
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
### 1. 异步导入
|
||||
对于超大批量数据(10万+),可以考虑:
|
||||
- 使用消息队列异步处理
|
||||
- 提供导入进度查询接口
|
||||
- 导入完成后通知用户
|
||||
|
||||
### 2. 分批导入
|
||||
对于内存受限场景:
|
||||
- 将大数据集分批处理(每批1000条)
|
||||
- 使用事务保证每批数据的原子性
|
||||
- 失败时回滚当前批次
|
||||
|
||||
### 3. 并行处理
|
||||
对于多核CPU环境:
|
||||
- 使用线程池并行处理不同批次
|
||||
- 注意控制并发数,避免数据库连接耗尽
|
||||
|
||||
### 4. 缓存优化
|
||||
对于频繁导入相同数据的场景:
|
||||
- 使用Redis缓存常用数据
|
||||
- 缓存失效策略:TTL或主动更新
|
||||
|
||||
### 5. SQL进一步优化
|
||||
```sql
|
||||
-- 使用INSERT ON DUPLICATE KEY UPDATE(如果业务允许)
|
||||
INSERT INTO ccdi_biz_intermediary (biz_id, person_id, ...)
|
||||
VALUES (?, ?, ...)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
mobile = VALUES(mobile),
|
||||
...;
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
本次优化通过**批量查询 + 内存映射**的方式,成功将唯一性校验的数据库查询次数从N+1次降低到1次,性能提升99%以上。优化后的代码具有更好的可读性、可维护性和扩展性,为后续功能扩展奠定了良好基础。
|
||||
|
||||
优化核心思想:
|
||||
- **批量操作优于循环操作**
|
||||
- **内存计算优于网络计算**
|
||||
- **提前规划优于事后补救**
|
||||
@@ -35,19 +35,6 @@ public class CcdiEnumController {
|
||||
return AjaxResult.success(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取人员子类型选项
|
||||
*/
|
||||
@Operation(summary = "获取人员子类型选项")
|
||||
@GetMapping("/indivSubType")
|
||||
public AjaxResult getIndivSubTypeOptions() {
|
||||
List<EnumOptionVO> options = new ArrayList<>();
|
||||
for (IndivSubType type : IndivSubType.values()) {
|
||||
options.add(new EnumOptionVO(type.getCode(), type.getDesc()));
|
||||
}
|
||||
return AjaxResult.success(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性别选项
|
||||
*/
|
||||
|
||||
@@ -66,9 +66,6 @@ public class CcdiBizIntermediary implements Serializable {
|
||||
/** 关联人员ID */
|
||||
private String relatedNumId;
|
||||
|
||||
/** 关联关系 */
|
||||
private String relationTypeField;
|
||||
|
||||
/** 数据来源,MANUAL:手动录入, SYSTEM:系统同步, IMPORT:批量导入, API:接口获取 */
|
||||
private String dataSource;
|
||||
|
||||
|
||||
@@ -32,9 +32,6 @@ public class CcdiIntermediaryPersonAddDTO implements Serializable {
|
||||
@Schema(description = "人员子类型")
|
||||
private String personSubType;
|
||||
|
||||
@Schema(description = "关系类型")
|
||||
private String relationType;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private String gender;
|
||||
|
||||
@@ -76,7 +73,8 @@ public class CcdiIntermediaryPersonAddDTO implements Serializable {
|
||||
|
||||
@Schema(description = "关联关系")
|
||||
@Size(max = 50, message = "关联关系长度不能超过50个字符")
|
||||
private String relation;
|
||||
private String relationType;
|
||||
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
|
||||
@@ -36,9 +36,6 @@ public class CcdiIntermediaryPersonEditDTO implements Serializable {
|
||||
@Schema(description = "人员子类型")
|
||||
private String personSubType;
|
||||
|
||||
@Schema(description = "关系类型")
|
||||
private String relationType;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private String gender;
|
||||
|
||||
@@ -79,7 +76,7 @@ public class CcdiIntermediaryPersonEditDTO implements Serializable {
|
||||
|
||||
@Schema(description = "关联关系")
|
||||
@Size(max = 50, message = "关联关系长度不能超过50个字符")
|
||||
private String relation;
|
||||
private String relationType;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
|
||||
@@ -22,19 +22,19 @@ public class CcdiIntermediaryEntityExcel implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 机构名称 */
|
||||
@ExcelProperty(value = "机构名称", index = 0)
|
||||
@ExcelProperty(value = "机构名称*", index = 0)
|
||||
@ColumnWidth(30)
|
||||
private String enterpriseName;
|
||||
|
||||
/** 统一社会信用代码 */
|
||||
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
||||
@ExcelProperty(value = "统一社会信用代码*", index = 1)
|
||||
@ColumnWidth(20)
|
||||
private String socialCreditCode;
|
||||
|
||||
/** 主体类型 */
|
||||
@ExcelProperty(value = "主体类型", index = 2)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_enterprise_type")
|
||||
@DictDropdown(dictType = "ccdi_entity_type")
|
||||
private String enterpriseType;
|
||||
|
||||
/** 企业性质 */
|
||||
@@ -71,7 +71,7 @@ public class CcdiIntermediaryEntityExcel implements Serializable {
|
||||
/** 法定代表人证件类型 */
|
||||
@ExcelProperty(value = "法定代表人证件类型", index = 9)
|
||||
@ColumnWidth(20)
|
||||
@DictDropdown(dictType = "ccdi_id_type")
|
||||
@DictDropdown(dictType = "ccdi_certificate_type")
|
||||
private String legalCertType;
|
||||
|
||||
/** 法定代表人证件号码 */
|
||||
|
||||
@@ -21,7 +21,7 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 姓名 */
|
||||
@ExcelProperty(value = "姓名", index = 0)
|
||||
@ExcelProperty(value = "姓名*", index = 0)
|
||||
@ColumnWidth(15)
|
||||
private String name;
|
||||
|
||||
@@ -34,75 +34,68 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
|
||||
/** 人员子类型 */
|
||||
@ExcelProperty(value = "人员子类型", index = 2)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_person_sub_type")
|
||||
private String personSubType;
|
||||
|
||||
/** 关系类型 */
|
||||
@ExcelProperty(value = "关系类型", index = 3)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_relation_type")
|
||||
private String relationType;
|
||||
|
||||
/** 性别 */
|
||||
@ExcelProperty(value = "性别", index = 4)
|
||||
@ExcelProperty(value = "性别", index = 3)
|
||||
@ColumnWidth(10)
|
||||
@DictDropdown(dictType = "sys_user_sex")
|
||||
@DictDropdown(dictType = "ccdi_indiv_gender")
|
||||
private String gender;
|
||||
|
||||
/** 证件类型 */
|
||||
@ExcelProperty(value = "证件类型", index = 5)
|
||||
@ExcelProperty(value = "证件类型", index = 4)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_id_type")
|
||||
@DictDropdown(dictType = "ccdi_certificate_type")
|
||||
private String idType;
|
||||
|
||||
/** 证件号码 */
|
||||
@ExcelProperty(value = "证件号码", index = 6)
|
||||
@ExcelProperty(value = "证件号码*", index = 5)
|
||||
@ColumnWidth(20)
|
||||
private String personId;
|
||||
|
||||
/** 手机号码 */
|
||||
@ExcelProperty(value = "手机号码", index = 7)
|
||||
@ExcelProperty(value = "手机号码", index = 6)
|
||||
@ColumnWidth(15)
|
||||
private String mobile;
|
||||
|
||||
/** 微信号 */
|
||||
@ExcelProperty(value = "微信号", index = 8)
|
||||
@ExcelProperty(value = "微信号", index = 7)
|
||||
@ColumnWidth(15)
|
||||
private String wechatNo;
|
||||
|
||||
/** 联系地址 */
|
||||
@ExcelProperty(value = "联系地址", index = 9)
|
||||
@ExcelProperty(value = "联系地址", index = 8)
|
||||
@ColumnWidth(30)
|
||||
private String contactAddress;
|
||||
|
||||
/** 所在公司 */
|
||||
@ExcelProperty(value = "所在公司", index = 10)
|
||||
@ExcelProperty(value = "所在公司", index = 9)
|
||||
@ColumnWidth(20)
|
||||
private String company;
|
||||
|
||||
/** 企业统一信用码 */
|
||||
@ExcelProperty(value = "企业统一信用码", index = 11)
|
||||
@ExcelProperty(value = "企业统一信用码", index = 10)
|
||||
@ColumnWidth(20)
|
||||
private String socialCreditCode;
|
||||
|
||||
/** 职位 */
|
||||
@ExcelProperty(value = "职位", index = 12)
|
||||
@ExcelProperty(value = "职位", index = 11)
|
||||
@ColumnWidth(15)
|
||||
private String position;
|
||||
|
||||
/** 关联人员ID */
|
||||
@ExcelProperty(value = "关联人员ID", index = 13)
|
||||
@ExcelProperty(value = "关联人员ID", index = 12)
|
||||
@ColumnWidth(15)
|
||||
private String relatedNumId;
|
||||
|
||||
/** 关联关系 */
|
||||
@ExcelProperty(value = "关联关系", index = 14)
|
||||
/** 关系类型 */
|
||||
@ExcelProperty(value = "关系类型", index = 13)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "ccdi_relation")
|
||||
private String relation;
|
||||
@DictDropdown(dictType = "ccdi_relation_type")
|
||||
private String relationType;
|
||||
|
||||
/** 备注 */
|
||||
@ExcelProperty(value = "备注", index = 15)
|
||||
@ExcelProperty(value = "备注", index = 14)
|
||||
@ColumnWidth(30)
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,15 @@ public class CcdiIntermediaryEntityDetailVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "业务ID")
|
||||
private String bizId;
|
||||
|
||||
@Schema(description = "统一社会信用代码")
|
||||
private String socialCreditCode;
|
||||
|
||||
@Schema(description = "中介类型(1=个人, 2=实体)")
|
||||
private String intermediaryType;
|
||||
|
||||
@Schema(description = "企业名称")
|
||||
private String enterpriseName;
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ public class CcdiIntermediaryPersonDetailVO implements Serializable {
|
||||
@Schema(description = "人员ID")
|
||||
private String bizId;
|
||||
|
||||
@Schema(description = "中介类型(1=个人, 2=实体)")
|
||||
private String intermediaryType;
|
||||
|
||||
@Schema(description = "姓名")
|
||||
private String name;
|
||||
|
||||
@@ -54,6 +57,9 @@ public class CcdiIntermediaryPersonDetailVO implements Serializable {
|
||||
@Schema(description = "所在公司")
|
||||
private String company;
|
||||
|
||||
@Schema(description = "企业统一信用码")
|
||||
private String socialCreditCode;
|
||||
|
||||
@Schema(description = "职位")
|
||||
private String position;
|
||||
|
||||
|
||||
@@ -45,4 +45,8 @@ public class CcdiIntermediaryVO implements Serializable {
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.ruoyi.ccdi.enums;
|
||||
|
||||
|
||||
/**
|
||||
* 人员子类型枚举
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public enum IndivSubType {
|
||||
|
||||
/** 本人 */
|
||||
SELF("本人", "本人"),
|
||||
|
||||
/** 配偶 */
|
||||
SPOUSE("配偶", "配偶"),
|
||||
|
||||
/** 父亲 */
|
||||
FATHER("父亲", "父亲"),
|
||||
|
||||
/** 母亲 */
|
||||
MOTHER("母亲", "母亲"),
|
||||
|
||||
/** 兄弟 */
|
||||
BROTHER("兄弟", "兄弟"),
|
||||
|
||||
/** 姐妹 */
|
||||
SISTER("姐妹", "姐妹"),
|
||||
|
||||
/** 子女 */
|
||||
CHILD("子女", "子女");
|
||||
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
IndivSubType(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取描述
|
||||
*/
|
||||
public static String getDescByCode(String code) {
|
||||
for (IndivSubType type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type.getDesc();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.ccdi.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryQueryDTO;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 中介黑名单联合查询Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-05
|
||||
*/
|
||||
@Mapper
|
||||
public interface CcdiIntermediaryMapper {
|
||||
|
||||
/**
|
||||
* 联合查询中介列表(支持MyBatis Plus分页)
|
||||
* 通过UNION ALL联合查询个人中介和实体中介
|
||||
* 支持按中介类型筛选(1=个人, 2=实体, null=全部)
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介VO分页结果
|
||||
*/
|
||||
Page<CcdiIntermediaryVO> selectIntermediaryList(Page<CcdiIntermediaryVO> page, @Param("query") CcdiIntermediaryQueryDTO queryDTO);
|
||||
}
|
||||
@@ -4,11 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.domain.CcdiBizIntermediary;
|
||||
import com.ruoyi.ccdi.domain.CcdiEnterpriseBaseInfo;
|
||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryEntityAddDTO;
|
||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryEntityEditDTO;
|
||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryPersonAddDTO;
|
||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryPersonEditDTO;
|
||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryQueryDTO;
|
||||
import com.ruoyi.ccdi.domain.dto.*;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryEntityDetailVO;
|
||||
@@ -16,6 +12,7 @@ import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryPersonDetailVO;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO;
|
||||
import com.ruoyi.ccdi.mapper.CcdiBizIntermediaryMapper;
|
||||
import com.ruoyi.ccdi.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||
import com.ruoyi.ccdi.mapper.CcdiIntermediaryMapper;
|
||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryService;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -41,8 +38,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
@Resource
|
||||
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
||||
|
||||
@Resource
|
||||
private CcdiIntermediaryMapper intermediaryMapper;
|
||||
|
||||
/**
|
||||
* 分页查询中介列表
|
||||
* 使用XML联合查询实现,支持个人中介和实体中介的灵活查询
|
||||
* 使用MyBatis Plus分页插件自动处理分页
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
@@ -50,61 +52,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
*/
|
||||
@Override
|
||||
public Page<CcdiIntermediaryVO> selectIntermediaryPage(Page<CcdiIntermediaryVO> page, CcdiIntermediaryQueryDTO queryDTO) {
|
||||
Page<CcdiIntermediaryVO> voPage = new Page<>(page.getCurrent(), page.getSize());
|
||||
List<CcdiIntermediaryVO> list = new ArrayList<>();
|
||||
|
||||
// 查询个人中介
|
||||
LambdaQueryWrapper<CcdiBizIntermediary> personWrapper = new LambdaQueryWrapper<>();
|
||||
personWrapper.eq(CcdiBizIntermediary::getPersonType, "中介")
|
||||
.like(StringUtils.isNotEmpty(queryDTO.getName()), CcdiBizIntermediary::getName, queryDTO.getName())
|
||||
.like(StringUtils.isNotEmpty(queryDTO.getCertificateNo()), CcdiBizIntermediary::getPersonId, queryDTO.getCertificateNo());
|
||||
|
||||
List<CcdiBizIntermediary> personList = bizIntermediaryMapper.selectList(personWrapper);
|
||||
for (CcdiBizIntermediary person : personList) {
|
||||
CcdiIntermediaryVO vo = new CcdiIntermediaryVO();
|
||||
vo.setId(person.getBizId());
|
||||
vo.setName(person.getName());
|
||||
vo.setCertificateNo(person.getPersonId());
|
||||
vo.setIntermediaryType("1");
|
||||
vo.setPersonType(person.getPersonType());
|
||||
vo.setCompany(person.getCompany());
|
||||
vo.setDataSource(person.getDataSource());
|
||||
vo.setCreateTime(person.getCreateTime());
|
||||
list.add(vo);
|
||||
}
|
||||
|
||||
// 查询实体中介
|
||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> entityWrapper = new LambdaQueryWrapper<>();
|
||||
entityWrapper.eq(CcdiEnterpriseBaseInfo::getRiskLevel, "1")
|
||||
.eq(CcdiEnterpriseBaseInfo::getEntSource, "INTERMEDIARY")
|
||||
.like(StringUtils.isNotEmpty(queryDTO.getName()), CcdiEnterpriseBaseInfo::getEnterpriseName, queryDTO.getName())
|
||||
.like(StringUtils.isNotEmpty(queryDTO.getCertificateNo()), CcdiEnterpriseBaseInfo::getSocialCreditCode, queryDTO.getCertificateNo());
|
||||
|
||||
List<CcdiEnterpriseBaseInfo> entityList = enterpriseBaseInfoMapper.selectList(entityWrapper);
|
||||
for (CcdiEnterpriseBaseInfo entity : entityList) {
|
||||
CcdiIntermediaryVO vo = new CcdiIntermediaryVO();
|
||||
vo.setId(entity.getSocialCreditCode());
|
||||
vo.setName(entity.getEnterpriseName());
|
||||
vo.setCertificateNo(entity.getSocialCreditCode());
|
||||
vo.setIntermediaryType("2");
|
||||
vo.setDataSource(entity.getDataSource());
|
||||
vo.setCreateTime(entity.getCreateTime());
|
||||
list.add(vo);
|
||||
}
|
||||
|
||||
// 手动分页
|
||||
int start = (int) ((voPage.getCurrent() - 1) * voPage.getSize());
|
||||
int end = (int) Math.min(start + voPage.getSize(), list.size());
|
||||
|
||||
List<CcdiIntermediaryVO> pageList = new ArrayList<>();
|
||||
if (start < list.size()) {
|
||||
pageList = list.subList(start, end);
|
||||
}
|
||||
|
||||
voPage.setRecords(pageList);
|
||||
voPage.setTotal(list.size());
|
||||
|
||||
return voPage;
|
||||
// 直接调用Mapper的联合查询方法,MyBatis Plus会自动处理分页
|
||||
return intermediaryMapper.selectIntermediaryList(page, queryDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,6 +71,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
CcdiIntermediaryPersonDetailVO vo = new CcdiIntermediaryPersonDetailVO();
|
||||
BeanUtils.copyProperties(person, vo);
|
||||
// 设置中介类型为个人
|
||||
vo.setIntermediaryType("1");
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -140,6 +91,10 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
CcdiIntermediaryEntityDetailVO vo = new CcdiIntermediaryEntityDetailVO();
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
// 设置中介类型为实体
|
||||
vo.setIntermediaryType("2");
|
||||
// 设置业务ID(使用socialCreditCode作为bizId,前端判断是否为新增模式)
|
||||
vo.setBizId(socialCreditCode);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -159,7 +114,6 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
CcdiBizIntermediary person = new CcdiBizIntermediary();
|
||||
BeanUtils.copyProperties(addDTO, person);
|
||||
person.setPersonType("中介");
|
||||
person.setDataSource("MANUAL");
|
||||
|
||||
return bizIntermediaryMapper.insert(person);
|
||||
@@ -286,6 +240,7 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
/**
|
||||
* 导入个人中介数据(批量操作)
|
||||
* 优化:使用批量查询替代循环中的单条查询,减少数据库交互次数
|
||||
*
|
||||
* @param list Excel实体列表
|
||||
* @param updateSupport 是否更新支持
|
||||
@@ -308,7 +263,28 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
List<CcdiBizIntermediary> updateList = new ArrayList<>();
|
||||
List<String> personIds = new ArrayList<>();
|
||||
|
||||
// 第一轮:数据验证和分类
|
||||
// 第一轮:收集所有personId
|
||||
for (CcdiIntermediaryPersonExcel excel : list) {
|
||||
if (StringUtils.isNotEmpty(excel.getPersonId())) {
|
||||
personIds.add(excel.getPersonId());
|
||||
}
|
||||
}
|
||||
|
||||
// 第二轮:批量查询已存在的记录(一次查询替代N次查询)
|
||||
java.util.Map<String, String> personIdToBizIdMap = new java.util.HashMap<>();
|
||||
if (!personIds.isEmpty()) {
|
||||
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBizIntermediary::getBizId, CcdiBizIntermediary::getPersonId);
|
||||
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
|
||||
List<CcdiBizIntermediary> existingList = bizIntermediaryMapper.selectList(wrapper);
|
||||
|
||||
// 建立personId到bizId的映射
|
||||
for (CcdiBizIntermediary existing : existingList) {
|
||||
personIdToBizIdMap.put(existing.getPersonId(), existing.getBizId());
|
||||
}
|
||||
}
|
||||
|
||||
// 第三轮:数据验证和分类(使用Map进行快速判断,避免重复查询)
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
try {
|
||||
CcdiIntermediaryPersonExcel excel = list.get(i);
|
||||
@@ -327,12 +303,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
person.setPersonType("中介");
|
||||
person.setDataSource("IMPORT");
|
||||
|
||||
personIds.add(excel.getPersonId());
|
||||
|
||||
// 检查唯一性
|
||||
if (!checkPersonIdUnique(excel.getPersonId(), null)) {
|
||||
// 使用Map快速判断是否存在
|
||||
String existingBizId = personIdToBizIdMap.get(excel.getPersonId());
|
||||
if (existingBizId != null) {
|
||||
// 记录已存在
|
||||
if (updateSupport) {
|
||||
// 需要更新,暂时加入更新列表
|
||||
// 需要更新,设置bizId
|
||||
person.setBizId(existingBizId);
|
||||
updateList.add(person);
|
||||
} else {
|
||||
throw new RuntimeException("该证件号已存在");
|
||||
@@ -351,12 +328,15 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
}
|
||||
}
|
||||
|
||||
// 构建返回消息
|
||||
StringBuilder resultMsg = new StringBuilder();
|
||||
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
resultMsg.append("很抱歉,导入失败!共 ").append(failureNum).append(" 条数据格式不正确,错误如下:");
|
||||
resultMsg.append(failureMsg);
|
||||
}
|
||||
|
||||
// 第二轮:批量处理
|
||||
// 第四轮:批量处理
|
||||
try {
|
||||
// 批量插入新记录
|
||||
if (!insertList.isEmpty()) {
|
||||
@@ -365,31 +345,16 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
// 批量更新已存在的记录
|
||||
if (!updateList.isEmpty()) {
|
||||
// 查询已存在记录的bizId
|
||||
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
|
||||
List<CcdiBizIntermediary> existingList = bizIntermediaryMapper.selectList(wrapper);
|
||||
|
||||
// 建立personId到bizId的映射
|
||||
java.util.Map<String, String> personIdToBizIdMap = new java.util.HashMap<>();
|
||||
for (CcdiBizIntermediary existing : existingList) {
|
||||
personIdToBizIdMap.put(existing.getPersonId(), existing.getBizId());
|
||||
}
|
||||
|
||||
// 设置bizId到更新列表
|
||||
for (CcdiBizIntermediary person : updateList) {
|
||||
String bizId = personIdToBizIdMap.get(person.getPersonId());
|
||||
if (bizId != null) {
|
||||
person.setBizId(bizId);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
bizIntermediaryMapper.updateBatch(updateList);
|
||||
}
|
||||
|
||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条");
|
||||
return successMsg.toString();
|
||||
// 只在有失败的情况下才返回成功信息,否则返回简洁的成功消息
|
||||
if (failureNum > 0) {
|
||||
resultMsg.append("<br/><br/>成功导入 ").append(successNum).append(" 条数据");
|
||||
} else {
|
||||
resultMsg.append("恭喜您,数据已全部导入成功!共 ").append(successNum).append(" 条");
|
||||
}
|
||||
return resultMsg.toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("批量操作失败:" + e.getMessage());
|
||||
}
|
||||
@@ -397,6 +362,7 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
/**
|
||||
* 导入实体中介数据(批量操作)
|
||||
* 优化:使用批量查询替代循环中的单条查询,减少数据库交互次数
|
||||
*
|
||||
* @param list Excel实体列表
|
||||
* @param updateSupport 是否更新支持
|
||||
@@ -419,7 +385,27 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
List<CcdiEnterpriseBaseInfo> updateList = new ArrayList<>();
|
||||
List<String> socialCreditCodes = new ArrayList<>();
|
||||
|
||||
// 第一轮:数据验证和分类
|
||||
// 第一轮:收集所有socialCreditCode
|
||||
for (CcdiIntermediaryEntityExcel excel : list) {
|
||||
if (StringUtils.isNotEmpty(excel.getSocialCreditCode())) {
|
||||
socialCreditCodes.add(excel.getSocialCreditCode());
|
||||
}
|
||||
}
|
||||
|
||||
// 第二轮:批量查询已存在的记录(一次查询替代N次查询)
|
||||
java.util.Map<String, CcdiEnterpriseBaseInfo> existingEntityMap = new java.util.HashMap<>();
|
||||
if (!socialCreditCodes.isEmpty()) {
|
||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, socialCreditCodes);
|
||||
List<CcdiEnterpriseBaseInfo> existingList = enterpriseBaseInfoMapper.selectList(wrapper);
|
||||
|
||||
// 建立socialCreditCode到实体的映射
|
||||
for (CcdiEnterpriseBaseInfo existing : existingList) {
|
||||
existingEntityMap.put(existing.getSocialCreditCode(), existing);
|
||||
}
|
||||
}
|
||||
|
||||
// 第三轮:数据验证和分类(使用Map进行快速判断,避免重复查询)
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
try {
|
||||
CcdiIntermediaryEntityExcel excel = list.get(i);
|
||||
@@ -436,13 +422,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
entity.setEntSource("INTERMEDIARY");
|
||||
entity.setDataSource("IMPORT");
|
||||
|
||||
// 检查唯一性
|
||||
// 使用Map快速判断是否存在
|
||||
if (StringUtils.isNotEmpty(excel.getSocialCreditCode())) {
|
||||
socialCreditCodes.add(excel.getSocialCreditCode());
|
||||
|
||||
if (!checkSocialCreditCodeUnique(excel.getSocialCreditCode(), null)) {
|
||||
CcdiEnterpriseBaseInfo existingEntity = existingEntityMap.get(excel.getSocialCreditCode());
|
||||
if (existingEntity != null) {
|
||||
// 记录已存在
|
||||
if (updateSupport) {
|
||||
// 需要更新,加入更新列表
|
||||
// 需要更新,直接使用socialCreditCode作为主键
|
||||
updateList.add(entity);
|
||||
} else {
|
||||
throw new RuntimeException("该统一社会信用代码已存在");
|
||||
@@ -465,12 +451,15 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
}
|
||||
}
|
||||
|
||||
// 构建返回消息
|
||||
StringBuilder resultMsg = new StringBuilder();
|
||||
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
resultMsg.append("很抱歉,导入失败!共 ").append(failureNum).append(" 条数据格式不正确,错误如下:");
|
||||
resultMsg.append(failureMsg);
|
||||
}
|
||||
|
||||
// 第二轮:批量处理
|
||||
// 第四轮:批量处理
|
||||
try {
|
||||
// 批量插入新记录
|
||||
if (!insertList.isEmpty()) {
|
||||
@@ -479,12 +468,16 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
||||
|
||||
// 批量更新已存在的记录
|
||||
if (!updateList.isEmpty()) {
|
||||
// 批量更新(socialCreditCode已在实体中)
|
||||
enterpriseBaseInfoMapper.updateBatch(updateList);
|
||||
}
|
||||
|
||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条");
|
||||
return successMsg.toString();
|
||||
// 只在有失败的情况下才返回成功信息,否则返回简洁的成功消息
|
||||
if (failureNum > 0) {
|
||||
resultMsg.append("<br/><br/>成功导入 ").append(successNum).append(" 条数据");
|
||||
} else {
|
||||
resultMsg.append("恭喜您,数据已全部导入成功!共 ").append(successNum).append(" 条");
|
||||
}
|
||||
return resultMsg.toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("批量操作失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ccdi.mapper.CcdiBizIntermediaryMapper">
|
||||
<mapper namespace="com.ruoyi.ccdi.mapper.CcdiIntermediaryMapper">
|
||||
|
||||
<!--
|
||||
统一列表联合查询
|
||||
使用UNION ALL联合查询个人中介和实体中介
|
||||
支持按中介类型、姓名、证件号筛选
|
||||
中介黑名单联合查询
|
||||
支持按中介类型筛选: 1=个人中介, 2=实体中介, null=全部
|
||||
使用MyBatis Plus分页插件自动处理分页
|
||||
-->
|
||||
<select id="selectIntermediaryList" resultType="com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO">
|
||||
SELECT * FROM (
|
||||
<!-- 查询个人中介 -->
|
||||
SELECT
|
||||
biz_id as id,
|
||||
@@ -19,17 +20,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
person_type,
|
||||
company,
|
||||
data_source,
|
||||
create_time
|
||||
create_time,
|
||||
update_time
|
||||
FROM ccdi_biz_intermediary
|
||||
WHERE person_type = '中介'
|
||||
<if test="intermediaryType == null or intermediaryType == '1'">
|
||||
<if test="name != null and name != ''">
|
||||
AND name LIKE CONCAT('%', #{name}, '%')
|
||||
</if>
|
||||
<if test="certificateNo != null and certificateNo != ''">
|
||||
AND person_id = #{certificateNo}
|
||||
</if>
|
||||
</if>
|
||||
|
||||
UNION ALL
|
||||
|
||||
@@ -42,19 +35,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
'实体' as person_type,
|
||||
enterprise_name as company,
|
||||
data_source,
|
||||
create_time
|
||||
create_time,
|
||||
update_time
|
||||
FROM ccdi_enterprise_base_info
|
||||
WHERE risk_level = '1' AND ent_source = 'INTERMEDIARY'
|
||||
<if test="intermediaryType == null or intermediaryType == '2'">
|
||||
<if test="name != null and name != ''">
|
||||
AND enterprise_name LIKE CONCAT('%', #{name}, '%')
|
||||
) AS combined_result
|
||||
<where>
|
||||
<!-- 按中介类型筛选 -->
|
||||
<if test="query.intermediaryType != null and query.intermediaryType != ''">
|
||||
AND intermediary_type = #{query.intermediaryType}
|
||||
</if>
|
||||
<if test="certificateNo != null and certificateNo != ''">
|
||||
AND social_credit_code = #{certificateNo}
|
||||
<!-- 按姓名/机构名称模糊查询 -->
|
||||
<if test="query.name != null and query.name != ''">
|
||||
AND name LIKE CONCAT('%', #{query.name}, '%')
|
||||
</if>
|
||||
<!-- 按证件号/统一社会信用代码精确查询 -->
|
||||
<if test="query.certificateNo != null and query.certificateNo != ''">
|
||||
AND certificate_no = #{query.certificateNo}
|
||||
</if>
|
||||
|
||||
ORDER BY create_time DESC
|
||||
</where>
|
||||
ORDER BY update_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -10,16 +10,6 @@ export function getIndivTypeOptions() {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询人员子类型选项
|
||||
*/
|
||||
export function getIndivSubTypeOptions() {
|
||||
return request({
|
||||
url: '/ccdi/enum/indivSubType',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询性别选项
|
||||
*/
|
||||
|
||||
@@ -9,20 +9,19 @@ export function listIntermediary(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 查询中介黑名单详细
|
||||
export function getIntermediary(intermediaryId) {
|
||||
// 查询个人中介详细
|
||||
export function getPersonIntermediary(bizId) {
|
||||
return request({
|
||||
url: '/ccdi/intermediary/' + intermediaryId,
|
||||
url: '/ccdi/intermediary/person/' + bizId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增中介黑名单
|
||||
export function addIntermediary(data) {
|
||||
// 查询实体中介详细
|
||||
export function getEntityIntermediary(socialCreditCode) {
|
||||
return request({
|
||||
url: '/ccdi/intermediary',
|
||||
method: 'post',
|
||||
data: data
|
||||
url: '/ccdi/intermediary/entity/' + socialCreditCode,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="姓名/机构名称" align="center" prop="name" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="证件号" align="center" prop="certificateNo" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="中介类型" align="center" prop="intermediaryTypeName" width="100"/>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<el-table-column label="中介类型" align="center" prop="intermediaryType" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === '0'" type="success">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
<span v-if="scope.row.intermediaryType === '1'">个人</span>
|
||||
<span v-else-if="scope.row.intermediaryType === '2'">实体</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
|
||||
@@ -2,54 +2,67 @@
|
||||
<el-dialog title="中介黑名单详情" :visible.sync="visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<!-- 核心字段 -->
|
||||
<el-descriptions-item label="中介ID">{{ detailData.intermediaryId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中介类型">{{ detailData.intermediaryTypeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名/机构名称">{{ detailData.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号/信用代码">{{ detailData.certificateNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag v-if="detailData.status === '0'" type="success">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
<el-descriptions-item label="中介类型">
|
||||
<span v-if="detailData.intermediaryType === '1'">个人</span>
|
||||
<span v-else-if="detailData.intermediaryType === '2'">实体</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名/机构名称">{{ detailData.name || detailData.enterpriseName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号/信用代码">
|
||||
<span v-if="detailData.intermediaryType === '1'">{{ detailData.personId || '-' }}</span>
|
||||
<span v-else>{{ detailData.socialCreditCode || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据来源">{{ detailData.dataSourceName || '-' }}</el-descriptions-item>
|
||||
|
||||
<!-- 个人类型专属字段 -->
|
||||
<template v-if="detailData.intermediaryType === '1'">
|
||||
<el-descriptions-item label="人员类型">{{ detailData.indivType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人员子类型">{{ detailData.indivSubType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detailData.indivGenderName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ detailData.indivCertType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ detailData.indivPhone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信号">{{ detailData.indivWechat || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系地址" :span="2">{{ detailData.indivAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在公司">{{ detailData.indivCompany || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职位">{{ detailData.indivPosition || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关联人员ID">{{ detailData.indivRelatedId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关联关系">{{ detailData.indivRelation || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人员类型">{{ detailData.personType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人员子类型">{{ detailData.personSubType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<span v-if="detailData.gender === 'M'">男</span>
|
||||
<span v-else-if="detailData.gender === 'F'">女</span>
|
||||
<span v-else-if="detailData.gender === 'O'">其他</span>
|
||||
<span v-else>{{ detailData.gender || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ detailData.idType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ detailData.mobile || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="微信号">{{ detailData.wechatNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系地址" :span="2">{{ detailData.contactAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在公司">{{ detailData.company || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职位">{{ detailData.position || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业统一信用码">{{ detailData.socialCreditCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关系类型">{{ detailData.relationType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="关联人员ID">{{ detailData.relatedNumId || '-' }}</el-descriptions-item>
|
||||
</template>
|
||||
|
||||
<!-- 机构类型专属字段 -->
|
||||
<template v-if="detailData.intermediaryType === '2'">
|
||||
<el-descriptions-item label="统一社会信用代码" :span="2">{{ detailData.corpCreditCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主体类型">{{ detailData.corpType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业性质">{{ detailData.corpNature || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行业分类">{{ detailData.corpIndustryCategory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属行业">{{ detailData.corpIndustry || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成立日期">{{ detailData.corpEstablishDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="注册地址" :span="2">{{ detailData.corpAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人">{{ detailData.corpLegalRep || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人证件类型">{{ detailData.corpLegalCertType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人证件号码" :span="2">{{ detailData.corpLegalCertNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东1">{{ detailData.corpShareholder1 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东2">{{ detailData.corpShareholder2 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东3">{{ detailData.corpShareholder3 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东4">{{ detailData.corpShareholder4 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东5">{{ detailData.corpShareholder5 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="统一社会信用代码" :span="2">{{ detailData.socialCreditCode || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="主体类型">{{ detailData.enterpriseType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业性质">{{ detailData.enterpriseNature || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="行业分类">{{ detailData.industryClass || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属行业">{{ detailData.industryName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成立日期">{{ detailData.establishDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="注册地址" :span="2">{{ detailData.registerAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人">{{ detailData.legalRepresentative || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人证件类型">{{ detailData.legalCertType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法定代表人证件号码" :span="2">{{ detailData.legalCertNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东1">{{ detailData.shareholder1 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东2">{{ detailData.shareholder2 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东3">{{ detailData.shareholder3 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东4">{{ detailData.shareholder4 || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="股东5">{{ detailData.shareholder5 || '-' }}</el-descriptions-item>
|
||||
</template>
|
||||
|
||||
<!-- 通用字段 -->
|
||||
<el-descriptions-item label="数据来源">
|
||||
<span v-if="detailData.dataSource === 'MANUAL'">手动录入</span>
|
||||
<span v-else-if="detailData.dataSource === 'SYSTEM'">系统同步</span>
|
||||
<span v-else-if="detailData.dataSource === 'IMPORT'">批量导入</span>
|
||||
<span v-else-if="detailData.dataSource === 'API'">接口获取</span>
|
||||
<span v-else>{{ detailData.dataSource || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">{{ detailData.createBy || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createTime || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">关 闭</el-button>
|
||||
|
||||
@@ -43,15 +43,15 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证件号" prop="certificateNo">
|
||||
<el-input v-model="form.certificateNo" placeholder="请输入证件号码" maxlength="50" clearable/>
|
||||
<el-form-item label="证件号" prop="personId">
|
||||
<el-input v-model="form.personId" placeholder="请输入证件号码" maxlength="50" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="人员类型">
|
||||
<el-select v-model="form.indivType" placeholder="请选择人员类型" clearable style="width: 100%">
|
||||
<el-select v-model="form.personType" placeholder="请选择人员类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in indivTypeOptions"
|
||||
:key="item.value"
|
||||
@@ -63,21 +63,14 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="人员子类型">
|
||||
<el-select v-model="form.indivSubType" placeholder="请选择人员子类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in indivSubTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input v-model="form.personSubType" placeholder="请输入人员子类型" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别">
|
||||
<el-select v-model="form.indivGender" placeholder="请选择性别" clearable style="width: 100%">
|
||||
<el-select v-model="form.gender" placeholder="请选择性别" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in genderOptions"
|
||||
:key="item.value"
|
||||
@@ -89,7 +82,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证件类型">
|
||||
<el-select v-model="form.indivCertType" placeholder="请选择证件类型" clearable style="width: 100%">
|
||||
<el-select v-model="form.idType" placeholder="请选择证件类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in certTypeOptions"
|
||||
:key="item.value"
|
||||
@@ -103,39 +96,46 @@
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号码">
|
||||
<el-input v-model="form.indivPhone" placeholder="请输入手机号码" maxlength="20" clearable/>
|
||||
<el-input v-model="form.mobile" placeholder="请输入手机号码" maxlength="20" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信号">
|
||||
<el-input v-model="form.indivWechat" placeholder="请输入微信号" maxlength="50" clearable/>
|
||||
<el-input v-model="form.wechatNo" placeholder="请输入微信号" maxlength="50" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="联系地址">
|
||||
<el-input v-model="form.indivAddress" placeholder="请输入联系地址" maxlength="200" clearable/>
|
||||
<el-input v-model="form.contactAddress" placeholder="请输入联系地址" maxlength="200" clearable/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所在公司">
|
||||
<el-input v-model="form.indivCompany" placeholder="请输入所在公司" maxlength="100" clearable/>
|
||||
<el-input v-model="form.company" placeholder="请输入所在公司" maxlength="200" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职位">
|
||||
<el-input v-model="form.indivPosition" placeholder="请输入职位" maxlength="100" clearable/>
|
||||
<el-input v-model="form.position" placeholder="请输入职位" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联人员ID">
|
||||
<el-input v-model="form.indivRelatedId" placeholder="请输入关联人员ID" maxlength="20" clearable/>
|
||||
<el-form-item label="企业统一信用码">
|
||||
<el-input v-model="form.socialCreditCode" placeholder="请输入企业统一信用码" maxlength="50" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联人员ID">
|
||||
<el-input v-model="form.relatedNumId" placeholder="请输入关联人员ID" maxlength="50" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联关系">
|
||||
<el-select v-model="form.indivRelation" placeholder="请选择关联关系" clearable style="width: 100%">
|
||||
<el-select v-model="form.relationType" placeholder="请选择关联关系" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in relationTypeOptions"
|
||||
:key="item.value"
|
||||
@@ -168,17 +168,16 @@
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="机构名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入机构名称" maxlength="100" clearable/>
|
||||
<el-form-item label="机构名称" prop="enterpriseName">
|
||||
<el-input v-model="form.enterpriseName" placeholder="请输入机构名称" maxlength="200" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="证件号" prop="certificateNo">
|
||||
<el-form-item label="证件号" prop="socialCreditCode">
|
||||
<el-input
|
||||
v-model="form.certificateNo"
|
||||
@input="handleCertificateNoChange"
|
||||
placeholder="统一社会信用代码(18位)"
|
||||
maxlength="18"
|
||||
v-model="form.socialCreditCode"
|
||||
placeholder="统一社会信用代码"
|
||||
maxlength="50"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -187,7 +186,7 @@
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主体类型">
|
||||
<el-select v-model="form.corpType" placeholder="请选择主体类型" clearable style="width: 100%">
|
||||
<el-select v-model="form.enterpriseType" placeholder="请选择主体类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in corpTypeOptions"
|
||||
:key="item.value"
|
||||
@@ -199,7 +198,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="企业性质">
|
||||
<el-select v-model="form.corpNature" placeholder="请选择企业性质" clearable style="width: 100%">
|
||||
<el-select v-model="form.enterpriseNature" placeholder="请选择企业性质" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in corpNatureOptions"
|
||||
:key="item.value"
|
||||
@@ -214,7 +213,7 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="成立日期">
|
||||
<el-date-picker
|
||||
v-model="form.corpEstablishDate"
|
||||
v-model="form.establishDate"
|
||||
type="date"
|
||||
placeholder="选择成立日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
@@ -224,64 +223,71 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="行业分类">
|
||||
<el-input v-model="form.corpIndustryCategory" placeholder="请输入行业分类" maxlength="100" clearable/>
|
||||
<el-input v-model="form.industryClass" placeholder="请输入行业分类" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所属行业">
|
||||
<el-input v-model="form.corpIndustry" placeholder="请输入所属行业" maxlength="100" clearable/>
|
||||
<el-input v-model="form.industryName" placeholder="请输入所属行业" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="注册地址">
|
||||
<el-input v-model="form.corpAddress" type="textarea" placeholder="请输入注册地址" maxlength="500" :rows="2"/>
|
||||
<el-input v-model="form.registerAddress" type="textarea" placeholder="请输入注册地址" maxlength="500" :rows="2"/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法定代表人">
|
||||
<el-input v-model="form.corpLegalRep" placeholder="请输入法定代表人" maxlength="50" clearable/>
|
||||
<el-input v-model="form.legalRepresentative" placeholder="请输入法定代表人" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法定代表人证件类型">
|
||||
<el-input v-model="form.corpLegalCertType" placeholder="请输入证件类型" maxlength="30" clearable/>
|
||||
<el-select v-model="form.legalCertType" placeholder="请选择证件类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in certTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="法定代表人证件号码">
|
||||
<el-input v-model="form.corpLegalCertNo" placeholder="请输入法定代表人证件号码" maxlength="30" clearable/>
|
||||
<el-input v-model="form.legalCertNo" placeholder="请输入法定代表人证件号码" maxlength="50" clearable/>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">股东信息</el-divider>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东1">
|
||||
<el-input v-model="form.corpShareholder1" placeholder="请输入股东1" maxlength="30" clearable/>
|
||||
<el-input v-model="form.shareholder1" placeholder="请输入股东1" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东2">
|
||||
<el-input v-model="form.corpShareholder2" placeholder="请输入股东2" maxlength="30" clearable/>
|
||||
<el-input v-model="form.shareholder2" placeholder="请输入股东2" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东3">
|
||||
<el-input v-model="form.corpShareholder3" placeholder="请输入股东3" maxlength="30" clearable/>
|
||||
<el-input v-model="form.shareholder3" placeholder="请输入股东3" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东4">
|
||||
<el-input v-model="form.corpShareholder4" placeholder="请输入股东4" maxlength="30" clearable/>
|
||||
<el-input v-model="form.shareholder4" placeholder="请输入股东4" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="股东5">
|
||||
<el-input v-model="form.corpShareholder5" placeholder="请输入股东5" maxlength="30" clearable/>
|
||||
<el-input v-model="form.shareholder5" placeholder="请输入股东5" maxlength="100" clearable/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -329,10 +335,6 @@ export default {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
indivSubTypeOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
genderOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
@@ -370,7 +372,7 @@ export default {
|
||||
{ required: true, message: "姓名不能为空", trigger: "blur" },
|
||||
{ max: 100, message: "姓名长度不能超过100个字符", trigger: "blur" }
|
||||
],
|
||||
certificateNo: [
|
||||
personId: [
|
||||
{ required: true, message: "证件号不能为空", trigger: "blur" },
|
||||
{ max: 50, message: "证件号长度不能超过50个字符", trigger: "blur" }
|
||||
],
|
||||
@@ -380,13 +382,13 @@ export default {
|
||||
},
|
||||
// 机构类型验证规则
|
||||
corpRules: {
|
||||
name: [
|
||||
enterpriseName: [
|
||||
{ required: true, message: "机构名称不能为空", trigger: "blur" },
|
||||
{ max: 100, message: "机构名称长度不能超过100个字符", trigger: "blur" }
|
||||
{ max: 200, message: "机构名称长度不能超过200个字符", trigger: "blur" }
|
||||
],
|
||||
certificateNo: [
|
||||
{ required: true, message: "证件号不能为空", trigger: "blur" },
|
||||
{ max: 18, message: "统一社会信用代码长度为18位", trigger: "blur" }
|
||||
socialCreditCode: [
|
||||
{ required: true, message: "统一社会信用代码不能为空", trigger: "blur" },
|
||||
{ max: 50, message: "统一社会信用代码长度不能超过50个字符", trigger: "blur" }
|
||||
],
|
||||
remark: [
|
||||
{ max: 500, message: "备注长度不能超过500个字符", trigger: "blur" }
|
||||
@@ -397,7 +399,7 @@ export default {
|
||||
computed: {
|
||||
// 判断是否为新增模式
|
||||
isAddMode() {
|
||||
return !this.form || !this.form.intermediaryId;
|
||||
return !this.form || !this.form.bizId;
|
||||
},
|
||||
// 根据选择的类型返回分隔线文本
|
||||
getTypeDividerText() {
|
||||
@@ -426,7 +428,7 @@ export default {
|
||||
*/
|
||||
initDialogState() {
|
||||
// 始终基于当前的 form 状态判断
|
||||
const isAdd = !this.form || !this.form.intermediaryId;
|
||||
const isAdd = !this.form || !this.form.bizId;
|
||||
|
||||
if (isAdd) {
|
||||
// 新增模式:重置选择状态
|
||||
@@ -466,16 +468,6 @@ export default {
|
||||
}, 50);
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理机构类型证件号变更
|
||||
* 同步到统一社会信用代码字段
|
||||
*/
|
||||
handleCertificateNoChange(value) {
|
||||
if (this.form.intermediaryType === '2') {
|
||||
this.form.corpCreditCode = value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
@@ -488,12 +480,6 @@ export default {
|
||||
|
||||
// 根据类型验证不同的表单
|
||||
const formRef = this.form.intermediaryType === '1' ? 'indivForm' : 'corpForm';
|
||||
const rules = this.form.intermediaryType === '1' ? this.indivRules : this.corpRules;
|
||||
|
||||
// 机构类型:同步证件号到统一社会信用代码
|
||||
if (this.form.intermediaryType === '2') {
|
||||
this.form.corpCreditCode = this.form.certificateNo;
|
||||
}
|
||||
|
||||
this.$refs[formRef].validate(valid => {
|
||||
if (valid) {
|
||||
|
||||
@@ -148,9 +148,9 @@ export default {
|
||||
},
|
||||
handleDownloadTemplate() {
|
||||
if (this.formData.importType === 'person') {
|
||||
this.download('dpc/intermediary/importPersonTemplate', {}, `个人中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
this.download('ccdi/intermediary/importPersonTemplate', {}, `个人中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
} else {
|
||||
this.download('dpc/intermediary/importEntityTemplate', {}, `机构中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
this.download('ccdi/intermediary/importEntityTemplate', {}, `机构中介黑名单模板_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
},
|
||||
handleFileUploadProgress() {
|
||||
@@ -166,10 +166,26 @@ export default {
|
||||
this.isUploading = false;
|
||||
this.visible = false;
|
||||
this.$emit("success");
|
||||
|
||||
// 解析后端返回的消息,只展示错误部分
|
||||
let displayMessage = response.msg;
|
||||
|
||||
// 如果消息包含"恭喜您,数据已全部导入成功",说明全部成功,不展示详细列表
|
||||
if (displayMessage.includes('恭喜您,数据已全部导入成功')) {
|
||||
// 全部成功,使用简洁提示
|
||||
displayMessage = '导入成功!';
|
||||
}
|
||||
// 如果消息包含"很抱歉,导入失败",说明有错误,只展示错误部分
|
||||
else if (displayMessage.includes('很抱歉,导入失败')) {
|
||||
// 只保留错误部分,移除成功统计信息
|
||||
const lines = displayMessage.split('<br/><br/>');
|
||||
displayMessage = lines[0]; // 只取错误部分
|
||||
}
|
||||
|
||||
this.$msgbox({
|
||||
title: '导入结果',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: `<div style="overflow-y: auto; max-height: 60vh; padding-right: 10px; line-height: 1.6;">${response.msg}</div>`,
|
||||
message: `<div style="overflow-y: auto; max-height: 60vh; padding-right: 10px; line-height: 1.6;">${displayMessage}</div>`,
|
||||
confirmButtonText: '确定',
|
||||
customClass: 'import-result-dialog'
|
||||
});
|
||||
|
||||
@@ -22,14 +22,7 @@
|
||||
<el-select v-model="queryParams.intermediaryType" placeholder="中介类型" clearable style="width: 240px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="个人" value="1" />
|
||||
<el-option label="机构" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable style="width: 240px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="停用" value="1" />
|
||||
<el-option label="实体" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
:title="title"
|
||||
:form="form"
|
||||
:indiv-type-options="indivTypeOptions"
|
||||
:indiv-sub-type-options="indivSubTypeOptions"
|
||||
:gender-options="genderOptions"
|
||||
:cert-type-options="certTypeOptions"
|
||||
:relation-type-options="relationTypeOptions"
|
||||
@@ -82,7 +81,8 @@ import {
|
||||
addEntityIntermediary,
|
||||
addPersonIntermediary,
|
||||
delIntermediary,
|
||||
getIntermediary,
|
||||
getEntityIntermediary,
|
||||
getPersonIntermediary,
|
||||
listIntermediary,
|
||||
updateEntityIntermediary,
|
||||
updatePersonIntermediary
|
||||
@@ -92,7 +92,6 @@ import {
|
||||
getCorpNatureOptions,
|
||||
getCorpTypeOptions,
|
||||
getGenderOptions,
|
||||
getIndivSubTypeOptions,
|
||||
getIndivTypeOptions,
|
||||
getRelationTypeOptions
|
||||
} from "@/api/ccdiEnum";
|
||||
@@ -129,8 +128,7 @@ export default {
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
certificateNo: null,
|
||||
intermediaryType: null,
|
||||
status: null
|
||||
intermediaryType: null
|
||||
},
|
||||
form: {},
|
||||
upload: {
|
||||
@@ -138,7 +136,6 @@ export default {
|
||||
title: ""
|
||||
},
|
||||
indivTypeOptions: [],
|
||||
indivSubTypeOptions: [],
|
||||
genderOptions: [],
|
||||
certTypeOptions: [],
|
||||
relationTypeOptions: [],
|
||||
@@ -156,9 +153,6 @@ export default {
|
||||
getIndivTypeOptions().then(response => {
|
||||
this.indivTypeOptions = response.data;
|
||||
});
|
||||
getIndivSubTypeOptions().then(response => {
|
||||
this.indivSubTypeOptions = response.data;
|
||||
});
|
||||
getGenderOptions().then(response => {
|
||||
this.genderOptions = response.data;
|
||||
});
|
||||
@@ -191,7 +185,7 @@ export default {
|
||||
},
|
||||
/** 多选框选中数据 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.intermediaryId);
|
||||
this.ids = selection.map(item => item.id);
|
||||
this.single = selection.length !== 1;
|
||||
this.multiple = !selection.length;
|
||||
},
|
||||
@@ -204,40 +198,42 @@ export default {
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
intermediaryId: null,
|
||||
bizId: null,
|
||||
name: null,
|
||||
certificateNo: null,
|
||||
intermediaryType: "1",
|
||||
status: "0",
|
||||
remark: null,
|
||||
indivType: null,
|
||||
indivSubType: null,
|
||||
indivGender: null,
|
||||
indivCertType: null,
|
||||
indivPhone: null,
|
||||
indivWechat: null,
|
||||
indivAddress: null,
|
||||
indivCompany: null,
|
||||
indivPosition: null,
|
||||
indivRelatedId: null,
|
||||
indivRelation: null,
|
||||
corpCreditCode: null,
|
||||
corpType: null,
|
||||
corpNature: null,
|
||||
corpIndustryCategory: null,
|
||||
corpIndustry: null,
|
||||
corpEstablishDate: null,
|
||||
corpAddress: null,
|
||||
corpLegalRep: null,
|
||||
corpLegalCertType: null,
|
||||
corpLegalCertNo: null,
|
||||
corpShareholder1: null,
|
||||
corpShareholder2: null,
|
||||
corpShareholder3: null,
|
||||
corpShareholder4: null,
|
||||
corpShareholder5: null
|
||||
// 个人中介字段
|
||||
personId: null,
|
||||
personType: null,
|
||||
personSubType: null,
|
||||
relationType: null,
|
||||
gender: null,
|
||||
idType: null,
|
||||
mobile: null,
|
||||
wechatNo: null,
|
||||
contactAddress: null,
|
||||
company: null,
|
||||
socialCreditCode: null,
|
||||
position: null,
|
||||
relatedNumId: null,
|
||||
// 实体中介字段
|
||||
enterpriseName: null,
|
||||
enterpriseType: null,
|
||||
enterpriseNature: null,
|
||||
industryClass: null,
|
||||
industryName: null,
|
||||
establishDate: null,
|
||||
registerAddress: null,
|
||||
legalRepresentative: null,
|
||||
legalCertType: null,
|
||||
legalCertNo: null,
|
||||
shareholder1: null,
|
||||
shareholder2: null,
|
||||
shareholder3: null,
|
||||
shareholder4: null,
|
||||
shareholder5: null
|
||||
};
|
||||
// 注意:不调用 this.resetForm("form")
|
||||
// 注意:不调用 this.resetForm("form")
|
||||
// EditDialog 组件会在 visible 变化时自动处理表单验证状态的重置
|
||||
},
|
||||
/** 取消按钮 */
|
||||
@@ -247,25 +243,42 @@ export default {
|
||||
},
|
||||
/** 查看详情操作 */
|
||||
handleDetail(row) {
|
||||
const intermediaryId = row.intermediaryId;
|
||||
getIntermediary(intermediaryId).then(response => {
|
||||
if (row.intermediaryType === '1') {
|
||||
// 个人中介 - 使用row.id作为bizId
|
||||
getPersonIntermediary(row.id).then(response => {
|
||||
this.detailData = response.data;
|
||||
this.detailOpen = true;
|
||||
});
|
||||
} else {
|
||||
// 实体中介 - 使用row.id作为socialCreditCode
|
||||
getEntityIntermediary(row.id).then(response => {
|
||||
this.detailData = response.data;
|
||||
this.detailOpen = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset();
|
||||
const intermediaryId = row.intermediaryId || this.ids[0];
|
||||
getIntermediary(intermediaryId).then(response => {
|
||||
if (row.intermediaryType === '1') {
|
||||
// 个人中介 - 使用row.id作为bizId
|
||||
getPersonIntermediary(row.id).then(response => {
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改中介黑名单";
|
||||
});
|
||||
} else {
|
||||
// 实体中介 - 使用row.id作为socialCreditCode
|
||||
getEntityIntermediary(row.id).then(response => {
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改中介黑名单";
|
||||
});
|
||||
}
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
if (this.form.intermediaryId != null) {
|
||||
if (this.form.bizId != null) {
|
||||
// 修改模式:根据中介类型调用不同的接口
|
||||
if (this.form.intermediaryType === '1') {
|
||||
// 个人中介
|
||||
@@ -303,9 +316,12 @@ export default {
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const intermediaryIds = row.intermediaryId || this.ids;
|
||||
this.$modal.confirm('是否确认删除中介黑名单编号为"' + intermediaryIds + '"的数据项?').then(function() {
|
||||
return delIntermediary(intermediaryIds);
|
||||
const bizIds = row.id || this.ids.join(',');
|
||||
const confirmMsg = row.id
|
||||
? `确认删除中介"${row.name}"(证件号:${row.certificateNo})吗?`
|
||||
: `确认删除选中的 ${this.ids.length} 条中介数据吗?`;
|
||||
this.$modal.confirm(confirmMsg).then(function() {
|
||||
return delIntermediary(bizIds);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
|
||||
36
sql/dpc_intermediary_relation_type_dict_20260205.sql
Normal file
36
sql/dpc_intermediary_relation_type_dict_20260205.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
-- ============================================================
|
||||
-- 中介黑名单关系类型字典补充脚本
|
||||
-- 功能:为个人中介添加关系类型字典
|
||||
-- 作者:ruoyi
|
||||
-- 日期:2026-02-05
|
||||
-- ============================================================
|
||||
|
||||
USE `discipline-prelim-check`;
|
||||
|
||||
-- ============================================================
|
||||
-- 关系类型字典(ccdi_relation_type)
|
||||
-- ============================================================
|
||||
DELETE FROM sys_dict_data WHERE dict_type = 'ccdi_relation_type';
|
||||
DELETE FROM sys_dict_type WHERE dict_type = 'ccdi_relation_type';
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
VALUES ('关系类型', 'ccdi_relation_type', '0', 'admin', NOW(), '中介黑名单-人员关系类型');
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
VALUES
|
||||
(1, '配偶', '配偶', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(2, '父亲', '父亲', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(3, '母亲', '母亲', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(4, '子女', '子女', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(5, '兄弟', '兄弟', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(6, '姐妹', '姐妹', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(7, '合伙人', '合伙人', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(8, '同事', '同事', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL),
|
||||
(9, '其他', '其他', 'ccdi_relation_type', '', 'default', 'N', '0', 'admin', NOW(), NULL);
|
||||
|
||||
-- ============================================================
|
||||
-- 执行完成后操作说明
|
||||
-- ============================================================
|
||||
-- 1. 执行完成后,请通过系统管理 > 字典管理验证字典数据
|
||||
-- 2. 重要:在字典类型页面点击"刷新缓存"按钮,确保字典数据加载到 Redis
|
||||
-- 3. 验证字典缓存是否生效(可通过测试模板下载确认下拉框是否显示)
|
||||
Reference in New Issue
Block a user