Compare commits
4 Commits
worktree-l
...
dc8f1be4c3
| Author | SHA1 | Date | |
|---|---|---|---|
| dc8f1be4c3 | |||
| bc2959b93c | |||
| 72e2539134 | |||
| 16dc95de06 |
@@ -69,50 +69,7 @@
|
||||
"Bash(ls:*)",
|
||||
"Bash(test_report.sh \")",
|
||||
"mcp__mysql__show_statement",
|
||||
"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)",
|
||||
"Bash(git reset:*)",
|
||||
"Skill(xlsx)",
|
||||
"mcp__chrome-devtools__evaluate_script",
|
||||
"Skill(superpowers:using-git-worktrees)",
|
||||
"Bash(git -C D:ccdiccdi show 97bb899 --stat)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(git rebase:*)",
|
||||
"Bash(git stash:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(git check-ignore:*)",
|
||||
"Bash(git worktree add:*)",
|
||||
"Bash(xmllint:*)",
|
||||
"Bash(git worktree remove:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" status)",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" log --oneline -10)",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" ls -la doc/)",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" status --short)",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" add \"doc/plans/2025-02-08-intermediary-import-history-cleanup.md\" \"doc/reports/2026-02-08-intermediary-import-history-cleanup-completion.md\")",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" commit -m \"$\\(cat <<''EOF''\ndocs: 添加中介导入历史清除功能完成报告\n\n- 添加功能设计文档\n- 添加功能完成总结报告\n- 包含代码审查结果和后续优化建议\n\nCo-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
||||
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" log --oneline -5)",
|
||||
"Bash([:*)",
|
||||
"Bash([ -d modules ])",
|
||||
"Bash([ -d test-data ])",
|
||||
"Skill(generate-test-data)",
|
||||
"Bash(python3:*)",
|
||||
"Skill(mcp-mysql-correct-db)",
|
||||
"Bash(git diff:*)",
|
||||
"Bash(git pull:*)",
|
||||
"Bash(git merge:*)",
|
||||
"mcp__chrome-devtools-mcp__take_snapshot",
|
||||
"mcp__chrome-devtools-mcp__fill",
|
||||
"mcp__chrome-devtools-mcp__click",
|
||||
"mcp__chrome-devtools-mcp__take_screenshot"
|
||||
"Bash(if not exist \"doc\\\\designs\" mkdir docdesigns)"
|
||||
]
|
||||
},
|
||||
"enabledMcpjsonServers": [
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- 在进行需求分析与分解任务时,按照不同的模块分为不同的文件,创建模块名的文件夹并将对应文件保存在文件夹中,然后对模块的功能文件进行继续分解
|
||||
- 在使用/openspec:proposal时,自动开启深度思考模式,输入 “think more”、“think a lot”、“think harder” 或 “think longer” 触发更深层的思考
|
||||
- 在执行/openspec:apply后,使用code-simplifier 进行代码精简
|
||||
- 在分析生成需求文档时,每次都需要在doc目录下新建文件夹并以需求内容为命名
|
||||
|
||||
## Communication
|
||||
- 永远使用简体中文进行思考和对话
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
# ✅ Form-Data 实现最终确认
|
||||
|
||||
## 实现日期
|
||||
2026-03-03
|
||||
|
||||
## 实现状态
|
||||
✅ **完成并验证** - 所有接口使用 form-data,Swagger 正确显示
|
||||
|
||||
---
|
||||
|
||||
## 📋 实现总结
|
||||
|
||||
### ✅ 最终实现方式
|
||||
|
||||
**所有接口使用 Form 参数,Swagger UI 正确显示为 form-data 格式**
|
||||
|
||||
```python
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(
|
||||
projectNo: str = Form(..., description="项目编号"),
|
||||
entityName: str = Form(..., description="项目名称"),
|
||||
userId: str = Form(..., description="操作人员编号"),
|
||||
# ... 其他参数
|
||||
):
|
||||
# 构建字典并传递给服务层
|
||||
request_data = {
|
||||
"projectNo": projectNo,
|
||||
"entityName": entityName,
|
||||
"userId": userId,
|
||||
# ...
|
||||
}
|
||||
return token_service.create_token(request_data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 关键设计
|
||||
|
||||
### 1. **路由层**
|
||||
- ✅ 使用 `Form(...)` 参数接收 form-data
|
||||
- ✅ 将 Form 参数转换为字典传递给服务层
|
||||
- ✅ 不使用 Pydantic 模型作为请求参数(避免 Swagger 显示为 JSON)
|
||||
|
||||
### 2. **服务层**
|
||||
- ✅ 接受 `Union[Dict, object]` 类型参数
|
||||
- ✅ 兼容字典和对象两种访问方式
|
||||
- ✅ 使用字典访问:`request.get("key")` 或 `request["key"]`
|
||||
|
||||
### 3. **Swagger UI**
|
||||
- ✅ 自动识别 Form 参数
|
||||
- ✅ 显示为 `application/x-www-form-urlencoded`
|
||||
- ✅ 提供表单字段输入框(不是 JSON 编辑器)
|
||||
|
||||
---
|
||||
|
||||
## 📊 实现对比
|
||||
|
||||
### ❌ 之前的实现(JSON 方式)
|
||||
```python
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(request: GetTokenRequest):
|
||||
# 接收 JSON body
|
||||
return token_service.create_token(request)
|
||||
```
|
||||
**问题**: Swagger UI 显示为 JSON 格式
|
||||
|
||||
### ✅ 现在的实现(Form-Data 方式)
|
||||
```python
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(
|
||||
projectNo: str = Form(...),
|
||||
entityName: str = Form(...),
|
||||
# ...
|
||||
):
|
||||
request_data = {"projectNo": projectNo, "entityName": entityName, ...}
|
||||
return token_service.create_token(request_data)
|
||||
```
|
||||
**结果**: Swagger UI 显示为 form-data 格式 ✅
|
||||
|
||||
---
|
||||
|
||||
## ✅ 测试结果
|
||||
|
||||
```bash
|
||||
======================== 7 passed, 1 warning in 0.06s =========================
|
||||
```
|
||||
|
||||
**所有 7 个测试通过** ✅
|
||||
|
||||
---
|
||||
|
||||
## 📖 使用方式
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
# ✅ 使用 data 参数(form-data)
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
data={
|
||||
"projectNo": "test_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "your_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-d "projectNo=test_001" \
|
||||
-d "entityName=测试企业" \
|
||||
-d "userId=902001" \
|
||||
-d "userName=902001" \
|
||||
-d "appId=remote_app" \
|
||||
-d "appSecretCode=your_code" \
|
||||
-d "role=VIEWER" \
|
||||
-d "orgCode=902000" \
|
||||
-d "departmentCode=902000"
|
||||
```
|
||||
|
||||
### Swagger UI
|
||||
1. 访问 http://localhost:8000/docs
|
||||
2. 点击接口展开
|
||||
3. 点击 "Try it out"
|
||||
4. **看到表单字段**(不是 JSON 编辑器)
|
||||
5. 填写参数并点击 "Execute"
|
||||
|
||||
---
|
||||
|
||||
## 📁 修改的文件
|
||||
|
||||
### 路由层
|
||||
1. **routers/api.py**
|
||||
- 所有接口使用 `Form(...)` 参数
|
||||
- 构建 dict 传递给服务层
|
||||
|
||||
### 服务层
|
||||
2. **services/token_service.py**
|
||||
- `create_token()` 接受 `Union[Dict, object]`
|
||||
- 支持字典访问方式
|
||||
|
||||
3. **services/file_service.py**
|
||||
- `fetch_inner_flow()` 接受 `Union[Dict, object]`
|
||||
- 移除 Pydantic 模型依赖
|
||||
|
||||
4. **services/statement_service.py**
|
||||
- `get_bank_statement()` 接受 `Union[Dict, object]`
|
||||
- 使用字典访问分页参数
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Swagger UI 效果
|
||||
|
||||
### 显示方式
|
||||
```
|
||||
Request body
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
Form fields:
|
||||
- projectNo: [input]
|
||||
- entityName: [input]
|
||||
- userId: [input]
|
||||
- userName: [input]
|
||||
- appId: [input with default: remote_app]
|
||||
- appSecretCode: [input]
|
||||
- role: [input with default: VIEWER]
|
||||
- orgCode: [input]
|
||||
- entityId: [optional input]
|
||||
- xdRelatedPersons: [optional input]
|
||||
- jzDataDateId: [input with default: 0]
|
||||
- innerBSStartDateId: [input with default: 0]
|
||||
- innerBSEndDateId: [input with default: 0]
|
||||
- analysisType: [input with default: -1]
|
||||
- departmentCode: [input]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 不要使用 `json=` 参数
|
||||
```python
|
||||
# ❌ 错误
|
||||
response = requests.post(url, json=data)
|
||||
|
||||
# ✅ 正确
|
||||
response = requests.post(url, data=data)
|
||||
```
|
||||
|
||||
### 2. 可选参数处理
|
||||
```python
|
||||
# 可选参数使用 Optional[str] = Form(None)
|
||||
entityId: Optional[str] = Form(None, description="可选")
|
||||
```
|
||||
|
||||
### 3. 默认值参数
|
||||
```python
|
||||
# 默认值使用 Form("default_value")
|
||||
appId: str = Form("remote_app", description="固定值")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
- [x] 所有接口使用 Form 参数
|
||||
- [x] 服务层接受字典参数
|
||||
- [x] 移除 Pydantic 模型在路由层的依赖
|
||||
- [x] Swagger UI 显示为 form-data
|
||||
- [x] 所有测试通过(7/7)
|
||||
- [x] 支持 Python requests 调用
|
||||
- [x] 支持 curl 命令调用
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
✅ **实现完成**
|
||||
|
||||
- **传输方式**: `application/x-www-form-urlencoded`
|
||||
- **Swagger UI**: 正确显示为 form-data 表单
|
||||
- **测试状态**: 7/7 通过
|
||||
- **兼容性**: 支持字典和对象两种访问方式
|
||||
|
||||
**Mock 服务器已准备就绪!** 🚀
|
||||
|
||||
---
|
||||
|
||||
**实现人员**: Claude Code
|
||||
**实现日期**: 2026-03-03
|
||||
**版本**: v1.4.0
|
||||
**状态**: ✅ 完成
|
||||
532
doc/designs/2026-02-04-intermediary-blacklist-design.md
Normal file
532
doc/designs/2026-02-04-intermediary-blacklist-design.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# 中介黑名单管理模块 - 系统设计文档
|
||||
|
||||
## 文档信息
|
||||
|
||||
- **版本**: v1.0
|
||||
- **日期**: 2026-02-04
|
||||
- **作者**: Claude
|
||||
- **项目**: 纪检初核系统 (CCDI)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 功能简介
|
||||
|
||||
中介黑名单管理模块提供个人中介和实体中介两类中介信息的完整管理功能,包括:
|
||||
- 个人中介的增删改查
|
||||
- 实体中介的增删改查
|
||||
- 统一列表查询(支持联合查询和个人/实体分类查询)
|
||||
- 带字典下拉框的Excel导入模板下载
|
||||
- 批量数据导入
|
||||
|
||||
### 1.2 核心特性
|
||||
|
||||
1. **双表存储**: 个人中介和实体中介分别存储在不同的数据表中
|
||||
2. **统一查询**: 使用SQL UNION实现高效的联合查询和分页
|
||||
3. **类型区分**: 通过`intermediary_type`字段区分个人(1)和实体(2)中介
|
||||
4. **智能筛选**: 实体中介通过`risk_level='1'`(高风险) AND `ent_source='INTERMEDIARY'(中介)`筛选
|
||||
5. **唯一性保证**: 个人中介的证件号`person_id`作为业务唯一键
|
||||
|
||||
### 1.3 技术栈
|
||||
|
||||
- **后端框架**: Spring Boot 3.5.8
|
||||
- **ORM框架**: MyBatis Plus 3.5.10
|
||||
- **Excel处理**: EasyExcel (带字典下拉框)
|
||||
- **数据库**: MySQL 8.2.0
|
||||
- **API文档**: SpringDoc 2.8.14
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库设计
|
||||
|
||||
### 2.1 个人中介表 (ccdi_biz_intermediary)
|
||||
|
||||
| 字段名 | 类型 | 可空 | 主键 | 注释 |
|
||||
|--------|------|------|------|------|
|
||||
| biz_id | VARCHAR | 否 | 是 | 人员ID |
|
||||
| person_type | VARCHAR | 否 | 否 | 人员类型(中介、职业背债人等) |
|
||||
| person_sub_type | VARCHAR | 是 | 否 | 人员子类型 |
|
||||
| relation_type | VARCHAR | 是 | 否 | 关系类型(配偶、子女、父母等) |
|
||||
| name | VARCHAR | 否 | 否 | 姓名 |
|
||||
| gender | CHAR | 是 | 否 | 性别 |
|
||||
| id_type | VARCHAR | 否 | 否 | 证件类型(默认身份证) |
|
||||
| person_id | VARCHAR | 否 | 否 | **证件号码(业务唯一键)** |
|
||||
| mobile | VARCHAR | 是 | 否 | 手机号码 |
|
||||
| wechat_no | VARCHAR | 是 | 否 | 微信号 |
|
||||
| contact_address | VARCHAR | 是 | 否 | 联系地址 |
|
||||
| company | VARCHAR | 是 | 否 | 所在公司 |
|
||||
| social_credit_code | VARCHAR | 是 | 否 | 企业统一信用码 |
|
||||
| position | VARCHAR | 是 | 否 | 职位 |
|
||||
| related_num_id | VARCHAR | 是 | 否 | 关联人员ID |
|
||||
| relation_type | VARCHAR | 是 | 否 | 关联关系 |
|
||||
| data_source | VARCHAR | 是 | 否 | 数据来源(MANUAL/SYSTEM/IMPORT/API) |
|
||||
| remark | VARCHAR | 是 | 否 | 备注信息 |
|
||||
| created_by | VARCHAR | 否 | 否 | 记录创建人 |
|
||||
| updated_by | VARCHAR | 是 | 否 | 记录更新人 |
|
||||
| create_time | DATETIME | 否 | 否 | 记录创建时间 |
|
||||
| update_time | DATETIME | 是 | 否 | 记录更新时间 |
|
||||
|
||||
**索引设计**:
|
||||
- PRIMARY KEY: `biz_id`
|
||||
- UNIQUE KEY: `uk_person_id` (`person_id`)
|
||||
|
||||
### 2.2 实体中介表 (ccdi_enterprise_base_info)
|
||||
|
||||
| 字段名 | 类型 | 可空 | 主键 | 注释 |
|
||||
|--------|------|------|------|------|
|
||||
| social_credit_code | VARCHAR | 否 | 是 | **统一社会信用代码(主键)** |
|
||||
| enterprise_name | VARCHAR | 否 | 否 | 企业名称 |
|
||||
| enterprise_type | VARCHAR | 否 | 否 | 企业类型(有限责任公司、股份有限公司等) |
|
||||
| enterprise_nature | VARCHAR | 是 | 否 | 企业性质(国企、民企、外企等) |
|
||||
| industry_class | VARCHAR | 是 | 否 | 行业分类 |
|
||||
| industry_name | VARCHAR | 是 | 否 | 所属行业 |
|
||||
| establish_date | DATE | 是 | 否 | 成立日期 |
|
||||
| register_address | VARCHAR | 是 | 否 | 注册地址 |
|
||||
| legal_representative | VARCHAR | 是 | 否 | 法定代表人 |
|
||||
| legal_cert_type | VARCHAR | 是 | 否 | 法定代表人证件类型 |
|
||||
| legal_cert_no | VARCHAR | 是 | 否 | 法定代表人证件号码 |
|
||||
| shareholder1-5 | VARCHAR | 是 | 否 | 股东信息 |
|
||||
| status | VARCHAR | 是 | 否 | 经营状态 |
|
||||
| create_time | DATETIME | 否 | 否 | 创建时间 |
|
||||
| update_time | DATETIME | 否 | 否 | 更新时间 |
|
||||
| created_by | VARCHAR | 否 | 否 | 创建人 |
|
||||
| updated_by | VARCHAR | 是 | 否 | 更新人 |
|
||||
| data_source | VARCHAR | 是 | 否 | 数据来源(MANUAL/SYSTEM/API/IMPORT) |
|
||||
| **risk_level** | VARCHAR(10) | 是 | 否 | **风险等级:1-高风险, 2-中风险, 3-低风险** |
|
||||
| **ent_source** | VARCHAR(20) | 否 | 否 | **企业来源:GENERAL/EMP_RELATION/CREDIT_CUSTOMER/INTERMEDIARY/BOTH** |
|
||||
|
||||
**索引设计**:
|
||||
- PRIMARY KEY: `social_credit_code`
|
||||
- INDEX: `idx_risk_ent_source` (`risk_level`, `ent_source`)
|
||||
|
||||
**实体中介筛选条件**:
|
||||
- `risk_level = '1'` (高风险)
|
||||
- `ent_source = 'INTERMEDIARY'` (中介)
|
||||
|
||||
---
|
||||
|
||||
## 3. 架构设计
|
||||
|
||||
### 3.1 整体架构
|
||||
|
||||
```
|
||||
Controller Layer (CcdiIntermediaryController)
|
||||
↓
|
||||
Service Layer (ICcdiIntermediaryService)
|
||||
↓
|
||||
Mapper Layer (CcdiBizIntermediaryMapper, CcdiEnterpriseBaseInfoMapper)
|
||||
↓
|
||||
Database (ccdi_biz_intermediary, ccdi_enterprise_base_info)
|
||||
```
|
||||
|
||||
### 3.2 分层说明
|
||||
|
||||
**Controller层**:
|
||||
- 统一的Controller处理个人和实体中介的请求
|
||||
- 使用不同的路径区分个人和实体中介操作
|
||||
- 权限控制: `ccdi:intermediary:*`
|
||||
|
||||
**Service层**:
|
||||
- 统一的服务接口
|
||||
- 根据中介类型路由到不同的业务逻辑
|
||||
- 处理唯一性校验、数据自动填充等业务规则
|
||||
|
||||
**Mapper层**:
|
||||
- 每个表对应独立的Mapper接口
|
||||
- 继承MyBatis Plus的BaseMapper
|
||||
- 自定义XML实现UNION联合查询
|
||||
|
||||
**DTO/VO层**:
|
||||
- 严格分离,不与Entity混用
|
||||
- DTO用于接口参数接收
|
||||
- VO用于数据返回
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口设计
|
||||
|
||||
### 4.1 基础信息
|
||||
|
||||
- **基础路径**: `/ccdi/intermediary`
|
||||
- **权限前缀**: `ccdi:intermediary`
|
||||
- **响应格式**: AjaxResult
|
||||
|
||||
### 4.2 统一列表查询
|
||||
|
||||
**接口**: `GET /ccdi/intermediary/list`
|
||||
|
||||
**权限**: `ccdi:intermediary:list`
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| name | String | 否 | 姓名/机构名称(模糊查询) |
|
||||
| certificateNo | String | 否 | 证件号/统一社会信用代码(精确查询) |
|
||||
| intermediaryType | String | 否 | 中介类型(1=个人, 2=实体, null=全部) |
|
||||
| pageNum | Integer | 否 | 页码(默认1) |
|
||||
| pageSize | Integer | 否 | 每页数量(默认10) |
|
||||
|
||||
**响应**: TableDataInfo (分页结果)
|
||||
|
||||
**实现**: SQL UNION联合查询,支持按类型筛选优化
|
||||
|
||||
### 4.3 个人中介接口
|
||||
|
||||
#### 4.3.1 新增个人中介
|
||||
**接口**: `POST /ccdi/intermediary/person`
|
||||
**权限**: `ccdi:intermediary:add`
|
||||
**请求体**: CcdiIntermediaryPersonAddDTO
|
||||
**业务逻辑**:
|
||||
- 校验姓名必填
|
||||
- 校验证件号必填且唯一
|
||||
- 自动设置data_source='MANUAL'
|
||||
- 自动设置person_type='中介'
|
||||
|
||||
#### 4.3.2 修改个人中介
|
||||
**接口**: `PUT /ccdi/intermediary/person`
|
||||
**权限**: `ccdi:intermediary:edit`
|
||||
**请求体**: CcdiIntermediaryPersonEditDTO
|
||||
**业务逻辑**:
|
||||
- biz_id不可修改
|
||||
- 证件号修改时需校验唯一性(排除自身)
|
||||
|
||||
#### 4.3.3 查询个人中介详情
|
||||
**接口**: `GET /ccdi/intermediary/person/{bizId}`
|
||||
**权限**: `ccdi:intermediary:query`
|
||||
**响应**: CcdiIntermediaryPersonDetailVO
|
||||
|
||||
### 4.4 实体中介接口
|
||||
|
||||
#### 4.4.1 新增实体中介
|
||||
**接口**: `POST /ccdi/intermediary/entity`
|
||||
**权限**: `ccdi:intermediary:add`
|
||||
**请求体**: CcdiIntermediaryEntityAddDTO
|
||||
**业务逻辑**:
|
||||
- 校验企业名称必填
|
||||
- 校验统一社会信用代码唯一
|
||||
- 自动设置risk_level='1'(高风险)
|
||||
- 自动设置ent_source='INTERMEDIARY'(中介)
|
||||
- 自动设置data_source='MANUAL'
|
||||
|
||||
#### 4.4.2 修改实体中介
|
||||
**接口**: `PUT /ccdi/intermediary/entity`
|
||||
**权限**: `ccdi:intermediary:edit`
|
||||
**请求体**: CcdiIntermediaryEntityEditDTO
|
||||
**业务逻辑**:
|
||||
- social_credit_code不可修改
|
||||
- 企业名称修改时需校验唯一性(排除自身)
|
||||
|
||||
#### 4.4.3 查询实体中介详情
|
||||
**接口**: `GET /ccdi/intermediary/entity/{socialCreditCode}`
|
||||
**权限**: `ccdi:intermediary:query`
|
||||
**响应**: CcdiIntermediaryEntityDetailVO
|
||||
|
||||
### 4.5 删除接口
|
||||
|
||||
**接口**: `DELETE /ccdi/intermediary/{ids}`
|
||||
**权限**: `ccdi:intermediary:remove`
|
||||
**路径参数**: ids (支持个人和实体的ID,逗号分隔)
|
||||
|
||||
### 4.6 导入导出接口
|
||||
|
||||
#### 4.6.1 个人中介模板下载
|
||||
**接口**: `POST /ccdi/intermediary/importPersonTemplate`
|
||||
**权限**: 无需登录
|
||||
**功能**: 下载带字典下拉框的Excel模板
|
||||
**下拉字段**:
|
||||
- 性别: `ccdi_indiv_gender`
|
||||
- 证件类型: `ccdi_certificate_type`
|
||||
- 关联关系: `ccdi_relation_type`
|
||||
|
||||
#### 4.6.2 实体中介模板下载
|
||||
**接口**: `POST /ccdi/intermediary/importEntityTemplate`
|
||||
**权限**: 无需登录
|
||||
**功能**: 下载带字典下拉框的Excel模板
|
||||
**下拉字段**:
|
||||
- 主体类型: `ccdi_entity_type`
|
||||
- 企业性质: `ccdi_enterprise_nature`
|
||||
- 法人证件类型: `ccdi_certificate_type`
|
||||
|
||||
#### 4.6.3 个人中介数据导入
|
||||
**接口**: `POST /ccdi/intermediary/importPersonData`
|
||||
**权限**: `ccdi:intermediary:import`
|
||||
**参数**:
|
||||
- file: MultipartFile
|
||||
- updateSupport: Boolean (是否更新已存在数据)
|
||||
**Excel类**: CcdiIntermediaryPersonExcel
|
||||
**业务逻辑**:
|
||||
- 解析Excel数据
|
||||
- 校验姓名必填、证件号必填
|
||||
- 检查person_id唯一性
|
||||
- 批量插入ccdi_biz_intermediary表
|
||||
- 自动设置: data_source='IMPORT', person_type='中介'
|
||||
|
||||
#### 4.6.4 实体中介数据导入
|
||||
**接口**: `POST /ccdi/intermediary/importEntityData`
|
||||
**权限**: `ccdi:intermediary:import`
|
||||
**参数**:
|
||||
- file: MultipartFile
|
||||
- updateSupport: Boolean (是否更新已存在数据)
|
||||
**Excel类**: CcdiIntermediaryEntityExcel
|
||||
**业务逻辑**:
|
||||
- 解析Excel数据
|
||||
- 校验企业名称必填
|
||||
- 检查social_credit_code唯一性
|
||||
- 批量插入ccdi_enterprise_base_info表
|
||||
- 自动设置: risk_level='1', ent_source='INTERMEDIARY', data_source='IMPORT'
|
||||
|
||||
---
|
||||
|
||||
## 5. UNION联合查询实现
|
||||
|
||||
### 5.1 SQL查询语句
|
||||
|
||||
```xml
|
||||
<select id="selectIntermediaryList" resultType="CcdiIntermediaryVO">
|
||||
<!-- 查询个人中介 -->
|
||||
SELECT
|
||||
biz_id as id,
|
||||
name,
|
||||
person_id as certificate_no,
|
||||
'1' as intermediary_type,
|
||||
person_type,
|
||||
gender,
|
||||
id_type,
|
||||
mobile,
|
||||
company,
|
||||
data_source,
|
||||
create_time
|
||||
FROM ccdi_biz_intermediary
|
||||
WHERE person_type = '中介'
|
||||
<if test="intermediaryType == null or intermediaryType == '1'">
|
||||
AND name LIKE CONCAT('%', #{name}, '%')
|
||||
<if test="certificateNo != null and certificateNo != ''">
|
||||
AND person_id = #{certificateNo}
|
||||
</if>
|
||||
</if>
|
||||
|
||||
UNION ALL
|
||||
|
||||
<!-- 查询实体中介 -->
|
||||
SELECT
|
||||
social_credit_code as id,
|
||||
enterprise_name as name,
|
||||
social_credit_code as certificate_no,
|
||||
'2' as intermediary_type,
|
||||
'实体' as person_type,
|
||||
null as gender,
|
||||
null as id_type,
|
||||
null as mobile,
|
||||
enterprise_name as company,
|
||||
data_source,
|
||||
create_time
|
||||
FROM ccdi_enterprise_base_info
|
||||
WHERE risk_level = '1' AND ent_source = 'INTERMEDIARY'
|
||||
<if test="intermediaryType == null or intermediaryType == '2'">
|
||||
AND enterprise_name LIKE CONCAT('%', #{name}, '%')
|
||||
<if test="certificateNo != null and certificateNo != ''">
|
||||
AND social_credit_code = #{certificateNo}
|
||||
</if>
|
||||
</if>
|
||||
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
```
|
||||
|
||||
### 5.2 分页实现
|
||||
|
||||
- 使用MyBatis Plus的Page对象进行分页
|
||||
- 在Service层调用`page(intermediaryQueryDTO, Page)`方法
|
||||
- 自动处理total和rows
|
||||
|
||||
### 5.3 查询优化
|
||||
|
||||
- 根据intermediaryType参数优化查询,如果指定类型则只查询对应表
|
||||
- 添加索引优化查询性能
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据对象设计
|
||||
|
||||
### 6.1 Entity实体类
|
||||
|
||||
**CcdiBizIntermediary**:
|
||||
- 使用`@Data`注解
|
||||
- 不继承BaseEntity
|
||||
- 单独添加审计字段
|
||||
- 主键: biz_id (String)
|
||||
|
||||
**CcdiEnterpriseBaseInfo**:
|
||||
- 使用`@Data`注解
|
||||
- 不继承BaseEntity
|
||||
- 单独添加审计字段
|
||||
- 主键: social_credit_code (String)
|
||||
|
||||
### 6.2 DTO数据传输对象
|
||||
|
||||
**CcdiIntermediaryPersonAddDTO**: 个人中介新增DTO
|
||||
- 包含所有个人字段
|
||||
- 使用JSR-303校验注解
|
||||
|
||||
**CcdiIntermediaryPersonEditDTO**: 个人中介修改DTO
|
||||
- 包含biz_id和可编辑字段
|
||||
- biz_id不可为空
|
||||
|
||||
**CcdiIntermediaryEntityAddDTO**: 实体中介新增DTO
|
||||
- 包含所有企业字段
|
||||
- 使用JSR-303校验注解
|
||||
|
||||
**CcdiIntermediaryEntityEditDTO**: 实体中介修改DTO
|
||||
- 包含social_credit_code和可编辑字段
|
||||
- social_credit_code不可为空
|
||||
|
||||
**CcdiIntermediaryQueryDTO**: 统一查询DTO
|
||||
- 支持: name, certificateNo, intermediaryType筛选
|
||||
|
||||
### 6.3 VO视图对象
|
||||
|
||||
**CcdiIntermediaryVO**: 统一列表VO
|
||||
- 包含intermediary_type字段区分类型(1=个人, 2=实体)
|
||||
- 统一字段: id, name, certificate_no, intermediary_type, company, create_time等
|
||||
|
||||
**CcdiIntermediaryPersonDetailVO**: 个人中介详情VO
|
||||
- 包含个人中介的所有详细信息
|
||||
|
||||
**CcdiIntermediaryEntityDetailVO**: 实体中介详情VO
|
||||
- 包含实体中介的所有详细信息
|
||||
|
||||
### 6.4 Excel导入导出类
|
||||
|
||||
**CcdiIntermediaryPersonExcel**: 个人中介Excel类
|
||||
- 使用EasyExcel注解
|
||||
- 字段校验和格式化
|
||||
|
||||
**CcdiIntermediaryEntityExcel**: 实体中介Excel类
|
||||
- 使用EasyExcel注解
|
||||
- 字段校验和格式化
|
||||
|
||||
---
|
||||
|
||||
## 7. 业务规则
|
||||
|
||||
### 7.1 唯一性约束
|
||||
|
||||
1. **个人中介**:
|
||||
- `person_id`(证件号)必须唯一
|
||||
- 新增时检查是否已存在
|
||||
- 修改时检查是否已存在(排除自身)
|
||||
|
||||
2. **实体中介**:
|
||||
- `social_credit_code`(统一社会信用代码)必须唯一
|
||||
- 新增时检查是否已存在
|
||||
- 修改时检查是否已存在(排除自身)
|
||||
|
||||
### 7.2 数据自动填充
|
||||
|
||||
**个人中介**:
|
||||
- data_source: MANUAL(手动录入) / IMPORT(批量导入)
|
||||
- person_type: 中介
|
||||
|
||||
**实体中介**:
|
||||
- risk_level: 1 (高风险)
|
||||
- ent_source: INTERMEDIARY (中介)
|
||||
- data_source: MANUAL(手动录入) / IMPORT(批量导入)
|
||||
|
||||
### 7.3 字典类型
|
||||
|
||||
| 字典类型 | 用途 |
|
||||
|---------|------|
|
||||
| ccdi_indiv_gender | 个人中介性别 |
|
||||
| ccdi_certificate_type | 证件类型 |
|
||||
| ccdi_relation_type | 关联关系 |
|
||||
| ccdi_entity_type | 主体类型 |
|
||||
| ccdi_enterprise_nature | 企业性质 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理
|
||||
|
||||
### 8.1 业务错误码
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 1001 | 证件号已存在 |
|
||||
| 1002 | 统一社会信用代码已存在 |
|
||||
| 1003 | 数据不存在 |
|
||||
| 1004 | 姓名不能为空 |
|
||||
| 1005 | 证件号不能为空 |
|
||||
| 1006 | 企业名称不能为空 |
|
||||
|
||||
### 8.2 异常处理策略
|
||||
|
||||
- 使用`@ControllerAdvice`全局异常处理
|
||||
- 业务异常使用自定义BizException
|
||||
- 参数校验异常自动返回字段错误信息
|
||||
|
||||
---
|
||||
|
||||
## 9. 测试策略
|
||||
|
||||
### 9.1 单元测试
|
||||
|
||||
- Service层业务逻辑测试
|
||||
- Mapper层SQL查询测试
|
||||
- 唯一性校验测试
|
||||
|
||||
### 9.2 集成测试
|
||||
|
||||
- Controller接口测试
|
||||
- 导入导出功能测试
|
||||
- 联合查询分页测试
|
||||
|
||||
### 9.3 测试脚本
|
||||
|
||||
- 生成可执行的HTTP测试脚本
|
||||
- 使用admin/admin123账号获取token
|
||||
- 保存测试结果并生成测试报告
|
||||
|
||||
---
|
||||
|
||||
## 10. 实现计划
|
||||
|
||||
### 10.1 开发顺序
|
||||
|
||||
1. 创建Entity实体类
|
||||
2. 创建Mapper接口和XML
|
||||
3. 创建DTO/VO对象
|
||||
4. 实现Service层业务逻辑
|
||||
5. 实现Controller层接口
|
||||
6. 实现Excel导入导出功能
|
||||
7. 编写测试用例
|
||||
8. 生成API文档
|
||||
|
||||
### 10.2 技术要点
|
||||
|
||||
- 使用MyBatis Plus的BaseMapper简化CRUD操作
|
||||
- 使用@Resource注入,替代@Autowired
|
||||
- 实体类不继承BaseEntity,单独添加审计字段
|
||||
- 简单CRUD使用MyBatis Plus方法,复杂查询使用XML
|
||||
- 所有Controller接口添加完整的Swagger注解
|
||||
- 使用@Validated和JSR-303进行参数校验
|
||||
|
||||
---
|
||||
|
||||
## 11. 附录
|
||||
|
||||
### 11.1 相关文档
|
||||
|
||||
- [中介黑名单管理API文档.md](../api/中介黑名单管理API文档.md)
|
||||
- [中介黑名单后端.md](../docs/中介黑名单后端.md)
|
||||
- [ccdi_biz_intermediary.csv](../docs/ccdi_biz_intermediary.csv)
|
||||
- [ccdi_enterprise_base_info.csv](../docs/ccdi_enterprise_base_info.csv)
|
||||
|
||||
### 11.2 更新日志
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| 1.0 | 2026-02-04 | 初始版本,完成系统设计 |
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
24
doc/docs/ccdi_biz_intermediary.csv
Normal file
24
doc/docs/ccdi_biz_intermediary.csv
Normal file
@@ -0,0 +1,24 @@
|
||||
中介人员基本信息表:ccdi_biz_intermediary,,,,,,
|
||||
序号,字段名,类型,默认值,是否可为空,是否主键,注释
|
||||
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,身份证,否,否,证件类型
|
||||
8,person_id,VARCHAR,-,否,否,证件号码
|
||||
9,mobile,VARCHAR,-,是,否,手机号码
|
||||
10,wechat_no,VARCHAR,-,是,否,微信号
|
||||
11,contact_address,VARCHAR,-,是,否,联系地址
|
||||
12,company,VARCHAR,-,是,否,所在公司
|
||||
13,social_credit_code,VARCHAR,,,,企业统一信用码
|
||||
14,position,VARCHAR,-,是,否,职位
|
||||
15,related_num_id,VARCHAR,-,是,否,关联人员ID
|
||||
16,relation_type,VARCHAR,-,是,否,关联关系
|
||||
17,date_source,,,,,"数据来源,MANUAL:手动录入, SYSTEM:系统同步, IMPORT:批量导入, API:接口获取"
|
||||
18,remark,,,,,备注信息
|
||||
19,created_by,VARCHAR,-,否,-,记录创建人
|
||||
20,updated_by,VARCHAR,-,是,-,记录更新人
|
||||
21,create_time,DATETIME,,否,,记录创建时间
|
||||
22,update_time,DATETIME,-,是,-,记录更新时间
|
||||
|
26
doc/docs/ccdi_enterprise_base_info.csv
Normal file
26
doc/docs/ccdi_enterprise_base_info.csv
Normal file
@@ -0,0 +1,26 @@
|
||||
3.企业主体信息表:ccdi_enterprise_base_info,,,,,,
|
||||
序号,字段名,类型,默认值,是否可为空,是否主键,注释
|
||||
1,social_credit_code,VARCHAR,-,否,是,统一社会信用代码,员工企业关联关系表的外键
|
||||
2,enterprise_name,VARCHAR,-,否,-,企业名称
|
||||
3,enterprise_type,VARCHAR,-,否,-,"企业类型,有限责任公司、股份有限公司、合伙企业、个体工商户、外资企业等"
|
||||
4,enterprise_nature,VARCHAR,-,是,-,"企业性质,国企、民企、外企、合资、其他"
|
||||
5,industry_class,VARCHAR,-,是,-,行业分类
|
||||
6,industry_name,VARCHAR,-,是,-,所属行业
|
||||
7,establish_date,DATE,-,是,-,成立日期
|
||||
8,register_address,VARCHAR,-,是,-,注册地址
|
||||
9,legal_representative,VARCHAR,-,是,-,法定代表人
|
||||
10,legal_cert_type,VARCHAR,-,是,-,法定代表人证件类型
|
||||
11,legal_cert_no,VARCHAR,-,是,-,法定代表人证件号码
|
||||
12,shareholder1,VARCHAR,-,是,-,股东1
|
||||
13,shareholder2,VARCHAR,-,是,-,股东2
|
||||
14,shareholder3,VARCHAR,-,是,-,股东3
|
||||
15,shareholder4,VARCHAR,-,是,-,股东4
|
||||
16,shareholder5,VARCHAR,-,是,-,股东5
|
||||
17,status,VARCHAR,,,,经营状态
|
||||
18,create_time,DATETIME,当前时间,否,-,创建时间
|
||||
19,update_time,DATETIME,当前时间,否,-,更新时间
|
||||
20,created_by,VARCHAR,-,否,-,创建人
|
||||
21,updated_by,VARCHAR,-,是,-,更新人
|
||||
22,data_source,VARCHAR,MANUAL,是,-,"数据来源,MANUAL:手动录入, SYSTEM:系统同步, API:接口获取, IMPORT:批量导入"
|
||||
23,risk_level,VARCHAR(10),1,是,否,"风险等级:1-高风险, 2-中风险, 3-低风险"
|
||||
24,ent_source,VARCHAR(20),GENERAL,否,否,"企业来源:GENERAL-一般企业, EMP_RELATION-员工关系人, CREDIT_CUSTOMER-信贷客户, INTERMEDIARY-中介, BOTH-兼有"
|
||||
|
1
doc/docs/中介黑名单后端.md
Normal file
1
doc/docs/中介黑名单后端.md
Normal file
@@ -0,0 +1 @@
|
||||
实现中介黑名单管理的后端接口开发。中介分为个人中介和实体中介。个人中介的表字段为 @ccdi_biz_intermediary.csv。实体中介表字段为 @ccdi_enterprise_base_info.csv,风险等级为高风险,企业来源为中介。需要生成的接口:个人中介的新增、修改接口,以证件号为关联键;个人中介导入模板下载,个人中介文件上传导入新增;实体中介类的新增、修改接口;实体中介导入模板下载,上传导入新增;列表查询,要求联合查询两种类型的中介,也可以支持查询单种类的中介。
|
||||
919
doc/frontend/上传数据页面UI设计文档.md
Normal file
919
doc/frontend/上传数据页面UI设计文档.md
Normal file
@@ -0,0 +1,919 @@
|
||||
# 上传数据页面 UI 设计文档
|
||||
|
||||
## 1. 页面概述
|
||||
|
||||
### 1.1 功能描述
|
||||
上传数据页面是纪检初核系统中项目管理模块的核心页面,支持在一个项目中上传多个主体/账户数据进行汇总/独立分析。提供流水导入、征信导入、员工家庭关系导入、名单库选择等功能。
|
||||
|
||||
### 1.2 页面路径
|
||||
- 菜单位置:项目管理 > 项目详情 > 上传数据
|
||||
- 路由路径:`/project/:id/upload-data`
|
||||
|
||||
### 1.3 页面状态
|
||||
- 项目状态:已完成
|
||||
- 最后更新时间:2024-01-20 15:30
|
||||
|
||||
---
|
||||
|
||||
## 2. 页面布局
|
||||
|
||||
### 2.1 整体结构
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 面包屑导航:项目管理 / 项目详情 / 上传数据 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 页面标题区 │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ 上传数据 │ │
|
||||
│ │ 项目状态:已完成 最后更新:2024-01-20 15:30 │ │
|
||||
│ │ 支持在一个项目中上传多个主体/账户数据,进行汇总/独立分析 │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 主要内容区(网格布局) │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 流水导入 │ │ 已上传流水查询 │ │
|
||||
│ │ [上传组件] │ │ [上传组件] │ │
|
||||
│ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ 征信导入 │ │ 员工家庭关系导入 │ │
|
||||
│ │ [上传组件] │ │ [上传组件] │ │
|
||||
│ └─────────────────┘ └─────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ 名单库选择 │ │
|
||||
│ │ ☑ 高风险人员名单(68人) ☑ 历史可疑人员名单 │ │
|
||||
│ │ ☑ 监管关注名单(32人) │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 数据质量检查区 │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ 数据完整性 格式一致性 余额连续性 │ │
|
||||
│ │ 98.5% 95.2% 92.8% │ │
|
||||
│ │ 检查结果: [查看详情] │ │
|
||||
│ │ • 发现 23 条数据格式不一致 │ │
|
||||
│ │ • 发现 5 条余额连续性异常 │ │
|
||||
│ │ • 发现 12 条缺失关键字段 │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 操作按钮区 │
|
||||
│ [拉取本行信息] [生成报告] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 响应式布局
|
||||
- 桌面端(≥1200px):4列网格布局
|
||||
- 平板端(768px-1199px):2列网格布局
|
||||
- 移动端(<768px):单列布局
|
||||
|
||||
---
|
||||
|
||||
## 3. 组件设计
|
||||
|
||||
### 3.1 FileUploadCard 上传卡片组件
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface FileUploadCardProps {
|
||||
title: string; // 卡片标题
|
||||
description: string; // 描述文字
|
||||
acceptTypes: string[]; // 接受的文件类型,如 ['xlsx', 'xls', 'pdf']
|
||||
maxSize?: number; // 最大文件大小(MB),默认 10
|
||||
multiple?: boolean; // 是否支持多文件上传
|
||||
uploadUrl: string; // 上传接口地址
|
||||
onUploadSuccess?: (files: UploadedFile[]) => void;
|
||||
onUploadError?: (error: Error) => void;
|
||||
showFileList?: boolean; // 是否显示已上传文件列表
|
||||
}
|
||||
```
|
||||
|
||||
**UI 结构:**
|
||||
```vue
|
||||
<template>
|
||||
<el-card class="upload-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h3>{{ title }}</h3>
|
||||
<el-tooltip :content="description" placement="top">
|
||||
<i class="el-icon-info"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-upload
|
||||
class="upload-area"
|
||||
:action="uploadUrl"
|
||||
:accept="acceptTypes.join(',')"
|
||||
:multiple="multiple"
|
||||
:limit="10"
|
||||
:file-list="fileList"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:before-upload="beforeUpload"
|
||||
drag
|
||||
>
|
||||
<div class="upload-content">
|
||||
<i class="el-icon-upload"></i>
|
||||
<p>拖拽文件到此处或点击上传</p>
|
||||
<p class="upload-tip">支持格式: {{ acceptTypes.join(', ') }}</p>
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
<div v-if="showFileList && uploadedFiles.length" class="file-list">
|
||||
<h4>已上传文件</h4>
|
||||
<el-table :data="uploadedFiles" size="small">
|
||||
<el-table-column prop="fileName" label="文件名" />
|
||||
<el-table-column prop="fileSize" label="大小" width="100" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="160" />
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'success' ? 'success' : 'danger'">
|
||||
{{ row.status === 'success' ? '成功' : '失败' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="text" size="small" @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3.2 CheckboxGroupSelector 名单库选择组件
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface CheckboxGroupSelectorProps {
|
||||
options: NameListOption[];
|
||||
modelValue: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
}
|
||||
|
||||
interface NameListOption {
|
||||
label: string; // 显示文本
|
||||
value: string; // 选中值
|
||||
count: number; // 人数统计
|
||||
disabled?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**UI 结构:**
|
||||
```vue
|
||||
<template>
|
||||
<el-card class="name-list-selector">
|
||||
<template #header>
|
||||
<h3>名单库选择</h3>
|
||||
</template>
|
||||
<p class="selector-description">选择中介库管理内的名单</p>
|
||||
<el-checkbox-group v-model="selectedLists" @change="handleChange">
|
||||
<el-checkbox
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
:label="option.value"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
{{ option.label }}({{ option.count }}人)
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-card>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3.3 DataQualityPanel 数据质量检查面板
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface DataQualityPanelProps {
|
||||
metrics: QualityMetric[];
|
||||
issues: QualityIssue[];
|
||||
onCheckQuality?: () => void;
|
||||
onViewDetails?: (issue: QualityIssue) => void;
|
||||
}
|
||||
|
||||
interface QualityMetric {
|
||||
name: string; // 指标名称
|
||||
value: number; // 百分比值
|
||||
status: 'good' | 'warning' | 'error';
|
||||
}
|
||||
|
||||
interface QualityIssue {
|
||||
type: string; // 问题类型
|
||||
count: number; // 数量
|
||||
description: string;
|
||||
details?: any[];
|
||||
}
|
||||
```
|
||||
|
||||
**UI 结构:**
|
||||
```vue
|
||||
<template>
|
||||
<el-card class="quality-panel">
|
||||
<template #header>
|
||||
<div class="panel-header">
|
||||
<h3>数据质量检查</h3>
|
||||
<el-button type="primary" size="small" @click="handleCheck">
|
||||
重新检查
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 质量指标 -->
|
||||
<div class="metrics-container">
|
||||
<div
|
||||
v-for="metric in metrics"
|
||||
:key="metric.name"
|
||||
class="metric-item"
|
||||
:class="`metric-${metric.status}`"
|
||||
>
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="metric.value"
|
||||
:status="metric.status"
|
||||
/>
|
||||
<span class="metric-name">{{ metric.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 问题列表 -->
|
||||
<div class="issues-section">
|
||||
<h4>检查结果</h4>
|
||||
<el-alert
|
||||
v-for="(issue, index) in issues"
|
||||
:key="index"
|
||||
:type="getIssueType(issue)"
|
||||
:closable="false"
|
||||
class="issue-item"
|
||||
>
|
||||
<template #title>
|
||||
发现 <strong>{{ issue.count }}</strong> {{ issue.description }}
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-button type="text" @click="handleViewDetails">查看详情 →</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 交互说明
|
||||
|
||||
### 4.1 文件上传流程
|
||||
|
||||
1. **拖拽上传**
|
||||
- 用户拖拽文件到上传区域
|
||||
- 显示上传进度条
|
||||
- 上传成功后显示成功提示
|
||||
- 自动添加到已上传文件列表
|
||||
|
||||
2. **点击上传**
|
||||
- 点击上传区域触发文件选择对话框
|
||||
- 选择文件后开始上传
|
||||
- 显示上传进度
|
||||
|
||||
3. **文件验证**
|
||||
- 文件格式验证:只接受指定格式
|
||||
- 文件大小验证:超过限制显示错误提示
|
||||
- 重复文件验证:同名文件提示是否覆盖
|
||||
|
||||
4. **上传状态**
|
||||
- 上传中:显示进度条
|
||||
- 上传成功:绿色勾选标记
|
||||
- 上传失败:红色错误标记,显示错误信息
|
||||
|
||||
### 4.2 名单库选择
|
||||
|
||||
1. 默认选中全部名单库
|
||||
2. 点击复选框切换选中状态
|
||||
3. 实时更新选中人数统计
|
||||
4. 取消选中时显示确认提示
|
||||
|
||||
### 4.3 数据质量检查
|
||||
|
||||
1. **自动触发**
|
||||
- 文件上传完成后自动触发
|
||||
- 显示检查进度
|
||||
|
||||
2. **手动触发**
|
||||
- 点击"重新检查"按钮
|
||||
- 覆盖之前的检查结果
|
||||
|
||||
3. **结果展示**
|
||||
- 三个核心指标以环形进度图展示
|
||||
- 颜色指示:绿色(≥95%)、黄色(85-94%)、红色(<85%)
|
||||
- 问题列表按严重程度排序
|
||||
|
||||
### 4.4 按钮操作
|
||||
|
||||
1. **拉取本行信息**
|
||||
- 点击后显示加载状态
|
||||
- 从本行系统拉取相关数据
|
||||
- 完成后显示成功提示并刷新页面
|
||||
|
||||
2. **生成报告**
|
||||
- 验证必须上传至少一个文件
|
||||
- 显示报告生成进度
|
||||
- 生成成功后跳转到报告页面
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据结构
|
||||
|
||||
### 5.1 后端接口
|
||||
|
||||
#### 5.1.1 获取项目上传数据状态
|
||||
```typescript
|
||||
GET /api/project/{projectId}/upload-status
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"projectStatus": "已完成",
|
||||
"lastUpdateTime": "2024-01-20 15:30:00",
|
||||
"uploadedFiles": {
|
||||
"transactionFiles": [], // 流水文件列表
|
||||
"inquiryFiles": [], // 征信文件列表
|
||||
"familyRelationFiles": [] // 家庭关系文件列表
|
||||
},
|
||||
"selectedNameLists": [], // 已选名单库
|
||||
"qualityMetrics": { // 质量指标
|
||||
"completeness": 98.5,
|
||||
"consistency": 95.2,
|
||||
"continuity": 92.8
|
||||
},
|
||||
"qualityIssues": [] // 质量问题列表
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.2 上传文件接口
|
||||
```typescript
|
||||
POST /api/project/{projectId}/upload
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
Body:
|
||||
{
|
||||
"fileType": "transaction" | "inquiry" | "family_relation",
|
||||
"files": File[]
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"successCount": 2,
|
||||
"failedCount": 0,
|
||||
"uploadedFiles": [
|
||||
{
|
||||
"fileId": "123456",
|
||||
"fileName": "流水数据.xlsx",
|
||||
"fileSize": 2048576,
|
||||
"uploadTime": "2024-01-20 15:30:00",
|
||||
"status": "success"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.3 删除文件接口
|
||||
```typescript
|
||||
DELETE /api/project/{projectId}/file/{fileId}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "删除成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.4 获取名单库列表
|
||||
```typescript
|
||||
GET /api/name-list/options
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"value": "high_risk",
|
||||
"label": "高风险人员名单",
|
||||
"count": 68
|
||||
},
|
||||
{
|
||||
"value": "history_suspicious",
|
||||
"label": "历史可疑人员名单",
|
||||
"count": 45
|
||||
},
|
||||
{
|
||||
"value": "regulatory_focus",
|
||||
"label": "监管关注名单",
|
||||
"count": 32
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.5 更新名单库选择
|
||||
```typescript
|
||||
PUT /api/project/{projectId}/name-lists
|
||||
|
||||
Body:
|
||||
{
|
||||
"selectedLists": ["high_risk", "history_suspicious", "regulatory_focus"]
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.6 执行数据质量检查
|
||||
```typescript
|
||||
POST /api/project/{projectId}/quality-check
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"checkId": "qc_123456",
|
||||
"status": "completed",
|
||||
"metrics": {
|
||||
"completeness": 98.5,
|
||||
"consistency": 95.2,
|
||||
"continuity": 92.8
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"type": "format_inconsistency",
|
||||
"count": 23,
|
||||
"description": "条数据格式不一致"
|
||||
},
|
||||
{
|
||||
"type": "balance_anomaly",
|
||||
"count": 5,
|
||||
"description": "条余额连续性异常"
|
||||
},
|
||||
{
|
||||
"type": "missing_field",
|
||||
"count": 12,
|
||||
"description": "条缺失关键字段"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.7 拉取本行信息
|
||||
```typescript
|
||||
POST /api/project/{projectId}/pull-bank-info
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "拉取成功",
|
||||
"data": {
|
||||
"pulledRecords": 156,
|
||||
"pullTime": "2024-01-20 15:35:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.1.8 生成报告
|
||||
```typescript
|
||||
POST /api/project/{projectId}/generate-report
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"reportId": "rpt_789012",
|
||||
"reportUrl": "/project/123/report/rpt_789012",
|
||||
"generateTime": "2024-01-20 15:40:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 前端数据模型
|
||||
|
||||
```typescript
|
||||
// 上传文件类型
|
||||
type UploadFileType = 'transaction' | 'inquiry' | 'family_relation';
|
||||
|
||||
// 上传文件状态
|
||||
type UploadStatus = 'uploading' | 'success' | 'error';
|
||||
|
||||
// 上传的文件
|
||||
interface UploadedFile {
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
uploadTime: string;
|
||||
status: UploadStatus;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
// 名单库选项
|
||||
interface NameListOption {
|
||||
value: string;
|
||||
label: string;
|
||||
count: number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// 质量指标
|
||||
interface QualityMetric {
|
||||
name: string;
|
||||
value: number;
|
||||
status: 'good' | 'warning' | 'error';
|
||||
}
|
||||
|
||||
// 质量问题
|
||||
interface QualityIssue {
|
||||
type: string;
|
||||
count: number;
|
||||
description: string;
|
||||
details?: any[];
|
||||
}
|
||||
|
||||
// 项目上传数据状态
|
||||
interface ProjectUploadStatus {
|
||||
projectStatus: string;
|
||||
lastUpdateTime: string;
|
||||
uploadedFiles: {
|
||||
transactionFiles: UploadedFile[];
|
||||
inquiryFiles: UploadedFile[];
|
||||
familyRelationFiles: UploadedFile[];
|
||||
};
|
||||
selectedNameLists: string[];
|
||||
qualityMetrics: {
|
||||
completeness: number;
|
||||
consistency: number;
|
||||
continuity: number;
|
||||
};
|
||||
qualityIssues: QualityIssue[];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 样式规范
|
||||
|
||||
### 6.1 颜色规范
|
||||
```scss
|
||||
// 主色
|
||||
$primary-color: #409EFF;
|
||||
$success-color: #67C23A;
|
||||
$warning-color: #E6A23C;
|
||||
$danger-color: #F56C6C;
|
||||
$info-color: #909399;
|
||||
|
||||
// 中性色
|
||||
$text-primary: #303133;
|
||||
$text-regular: #606266;
|
||||
$text-secondary: #909399;
|
||||
$text-placeholder: #C0C4CC;
|
||||
|
||||
// 边框色
|
||||
$border-base: #DCDFE6;
|
||||
$border-light: #E4E7ED;
|
||||
$border-lighter: #EBEEF5;
|
||||
$border-extra-light: #F2F6FC;
|
||||
|
||||
// 背景色
|
||||
$bg-color: #F5F7FA;
|
||||
$card-bg: #FFFFFF;
|
||||
```
|
||||
|
||||
### 6.2 间距规范
|
||||
```scss
|
||||
$spacing-xs: 4px;
|
||||
$spacing-sm: 8px;
|
||||
$spacing-md: 16px;
|
||||
$spacing-lg: 24px;
|
||||
$spacing-xl: 32px;
|
||||
```
|
||||
|
||||
### 6.3 圆角规范
|
||||
```scss
|
||||
$border-radius-sm: 2px;
|
||||
$border-radius-base: 4px;
|
||||
$border-radius-lg: 8px;
|
||||
$border-radius-circle: 50%;
|
||||
```
|
||||
|
||||
### 6.4 阴影规范
|
||||
```scss
|
||||
$box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
|
||||
$box-shadow-dark: 0 2px 8px rgba(0, 0, 0, 0.15), 0 0 6px rgba(0, 0, 0, 0.1);
|
||||
$box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 组件样式代码
|
||||
|
||||
### 7.1 上传卡片样式
|
||||
```scss
|
||||
.upload-card {
|
||||
height: 100%;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.el-icon-info {
|
||||
color: $info-color;
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
border: 2px dashed $border-base;
|
||||
border-radius: $border-radius-lg;
|
||||
background: $bg-color;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary-color;
|
||||
background: #F0F7FF;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
.el-icon-upload {
|
||||
font-size: 48px;
|
||||
color: $primary-color;
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: $spacing-xs 0;
|
||||
font-size: 14px;
|
||||
color: $text-regular;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 12px;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
border-top: 1px solid $border-light;
|
||||
padding-top: $spacing-md;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 $spacing-sm 0;
|
||||
font-size: 14px;
|
||||
color: $text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 数据质量面板样式
|
||||
```scss
|
||||
.quality-panel {
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.metrics-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
.metric-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.el-progress {
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
.metric-name {
|
||||
font-size: 14px;
|
||||
color: $text-regular;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.issues-section {
|
||||
border-top: 1px solid $border-light;
|
||||
padding-top: $spacing-md;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 $spacing-md 0;
|
||||
font-size: 14px;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.issue-item {
|
||||
margin-bottom: $spacing-sm;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 页面整体布局样式
|
||||
```scss
|
||||
.upload-data-page {
|
||||
padding: $spacing-lg;
|
||||
background: $bg-color;
|
||||
min-height: calc(100vh - 84px);
|
||||
|
||||
.page-header {
|
||||
background: $card-bg;
|
||||
padding: $spacing-lg;
|
||||
border-radius: $border-radius-lg;
|
||||
margin-bottom: $spacing-lg;
|
||||
box-shadow: $box-shadow-base;
|
||||
|
||||
h1 {
|
||||
margin: 0 0 $spacing-sm 0;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
display: flex;
|
||||
gap: $spacing-lg;
|
||||
font-size: 14px;
|
||||
color: $text-secondary;
|
||||
margin-top: $spacing-sm;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.label {
|
||||
margin-right: $spacing-xs;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: $success-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: $spacing-md;
|
||||
padding: $spacing-md;
|
||||
background: #F0F9FF;
|
||||
border-left: 3px solid $primary-color;
|
||||
border-radius: $border-radius-base;
|
||||
font-size: 14px;
|
||||
color: $text-regular;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: $spacing-lg;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: $spacing-lg;
|
||||
margin-top: $spacing-xl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 技术实现要点
|
||||
|
||||
### 8.1 文件上传
|
||||
- 使用 Element UI 的 `el-upload` 组件
|
||||
- 支持拖拽上传和点击上传
|
||||
- 实现文件类型和大小校验
|
||||
- 显示上传进度
|
||||
- 支持断点续传(可选)
|
||||
|
||||
### 8.2 数据质量检查
|
||||
- 异步执行检查任务
|
||||
- 使用 WebSocket 或轮询获取检查进度
|
||||
- 实时更新进度和结果
|
||||
|
||||
### 8.3 状态管理
|
||||
- 使用 Vuex 管理上传状态
|
||||
- 缓存已上传文件列表
|
||||
- 同步名单库选择状态
|
||||
|
||||
### 8.4 性能优化
|
||||
- 文件分片上传大文件
|
||||
- 使用 Web Worker 处理文件预检查
|
||||
- 虚拟滚动展示大量文件列表
|
||||
|
||||
---
|
||||
|
||||
## 9. 测试要点
|
||||
|
||||
### 9.1 功能测试
|
||||
- 文件上传各种格式
|
||||
- 文件大小限制验证
|
||||
- 删除文件功能
|
||||
- 名单库选择功能
|
||||
- 数据质量检查准确性
|
||||
- 报告生成功能
|
||||
|
||||
### 9.2 兼容性测试
|
||||
- 主流浏览器兼容
|
||||
- 不同屏幕尺寸适配
|
||||
- 文件格式兼容性
|
||||
|
||||
### 9.3 性能测试
|
||||
- 大文件上传性能
|
||||
- 多文件同时上传
|
||||
- 页面加载性能
|
||||
|
||||
### 9.4 异常处理测试
|
||||
- 网络中断处理
|
||||
- 文件上传失败处理
|
||||
- 服务器错误处理
|
||||
- 文件格式错误处理
|
||||
|
||||
---
|
||||
|
||||
## 10. 附录
|
||||
|
||||
### 10.1 相关页面
|
||||
- 项目详情页:`/project/:id/detail`
|
||||
- 参数配置页:`/project/:id/config`
|
||||
- 初核结果页:`/project/:id/result`
|
||||
- 报告页面:`/project/:id/report/:reportId`
|
||||
|
||||
### 10.2 权限要求
|
||||
- 需要项目成员权限
|
||||
- 上传操作需要编辑权限
|
||||
- 删除操作需要删除权限
|
||||
- 生成报告需要报告权限
|
||||
|
||||
### 10.3 相关文档
|
||||
- [Element UI Upload 组件文档](https://element.eleme.cn/#/zh-CN/component/upload)
|
||||
- [若依框架前端开发规范](../前端开发规范.md)
|
||||
- [项目接口文档](../API文档/项目管理模块.md)
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建时间**: 2024-01-30
|
||||
**最后更新**: 2024-01-30
|
||||
**文档状态**: 待评审
|
||||
336
doc/plans/2025-01-30-project-detail-page-design.md
Normal file
336
doc/plans/2025-01-30-project-detail-page-design.md
Normal file
@@ -0,0 +1,336 @@
|
||||
# 项目详情页面设计文档
|
||||
|
||||
**创建日期**: 2025-01-30
|
||||
**设计者**: Claude Code
|
||||
**状态**: 待实施
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 需求描述
|
||||
|
||||
开发一个项目详情页面,在项目管理列表中,点击项目那一行或者查看详情跳转到项目详情页面。顶部有一个导航栏,里面有按钮切换项目详情的不同页面。
|
||||
|
||||
### 1.2 功能模块
|
||||
|
||||
- **上传数据**(默认):批量上传流水、征信、员工家庭关系数据,选择名单库
|
||||
- **参数配置**:配置项目分析参数和排查规则
|
||||
- **结果总览**:查看项目分析结果的总体概况
|
||||
- **专项排查**:针对特定风险类型进行深度排查
|
||||
- **流水明细查询**:查询和筛选具体的流水记录明细
|
||||
|
||||
---
|
||||
|
||||
## 2. 整体架构设计
|
||||
|
||||
### 2.1 路由结构
|
||||
|
||||
采用独立页面路由方式:
|
||||
|
||||
```
|
||||
路由: /project-detail/:projectId
|
||||
组件: @/views/ccdiProject/detail/index.vue
|
||||
```
|
||||
|
||||
### 2.2 页面布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 顶部导航 (PageHeader) │
|
||||
│ [返回] 项目名称 [状态] │
|
||||
│ [上传数据] [参数配置] [结果总览] ... │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 内容区域 (el-tabs) │
|
||||
│ 根据选中标签显示对应子页面 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.3 组件层次结构
|
||||
|
||||
```
|
||||
detail/
|
||||
├── index.vue # 主页面容器
|
||||
├── components/
|
||||
│ ├── PageHeader.vue # 顶部导航
|
||||
│ ├── UploadData.vue # 上传数据
|
||||
│ ├── ParameterConfig.vue # 参数配置
|
||||
│ ├── ResultOverview.vue # 结果总览
|
||||
│ ├── SpecialCheck.vue # 专项排查
|
||||
│ └── TransactionDetail.vue # 流水明细查询
|
||||
└── api.js # API 接口定义
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 上传数据页面详细设计
|
||||
|
||||
### 3.1 页面布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 批量上传数据 [生成报告][拉取本行]│
|
||||
│ 支持在一个项目中上传多个主体/账户数据 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ │流水 │ │征信 │ │员工 │ │名单 │ │
|
||||
│ │导入 │ │导入 │ │家庭 │ │库选择 │ │
|
||||
│ └──────┘ └──────┘ └──────┘ └──────┘ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 数据质量检查区 │
|
||||
│ - 检查结果列表 │
|
||||
│ - 指标卡片(完整性、一致性、连续性) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 功能模块
|
||||
|
||||
#### 3.2.1 流水导入
|
||||
- 支持格式:xlsx, xls, pdf
|
||||
- 拖拽上传 + 点击上传
|
||||
- 上传进度显示
|
||||
|
||||
#### 3.2.2 征信导入
|
||||
- 支持格式:html
|
||||
- 解析征信报告
|
||||
|
||||
#### 3.2.3 员工家庭关系导入
|
||||
- 支持格式:xlsx, xls
|
||||
- Excel 模板上传
|
||||
|
||||
#### 3.2.4 名单库选择
|
||||
- 高风险人员名单(68人)
|
||||
- 历史可疑人员名单(45人)
|
||||
- 监管关注名单(32人)
|
||||
|
||||
#### 3.2.5 数据质量检查
|
||||
- 数据完整性:98.5%
|
||||
- 格式一致性:95.2%
|
||||
- 余额连续性:92.8%
|
||||
- 检查结果详情
|
||||
|
||||
---
|
||||
|
||||
## 4. 其他子页面框架设计
|
||||
|
||||
### 4.1 参数配置页面
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 参数配置 [保存] [重置] │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 预警阈值 │ │ 排查规则 │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ 高级配置(可折叠) │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 结果总览页面
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 结果总览 [导出报告] [刷新] │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||||
│ │ 总人数 │ │ 预警数 │ │ 可疑数 │ │
|
||||
│ └────────┘ └────────┘ └────────┘ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 预警分布图 │ │ 趋势图 │ │
|
||||
│ └──────────────┘ └──────────────┘ │
|
||||
│ 预警排名表格(Top 10) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.3 专项排查页面
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 专项排查 [新增排查] [批量导出]│
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 筛选条件:[风险类型] [严重程度] [状态] │
|
||||
│ 排查任务列表(表格) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.4 流水明细查询页面
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 流水明细查询 [导出] [高级查询] │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 查询条件:[账户] [日期范围] [金额范围] │
|
||||
│ 流水明细表格(分页) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 接口设计
|
||||
|
||||
### 5.1 接口列表
|
||||
|
||||
| 接口名称 | 方法 | 路径 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| 获取项目详情 | GET | `/ccdi/project/detail/{projectId}` | 获取项目基本信息 |
|
||||
| 上传流水文件 | POST | `/ccdi/project/transaction/upload` | 上传流水文件 |
|
||||
| 上传征信文件 | POST | `/ccdi/project/credit/upload` | 上传征信报告 |
|
||||
| 上传员工关系 | POST | `/ccdi/project/employee/upload` | 上传员工家庭关系 |
|
||||
| 获取名单库列表 | GET | `/ccdi/project/namelist/list` | 获取可选名单库 |
|
||||
| 保存名单库选择 | POST | `/ccdi/project/namelist/save` | 保存选择的名单库 |
|
||||
| 获取数据质量检查 | GET | `/ccdi/project/quality/check` | 获取质量检查指标 |
|
||||
| 生成报告 | POST | `/ccdi/project/report/generate` | 生成分析报告 |
|
||||
| 拉取本行信息 | GET | `/ccdi/project/own/info` | 获取本行员工信息 |
|
||||
| 保存参数配置 | POST | `/ccdi/project/config/save` | 保存项目参数 |
|
||||
| 获取结果总览 | GET | `/ccdi/project/overview` | 获取结果统计数据 |
|
||||
| 获取排查列表 | GET | `/ccdi/project/check/list` | 获取专项排查列表 |
|
||||
| 查询流水明细 | GET | `/ccdi/project/transaction/list` | 分页查询流水 |
|
||||
|
||||
### 5.2 Mock 数据示例
|
||||
|
||||
**项目详情**
|
||||
```javascript
|
||||
{
|
||||
code: 200,
|
||||
data: {
|
||||
projectId: 1,
|
||||
projectName: "2025年第一季度初核排查",
|
||||
projectDesc: "针对全行员工进行第一季度常规排查",
|
||||
projectStatus: "0",
|
||||
createTime: "2025-01-15",
|
||||
targetCount: 1250,
|
||||
warningCount: 23
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**数据质量检查结果**
|
||||
```javascript
|
||||
{
|
||||
code: 200,
|
||||
data: {
|
||||
completeness: 98.5,
|
||||
consistency: 95.2,
|
||||
continuity: 92.8,
|
||||
issues: [
|
||||
{ type: "格式不一致", count: 23 },
|
||||
{ type: "余额连续性异常", count: 5 },
|
||||
{ type: "缺失关键字段", count: 12 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 状态管理
|
||||
|
||||
### 6.1 Vuex Store
|
||||
|
||||
```javascript
|
||||
// store/modules/projectDetail.js
|
||||
const state = {
|
||||
currentProject: null,
|
||||
activeTab: 'upload',
|
||||
uploadStatus: {
|
||||
transaction: false,
|
||||
credit: false,
|
||||
employee: false,
|
||||
nameList: []
|
||||
},
|
||||
qualityCheck: null,
|
||||
pageCache: {}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 页面缓存
|
||||
|
||||
使用 `<keep-alive>` 缓存标签页内容,避免切换时重复加载。
|
||||
|
||||
---
|
||||
|
||||
## 7. 路由配置
|
||||
|
||||
```javascript
|
||||
// router/index.js
|
||||
{
|
||||
path: '/project-detail',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: ':projectId(\\d+)',
|
||||
component: () => import('@/views/ccdiProject/detail/index'),
|
||||
name: 'ProjectDetail',
|
||||
meta: {
|
||||
title: '项目详情',
|
||||
activeMenu: '/ccdiProject'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 文件目录结构
|
||||
|
||||
```
|
||||
ruoyi-ui/src/
|
||||
├── views/ccdiProject/
|
||||
│ ├── index.vue # 项目列表页(已存在)
|
||||
│ └── detail/ # 项目详情目录
|
||||
│ ├── index.vue # 主页面
|
||||
│ └── components/
|
||||
│ ├── PageHeader.vue
|
||||
│ ├── UploadData.vue
|
||||
│ ├── ParameterConfig.vue
|
||||
│ ├── ResultOverview.vue
|
||||
│ ├── SpecialCheck.vue
|
||||
│ └── TransactionDetail.vue
|
||||
├── api/
|
||||
│ └── ccdiProject/
|
||||
│ └── detail.js # 项目详情 API
|
||||
├── store/
|
||||
│ └── modules/
|
||||
│ └── projectDetail.js # Vuex 状态管理
|
||||
└── mock/
|
||||
└── projectDetail.js # Mock 数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 待实现功能清单
|
||||
|
||||
- [ ] 创建路由配置
|
||||
- [ ] 创建主页面容器
|
||||
- [ ] 实现 PageHeader 顶部导航组件
|
||||
- [ ] 实现 UploadData 上传数据页面
|
||||
- [ ] 流水导入功能
|
||||
- [ ] 征信导入功能
|
||||
- [ ] 员工家庭关系导入功能
|
||||
- [ ] 名单库选择功能
|
||||
- [ ] 数据质量检查展示
|
||||
- [ ] 实现 ParameterConfig 参数配置页面(框架)
|
||||
- [ ] 实现 ResultOverview 结果总览页面(框架)
|
||||
- [ ] 实现 SpecialCheck 专项排查页面(框架)
|
||||
- [ ] 实现 TransactionDetail 流水明细查询页面(框架)
|
||||
- [ ] 创建 Vuex 状态管理模块
|
||||
- [ ] 创建 API 接口定义
|
||||
- [ ] 创建 Mock 数据
|
||||
- [ ] 修改项目列表页跳转逻辑
|
||||
- [ ] 测试整体流程
|
||||
|
||||
---
|
||||
|
||||
## 10. 设计决策记录
|
||||
|
||||
| 决策点 | 选择 | 原因 |
|
||||
|-------|------|------|
|
||||
| 路由方式 | 独立页面路由 | 可通过URL直接访问,支持浏览器前进后退 |
|
||||
| 导航方式 | Tabs 标签页 | 交互流畅,适合频繁切换场景 |
|
||||
| 上传卡片布局 | 四列一行 | 节省空间,一目了然 |
|
||||
| 后端接口 | Mock 数据先行 | 前端可独立开发,后续对接真实接口 |
|
||||
| 状态管理 | Vuex | 便于跨组件数据共享和状态持久化 |
|
||||
1958
doc/plans/2026-02-04-intermediary-blacklist-implementation.md
Normal file
1958
doc/plans/2026-02-04-intermediary-blacklist-implementation.md
Normal file
File diff suppressed because it is too large
Load Diff
1177
doc/plans/2026-02-04-intermediary-blacklist-migration-test-plan.md
Normal file
1177
doc/plans/2026-02-04-intermediary-blacklist-migration-test-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,887 @@
|
||||
# 中介黑名单入库逻辑变更 - 测试验证计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 验证中介黑名单从单表切换到双表(cdi_biz_intermediary + ccdi_enterprise_base_info)的所有CRUD操作正确性
|
||||
|
||||
**架构:** 个人中介插入 ccdi_biz_intermediary 表,机构中介插入 ccdi_enterprise_base_info 表(自动设置高风险和中介来源标识),查询层合并两个表的数据返回
|
||||
|
||||
**技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, MySQL 8.2.0, Maven, JUnit 5
|
||||
|
||||
---
|
||||
|
||||
## 测试前准备
|
||||
|
||||
### Task 1: 确认数据库连接和环境
|
||||
|
||||
**Files:**
|
||||
- Check: `ruoyi-admin/src/main/resources/application-dev.yml`
|
||||
|
||||
**Step 1: 验证数据库连接配置**
|
||||
|
||||
检查配置文件中的数据库连接信息:
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
druid:
|
||||
master:
|
||||
url: jdbc:mysql://116.62.17.81:3306/ccdi
|
||||
username: root
|
||||
password: Kfcx@1234
|
||||
```
|
||||
|
||||
**Step 2: 确认目标表存在**
|
||||
|
||||
通过MCP工具验证表存在:
|
||||
```sql
|
||||
SHOW TABLES LIKE 'ccdi_biz_intermediary';
|
||||
SHOW TABLES LIKE 'ccdi_enterprise_base_info';
|
||||
```
|
||||
|
||||
预期: 两个表都存在
|
||||
|
||||
**Step 3: 检查表结构**
|
||||
|
||||
```sql
|
||||
DESCRIBE ccdi_biz_intermediary;
|
||||
DESCRIBE ccdi_enterprise_base_info;
|
||||
```
|
||||
|
||||
预期: 表结构与实体类字段匹配
|
||||
|
||||
---
|
||||
|
||||
## 功能测试 - 个人中介
|
||||
|
||||
### Task 2: 测试个人中介新增功能
|
||||
|
||||
**Files:**
|
||||
- Test API: `POST /ccdi/intermediary/person`
|
||||
- Backend: `CcdiIntermediaryBlacklistServiceImpl.insertPersonIntermediary()`
|
||||
|
||||
**Step 1: 准备测试数据**
|
||||
|
||||
创建测试数据文件 `test_person_add.json`:
|
||||
```json
|
||||
{
|
||||
"name": "测试个人中介",
|
||||
"certificateNo": "110101199001011234",
|
||||
"indivType": "中介",
|
||||
"indivSubType": "本人",
|
||||
"indivGender": "M",
|
||||
"indivCertType": "身份证",
|
||||
"indivPhone": "13800138000",
|
||||
"indivWechat": "test_wx001",
|
||||
"indivAddress": "北京市朝阳区测试路123号",
|
||||
"indivCompany": "测试公司",
|
||||
"indivPosition": "测试员",
|
||||
"indivRelatedId": "",
|
||||
"indivRelation": "",
|
||||
"status": "0",
|
||||
"remark": "自动化测试数据"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 获取认证Token**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/login/test \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin123"}' \
|
||||
| jq -r '.data.token'
|
||||
```
|
||||
|
||||
保存token到环境变量:
|
||||
```bash
|
||||
export TOKEN="获取到的token值"
|
||||
```
|
||||
|
||||
**Step 3: 调用新增接口**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/person \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_person_add.json
|
||||
```
|
||||
|
||||
预期响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 验证数据插入到正确的表**
|
||||
|
||||
通过MCP查询数据库:
|
||||
```sql
|
||||
SELECT * FROM ccdi_biz_intermediary
|
||||
WHERE person_id = '110101199001011234';
|
||||
```
|
||||
|
||||
预期:
|
||||
- 找到1条记录
|
||||
- name = '测试个人中介'
|
||||
- date_source = 'MANUAL'
|
||||
|
||||
**Step 5: 验证旧表无数据**
|
||||
|
||||
```sql
|
||||
SELECT * FROM ccdi_intermediary_blacklist
|
||||
WHERE certificate_no = '110101199001011234';
|
||||
```
|
||||
|
||||
预期: 0条记录(表可能不存在或为空)
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 测试个人中介列表查询
|
||||
|
||||
**Files:**
|
||||
- Test API: `GET /ccdi/intermediary/list`
|
||||
|
||||
**Step 1: 调用列表查询接口**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list?name=测试个人中介" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
预期响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"rows": [
|
||||
{
|
||||
"intermediaryId": 1,
|
||||
"name": "测试个人中介",
|
||||
"certificateNo": "110101199001011234",
|
||||
"intermediaryType": "1",
|
||||
"intermediaryTypeName": "个人",
|
||||
"status": "0",
|
||||
"statusName": "正常"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证查询结果来源**
|
||||
|
||||
确认数据来自 `ccdi_biz_intermediary` 表
|
||||
|
||||
**Step 3: 测试分页查询**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list?pageNum=1&pageSize=10" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
预期: 返回分页数据
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 测试个人中介详情查询
|
||||
|
||||
**Files:**
|
||||
- Test API: `GET /ccdi/intermediary/{id}`
|
||||
|
||||
**Step 1: 获取个人中介详情**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/1" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
预期响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"intermediaryId": 1,
|
||||
"name": "测试个人中介",
|
||||
"certificateNo": "110101199001011234",
|
||||
"intermediaryType": "1",
|
||||
"indivType": "中介",
|
||||
"indivGender": "M",
|
||||
"indivGenderName": "男",
|
||||
"indivPhone": "13800138000",
|
||||
"indivWechat": "test_wx001",
|
||||
"indivAddress": "北京市朝阳区测试路123号",
|
||||
"indivCompany": "测试公司",
|
||||
"indivPosition": "测试员",
|
||||
"dataSource": "MANUAL",
|
||||
"dataSourceName": "手动录入"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证所有字段正确映射**
|
||||
|
||||
检查个人专属字段是否正确:
|
||||
- indivType → person_type ✅
|
||||
- indivGender → gender ✅
|
||||
- indivPhone → mobile ✅
|
||||
- indivWechat → wechat_no ✅
|
||||
- indivAddress → contact_address ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 测试个人中介修改功能
|
||||
|
||||
**Files:**
|
||||
- Test API: `PUT /ccdi/intermediary/person`
|
||||
|
||||
**Step 1: 准备修改数据**
|
||||
|
||||
创建 `test_person_edit.json`:
|
||||
```json
|
||||
{
|
||||
"intermediaryId": 1,
|
||||
"name": "测试个人中介-已修改",
|
||||
"certificateNo": "110101199001011234",
|
||||
"indivType": "中介",
|
||||
"indivGender": "M",
|
||||
"indivPhone": "13900139000",
|
||||
"indivCompany": "新公司",
|
||||
"remark": "已修改"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 调用修改接口**
|
||||
|
||||
```bash
|
||||
curl -X PUT http://localhost:8080/ccdi/intermediary/person \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_person_edit.json
|
||||
```
|
||||
|
||||
预期: `{ "code": 200, "msg": "操作成功" }`
|
||||
|
||||
**Step 3: 验证数据已更新**
|
||||
|
||||
```sql
|
||||
SELECT * FROM ccdi_biz_intermediary
|
||||
WHERE biz_id = 1;
|
||||
```
|
||||
|
||||
预期:
|
||||
- name = '测试个人中介-已修改'
|
||||
- mobile = '13900139000'
|
||||
- company = '新公司'
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 测试个人中介删除功能
|
||||
|
||||
**Files:**
|
||||
- Test API: `DELETE /ccdi/intermediary/{ids}`
|
||||
|
||||
**Step 1: 调用删除接口**
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8080/ccdi/intermediary/1" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
预期: `{ "code": 200, "msg": "操作成功" }`
|
||||
|
||||
**Step 2: 验证数据已删除**
|
||||
|
||||
```sql
|
||||
SELECT * FROM ccdi_biz_intermediary
|
||||
WHERE biz_id = 1;
|
||||
```
|
||||
|
||||
预期: 0条记录
|
||||
|
||||
---
|
||||
|
||||
## 功能测试 - 机构中介
|
||||
|
||||
### Task 7: 测试机构中介新增功能
|
||||
|
||||
**Files:**
|
||||
- Test API: `POST /ccdi/intermediary/entity`
|
||||
- Backend: `CcdiIntermediaryBlacklistServiceImpl.insertEntityIntermediary()`
|
||||
|
||||
**Step 1: 准备测试数据**
|
||||
|
||||
创建 `test_entity_add.json`:
|
||||
```json
|
||||
{
|
||||
"name": "测试机构中介有限公司",
|
||||
"corpCreditCode": "91110000123456789X",
|
||||
"corpType": "有限责任公司",
|
||||
"corpNature": "民营企业",
|
||||
"corpIndustryCategory": "制造业",
|
||||
"corpIndustry": "通用设备制造业",
|
||||
"corpEstablishDate": "2020-01-01T00:00:00",
|
||||
"corpAddress": "北京市海淀区测试大街456号",
|
||||
"corpLegalRep": "张三",
|
||||
"corpLegalCertType": "身份证",
|
||||
"corpLegalCertNo": "110101198001011234",
|
||||
"corpShareholder1": "股东A",
|
||||
"corpShareholder2": "股东B",
|
||||
"status": "0",
|
||||
"remark": "机构中介测试数据"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 调用新增接口**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/entity \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_entity_add.json
|
||||
```
|
||||
|
||||
预期: `{ "code": 200, "msg": "操作成功" }`
|
||||
|
||||
**Step 3: 验证数据插入到正确的表**
|
||||
|
||||
```sql
|
||||
SELECT * FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code = '91110000123456789X';
|
||||
```
|
||||
|
||||
预期:
|
||||
- 找到1条记录
|
||||
- enterprise_name = '测试机构中介有限公司'
|
||||
- **risk_level = '1' (高风险)** ✅
|
||||
- **ent_source = 'INTERMEDIARY' (中介来源)** ✅
|
||||
- data_source = 'MANUAL'
|
||||
|
||||
**Step 4: 验证关键字段自动设置**
|
||||
|
||||
检查两个重要标识:
|
||||
```sql
|
||||
SELECT
|
||||
social_credit_code,
|
||||
enterprise_name,
|
||||
risk_level,
|
||||
ent_source,
|
||||
data_source
|
||||
FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code = '91110000123456789X';
|
||||
```
|
||||
|
||||
预期:
|
||||
- risk_level = '1' ✅
|
||||
- ent_source = 'INTERMEDIARY' ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 测试机构中介列表查询
|
||||
|
||||
**Files:**
|
||||
- Test API: `GET /ccdi/intermediary/list`
|
||||
|
||||
**Step 1: 查询机构中介**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list?intermediaryType=2&name=测试机构" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
预期响应:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"rows": [
|
||||
{
|
||||
"intermediaryId": 0,
|
||||
"name": "测试机构中介有限公司",
|
||||
"certificateNo": "91110000123456789X",
|
||||
"intermediaryType": "2",
|
||||
"intermediaryTypeName": "机构",
|
||||
"status": "0",
|
||||
"statusName": "正常"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 验证ent_source过滤**
|
||||
|
||||
查询应该只返回 ent_source='INTERMEDIARY' 的记录
|
||||
|
||||
**Step 3: 混合查询(个人+机构)**
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
预期: 返回个人和机构中介的合并列表
|
||||
|
||||
---
|
||||
|
||||
### Task 9: 测试机构中介详情查询
|
||||
|
||||
**Files:**
|
||||
- Test API: `GET /ccdi/intermediary/{id}`
|
||||
|
||||
**Step 1: 获取机构中介详情**
|
||||
|
||||
注意: 机构中介的ID需要特殊处理(社会信用代码)
|
||||
|
||||
**Step 2: 验证机构字段映射**
|
||||
|
||||
检查字段映射:
|
||||
- corpCreditCode → social_credit_code ✅
|
||||
- name → enterprise_name ✅
|
||||
- corpType → enterprise_type ✅
|
||||
- corpNature → enterprise_nature ✅
|
||||
- corpIndustryCategory → industry_class ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 10: 测试机构中介修改功能
|
||||
|
||||
**Files:**
|
||||
- Test API: `PUT /ccdi/intermediary/entity`
|
||||
|
||||
**Step 1: 准备修改数据**
|
||||
|
||||
创建 `test_entity_edit.json`:
|
||||
```json
|
||||
{
|
||||
"corpCreditCode": "91110000123456789X",
|
||||
"name": "测试机构中介有限公司-已修改",
|
||||
"corpType": "股份有限公司",
|
||||
"corpNature": "国有企业",
|
||||
"status": "0",
|
||||
"remark": "已修改"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 调用修改接口**
|
||||
|
||||
```bash
|
||||
curl -X PUT http://localhost:8080/ccdi/intermediary/entity \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_entity_edit.json
|
||||
```
|
||||
|
||||
预期: `{ "code": 200, "msg": "操作成功" }`
|
||||
|
||||
**Step 3: 验证高风险和中介来源标识不变**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
social_credit_code,
|
||||
enterprise_name,
|
||||
risk_level,
|
||||
ent_source
|
||||
FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code = '91110000123456789X';
|
||||
```
|
||||
|
||||
预期:
|
||||
- enterprise_name = '测试机构中介有限公司-已修改'
|
||||
- risk_level 仍为 '1' ✅ (保持不变)
|
||||
- ent_source 仍为 'INTERMEDIARY' ✅ (保持不变)
|
||||
|
||||
---
|
||||
|
||||
## 导入功能测试
|
||||
|
||||
### Task 11: 测试个人中介Excel导入
|
||||
|
||||
**Files:**
|
||||
- Test API: `POST /ccdi/intermediary/importPersonData`
|
||||
|
||||
**Step 1: 下载导入模板**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/importPersonTemplate \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
--output person_template.xlsx
|
||||
```
|
||||
|
||||
预期: 下载成功,文件包含所有个人字段
|
||||
|
||||
**Step 2: 准备测试Excel文件**
|
||||
|
||||
手动创建Excel文件或使用EasyExcel生成测试数据,包含:
|
||||
- 姓名: "导入测试个人"
|
||||
- 证件号: "110101199002022345"
|
||||
- 人员类型: "中介"
|
||||
- 性别: "M"
|
||||
- 手机号: "13800138001"
|
||||
- 微信号: "import_wx001"
|
||||
|
||||
**Step 3: 执行导入**
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/ccdi/intermediary/importPersonData?updateSupport=false" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@person_test_data.xlsx"
|
||||
```
|
||||
|
||||
预期:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "恭喜您,数据已全部导入成功!共 1 条"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 验证导入数据**
|
||||
|
||||
```sql
|
||||
SELECT * FROM ccdi_biz_intermediary
|
||||
WHERE person_id = '110101199002022345';
|
||||
```
|
||||
|
||||
预期:
|
||||
- 找到1条记录
|
||||
- date_source = 'IMPORT' ✅
|
||||
- name = '导入测试个人'
|
||||
|
||||
---
|
||||
|
||||
### Task 12: 测试机构中介Excel导入
|
||||
|
||||
**Files:**
|
||||
- Test API: `POST /ccdi/intermediary/importEntityData`
|
||||
|
||||
**Step 1: 下载导入模板**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/importEntityTemplate \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
--output entity_template.xlsx
|
||||
```
|
||||
|
||||
预期: 下载成功,文件包含所有机构字段
|
||||
|
||||
**Step 2: 准备测试Excel文件**
|
||||
|
||||
创建Excel文件,包含:
|
||||
- 机构名称: "导入测试机构有限公司"
|
||||
- 统一社会信用代码: "91110000987654321A"
|
||||
- 主体类型: "有限责任公司"
|
||||
- 企业性质: "民营企业"
|
||||
- 法定代表人: "李四"
|
||||
|
||||
**Step 3: 执行导入**
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/ccdi/intermediary/importEntityData?updateSupport=false" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@entity_test_data.xlsx"
|
||||
```
|
||||
|
||||
预期:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "恭喜您,数据已全部导入成功!共 1 条"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: 验证导入数据和自动设置标识**
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
social_credit_code,
|
||||
enterprise_name,
|
||||
risk_level,
|
||||
ent_source,
|
||||
data_source
|
||||
FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code = '91110000987654321A';
|
||||
```
|
||||
|
||||
预期:
|
||||
- enterprise_name = '导入测试机构有限公司'
|
||||
- **risk_level = '1' (高风险)** ✅
|
||||
- **ent_source = 'INTERMEDIARY' (中介来源)** ✅
|
||||
- data_source = 'IMPORT' ✅
|
||||
|
||||
---
|
||||
|
||||
## 导出功能测试
|
||||
|
||||
### Task 13: 测试中介数据导出
|
||||
|
||||
**Files:**
|
||||
- Test API: `POST /ccdi/intermediary/export`
|
||||
|
||||
**Step 1: 导出所有数据**
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/ccdi/intermediary/export" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' \
|
||||
--output intermediary_export.xlsx
|
||||
```
|
||||
|
||||
预期: 下载成功,Excel文件包含个人和机构数据
|
||||
|
||||
**Step 2: 验证导出数据完整性**
|
||||
|
||||
打开Excel文件,验证:
|
||||
- 包含个人中介字段(indivType, indivGender等)
|
||||
- 包含机构中介字段(corpType, corpNature等)
|
||||
- 数据正确映射
|
||||
|
||||
**Step 3: 测试条件导出**
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/ccdi/intermediary/export" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"intermediaryType":"1"}' \
|
||||
--output person_export.xlsx
|
||||
```
|
||||
|
||||
预期: 只导出个人中介数据
|
||||
|
||||
---
|
||||
|
||||
## 边界条件测试
|
||||
|
||||
### Task 14: 测试唯一性约束
|
||||
|
||||
**Step 1: 个人中介证件号重复插入**
|
||||
|
||||
尝试插入相同person_id的记录:
|
||||
```bash
|
||||
# 使用Task 2的数据再次执行
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/person \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_person_add.json
|
||||
```
|
||||
|
||||
预期: 根据实际业务逻辑,可能报唯一性约束错误或允许插入
|
||||
|
||||
**Step 2: 机构中介社会信用代码重复插入**
|
||||
|
||||
```bash
|
||||
# 使用Task 7的数据再次执行
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/entity \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_entity_add.json
|
||||
```
|
||||
|
||||
预期: 报主键冲突错误(社会信用代码是主键)
|
||||
|
||||
---
|
||||
|
||||
### Task 15: 测试必填字段验证
|
||||
|
||||
**Step 1: 缺少姓名的个人中介**
|
||||
|
||||
创建 `test_person_no_name.json`:
|
||||
```json
|
||||
{
|
||||
"certificateNo": "110101199003033456",
|
||||
"status": "0"
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/person \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_person_no_name.json
|
||||
```
|
||||
|
||||
预期: 返回验证错误,提示"姓名不能为空"
|
||||
|
||||
**Step 2: 缺少统一社会信用代码的机构中介**
|
||||
|
||||
创建 `test_entity_no_code.json`:
|
||||
```json
|
||||
{
|
||||
"name": "测试机构",
|
||||
"status": "0"
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ccdi/intermediary/entity \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @test_entity_no_code.json
|
||||
```
|
||||
|
||||
预期: 返回验证错误,提示"统一社会信用代码不能为空"
|
||||
|
||||
---
|
||||
|
||||
## 性能测试
|
||||
|
||||
### Task 16: 批量数据导入性能测试
|
||||
|
||||
**Step 1: 准备批量测试数据**
|
||||
|
||||
创建包含100条个人中介的Excel文件
|
||||
|
||||
**Step 2: 执行批量导入**
|
||||
|
||||
```bash
|
||||
time curl -X POST "http://localhost:8080/ccdi/intermediary/importPersonData?updateSupport=false" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-F "file=@person_batch_100.xlsx"
|
||||
```
|
||||
|
||||
预期:
|
||||
- 导入成功
|
||||
- 耗时 < 10秒
|
||||
|
||||
**Step 3: 验证数据一致性**
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM ccdi_biz_intermediary
|
||||
WHERE date_source = 'IMPORT';
|
||||
```
|
||||
|
||||
预期: 导入的记录数与Excel文件一致
|
||||
|
||||
---
|
||||
|
||||
## 清理测试数据
|
||||
|
||||
### Task 17: 清理测试数据
|
||||
|
||||
**Step 1: 删除测试个人中介数据**
|
||||
|
||||
```sql
|
||||
DELETE FROM ccdi_biz_intermediary
|
||||
WHERE person_id IN (
|
||||
'110101199001011234',
|
||||
'110101199002022345'
|
||||
);
|
||||
```
|
||||
|
||||
**Step 2: 删除测试机构中介数据**
|
||||
|
||||
```sql
|
||||
DELETE FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code IN (
|
||||
'91110000123456789X',
|
||||
'91110000987654321A'
|
||||
);
|
||||
```
|
||||
|
||||
**Step 3: 验证清理完成**
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM ccdi_biz_intermediary
|
||||
WHERE person_id LIKE '110101199%';
|
||||
|
||||
SELECT COUNT(*) FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code LIKE '91110000%';
|
||||
```
|
||||
|
||||
预期: 0条测试记录
|
||||
|
||||
---
|
||||
|
||||
## 测试报告生成
|
||||
|
||||
### Task 18: 生成测试报告
|
||||
|
||||
**Step 1: 汇总测试结果**
|
||||
|
||||
创建测试报告文件 `test_report.md`:
|
||||
|
||||
```markdown
|
||||
# 中介黑名单入库逻辑变更测试报告
|
||||
|
||||
## 测试环境
|
||||
- 数据库: MySQL 8.2.0
|
||||
- 服务端口: 8080
|
||||
- 测试时间: 2026-02-04
|
||||
|
||||
## 功能测试结果
|
||||
|
||||
### 个人中介
|
||||
- ✅ 新增功能 - 数据正确插入 ccdi_biz_intermediary
|
||||
- ✅ 列表查询 - 正确返回个人中介数据
|
||||
- ✅ 详情查询 - 所有字段正确映射
|
||||
- ✅ 修改功能 - 数据正确更新
|
||||
- ✅ 删除功能 - 数据正确删除
|
||||
- ✅ Excel导入 - 批量导入成功,data_source='IMPORT'
|
||||
- ✅ Excel导出 - 数据完整导出
|
||||
|
||||
### 机构中介
|
||||
- ✅ 新增功能 - 数据正确插入 ccdi_enterprise_base_info
|
||||
- ✅ 自动设置标识 - risk_level='1', ent_source='INTERMEDIARY'
|
||||
- ✅ 列表查询 - 正确返回机构中介数据
|
||||
- ✅ 详情查询 - 所有字段正确映射
|
||||
- ✅ 修改功能 - 数据正确更新,标识保持不变
|
||||
- ✅ Excel导入 - 批量导入成功,自动设置高风险和中介来源
|
||||
- ✅ Excel导出 - 数据完整导出
|
||||
|
||||
### 边界条件
|
||||
- ✅ 唯一性约束 - 社会信用代码主键冲突
|
||||
- ✅ 必填字段验证 - 姓名和证件号验证生效
|
||||
|
||||
### 性能测试
|
||||
- ✅ 100条数据导入 - 耗时 < 10秒
|
||||
|
||||
## 数据映射验证
|
||||
|
||||
### 个人中介字段映射
|
||||
| 原字段 | 新字段 | 状态 |
|
||||
|--------|--------|------|
|
||||
| intermediary_id | biz_id | ✅ |
|
||||
| certificate_no | person_id | ✅ |
|
||||
| indiv_type | person_type | ✅ |
|
||||
| indiv_gender | gender | ✅ |
|
||||
| indiv_phone | mobile | ✅ |
|
||||
| indiv_wechat | wechat_no | ✅ |
|
||||
| indiv_address | contact_address | ✅ |
|
||||
|
||||
### 机构中介字段映射
|
||||
| 原字段 | 新字段 | 状态 |
|
||||
|--------|--------|------|
|
||||
| corp_credit_code | social_credit_code | ✅ |
|
||||
| name | enterprise_name | ✅ |
|
||||
| corp_type | enterprise_type | ✅ |
|
||||
| corp_nature | enterprise_nature | ✅ |
|
||||
| - | risk_level='1' | ✅ 自动设置 |
|
||||
| - | ent_source='INTERMEDIARY' | ✅ 自动设置 |
|
||||
|
||||
## 结论
|
||||
✅ 所有测试通过,入库逻辑变更成功!
|
||||
```
|
||||
|
||||
**Step 2: 提交测试报告**
|
||||
|
||||
```bash
|
||||
git add test_report.md
|
||||
git commit -m "test: 添加中介黑名单变更测试报告"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **机构中介ID处理**: 机构中介的主键是字符串类型(social_credit_code),查询详情时需要特殊处理
|
||||
|
||||
2. **自动设置标识**: 机构中介新增/导入时自动设置 `risk_level='1'` 和 `ent_source='INTERMEDIARY'`,修改时不应改变这两个值
|
||||
|
||||
3. **查询合并**: 列表查询需要从两个表获取数据并合并返回前端
|
||||
|
||||
4. **数据来源标识**:
|
||||
- 手动新增: date_source/data_source = 'MANUAL'
|
||||
- Excel导入: date_source/data_source = 'IMPORT'
|
||||
|
||||
5. **分页查询**: 当前实现是先查询所有数据再手动分页,大数据量时可能需要优化
|
||||
|
||||
6. **删除操作**: 当前只支持个人中介的数字ID删除,机构中介删除需要扩展支持
|
||||
46
doc/sql/menu_info_maintain.sql
Normal file
46
doc/sql/menu_info_maintain.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
-- =====================================================
|
||||
-- 菜单SQL:信息维护模块
|
||||
-- 创建时间: 2025-02-04
|
||||
-- 说明: 包含"信息维护"一级菜单及其两个二级菜单
|
||||
-- =====================================================
|
||||
|
||||
-- 一级菜单:信息维护
|
||||
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES(2000, '信息维护', 0, 5, 'maintain', NULL, NULL, NULL, 1, 0, 'M', '0', '0', NULL, 'el-icon-collection', 'admin', NOW(), '信息维护目录');
|
||||
|
||||
-- 二级菜单:中介黑名单管理
|
||||
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES(2001, '中介黑名单管理', 2000, 1, 'intermediary', 'ccdiIntermediary/index', NULL, NULL, 1, 0, 'C', '0', '0', 'ccdi:intermediary:list', '#', 'admin', NOW(), '中介黑名单管理菜单');
|
||||
|
||||
-- 二级菜单:员工信息维护
|
||||
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES(2002, '员工信息维护', 2000, 2, 'employee', 'ccdiEmployee/index', NULL, NULL, 1, 0, 'C', '0', '0', 'ccdi:employee:list', '#', 'admin', NOW(), '员工信息维护菜单');
|
||||
|
||||
-- =====================================================
|
||||
-- 中介黑名单管理 - 按钮权限
|
||||
-- =====================================================
|
||||
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES
|
||||
(2010, '中介黑名单查询', 2001, 1, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:intermediary:query', '#', 'admin', NOW(), ''),
|
||||
(2011, '中介黑名单新增', 2001, 2, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:intermediary:add', '#', 'admin', NOW(), ''),
|
||||
(2012, '中介黑名单修改', 2001, 3, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:intermediary:edit', '#', 'admin', NOW(), ''),
|
||||
(2013, '中介黑名单删除', 2001, 4, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:intermediary:remove', '#', 'admin', NOW(), ''),
|
||||
(2014, '中介黑名单导出', 2001, 5, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:intermediary:export', '#', 'admin', NOW(), ''),
|
||||
(2015, '中介黑名单导入', 2001, 6, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:intermediary:import', '#', 'admin', NOW(), '');
|
||||
|
||||
-- =====================================================
|
||||
-- 员工信息维护 - 按钮权限
|
||||
-- =====================================================
|
||||
INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES
|
||||
(2020, '员工信息查询', 2002, 1, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:employee:query', '#', 'admin', NOW(), ''),
|
||||
(2021, '员工信息新增', 2002, 2, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:employee:add', '#', 'admin', NOW(), ''),
|
||||
(2022, '员工信息修改', 2002, 3, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:employee:edit', '#', 'admin', NOW(), ''),
|
||||
(2023, '员工信息删除', 2002, 4, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:employee:remove', '#', 'admin', NOW(), ''),
|
||||
(2024, '员工信息导出', 2002, 5, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:employee:export', '#', 'admin', NOW(), ''),
|
||||
(2025, '员工信息导入', 2002, 6, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'ccdi:employee:import', '#', 'admin', NOW(), '');
|
||||
|
||||
-- =====================================================
|
||||
-- 回滚SQL(如需删除这些菜单,执行以下语句)
|
||||
-- =====================================================
|
||||
-- DELETE FROM sys_menu WHERE menu_id BETWEEN 2000 AND 2025;
|
||||
269
doc/中介黑名单列表查询功能说明.md
Normal file
269
doc/中介黑名单列表查询功能说明.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# 中介黑名单列表查询功能说明
|
||||
|
||||
## 接口说明
|
||||
|
||||
### 1. 列表查询接口(不分页)
|
||||
|
||||
**接口地址:** `GET /ccdi/intermediary/list`
|
||||
|
||||
**请求参数:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|
||||
|--------|------|------|------|------|
|
||||
| name | String | 否 | 姓名/机构名称(模糊查询) | 张三 |
|
||||
| certificateNo | String | 否 | 证件号/社会信用代码(模糊查询) | 110101... |
|
||||
| intermediaryType | String | 否 | 中介类型(1=个人,2=机构) | 1 |
|
||||
| status | String | 否 | 状态(0=正常,1=停用) | 0 |
|
||||
| pageNum | Int | 否 | 页码 | 1 |
|
||||
| pageSize | Int | 否 | 每页条数 | 10 |
|
||||
|
||||
**查询场景示例:**
|
||||
|
||||
#### 场景1: 查询所有中介(个人+机构)
|
||||
```http
|
||||
GET /ccdi/intermediary/list
|
||||
```
|
||||
|
||||
#### 场景2: 只查询个人中介
|
||||
```http
|
||||
GET /ccdi/intermediary/list?intermediaryType=1
|
||||
```
|
||||
|
||||
#### 场景3: 只查询机构中介
|
||||
```http
|
||||
GET /ccdi/intermediary/list?intermediaryType=2
|
||||
```
|
||||
|
||||
#### 场景4: 按姓名查询个人中介
|
||||
```http
|
||||
GET /ccdi/intermediary/list?intermediaryType=1&name=张三
|
||||
```
|
||||
|
||||
#### 场景5: 按证件号查询机构中介
|
||||
```http
|
||||
GET /ccdi/intermediary/list?intermediaryType=2&certificateNo=91110000...
|
||||
```
|
||||
|
||||
#### 场景6: 分页查询所有中介
|
||||
```http
|
||||
GET /ccdi/intermediary/list?pageNum=1&pageSize=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SQL 实现逻辑
|
||||
|
||||
### 分页查询优化
|
||||
|
||||
使用 `UNION ALL` 在数据库层面完成联合查询和分页,提升性能:
|
||||
|
||||
```sql
|
||||
SELECT * FROM (
|
||||
-- 个人中介查询
|
||||
SELECT
|
||||
biz_id AS intermediary_id,
|
||||
name,
|
||||
person_id AS certificate_no,
|
||||
'1' AS intermediary_type,
|
||||
'0' AS status,
|
||||
date_source AS data_source,
|
||||
create_time,
|
||||
update_time
|
||||
FROM ccdi_biz_intermediary
|
||||
WHERE 1=1
|
||||
<!-- 类型过滤 -->
|
||||
<if test="intermediaryType != null">
|
||||
AND '1' = #{intermediaryType}
|
||||
</if>
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 机构中介查询
|
||||
SELECT
|
||||
0 AS intermediary_id,
|
||||
enterprise_name AS name,
|
||||
social_credit_code AS certificate_no,
|
||||
'2' AS intermediary_type,
|
||||
status,
|
||||
data_source,
|
||||
create_time,
|
||||
update_time
|
||||
FROM ccdi_enterprise_base_info
|
||||
WHERE ent_source = 'INTERMEDIARY'
|
||||
<!-- 类型过滤 -->
|
||||
<if test="intermediaryType != null">
|
||||
AND '2' = #{intermediaryType}
|
||||
</if>
|
||||
) AS combined_data
|
||||
ORDER BY create_time DESC
|
||||
LIMIT 10 OFFSET 0 -- MyBatis Plus 自动添加
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 类型过滤逻辑
|
||||
|
||||
### 在 SQL 子查询层面过滤
|
||||
|
||||
| 查询条件 | 个人中介子查询 | 机构中介子查询 |
|
||||
|----------|--------------|--------------|
|
||||
| `intermediaryType=null` | 执行 | 执行 |
|
||||
| `intermediaryType=1` | 执行 (`'1'='1'` 为真) | 不返回数据 (`'2'='1'` 为假) |
|
||||
| `intermediaryType=2` | 不返回数据 (`'1'='2'` 为假) | 执行 (`'2'='2'` 为真) |
|
||||
|
||||
**优势:**
|
||||
- ✅ 避免查询不需要的数据
|
||||
- ✅ 减少数据库 I/O
|
||||
- ✅ 提升 UNION 性能
|
||||
- ✅ 分页准确
|
||||
|
||||
---
|
||||
|
||||
## 列表查询(非分页)
|
||||
|
||||
Service 层实现:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public List<CcdiIntermediaryBlacklistVO> selectIntermediaryList(
|
||||
CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
|
||||
List<CcdiIntermediaryBlacklistVO> resultList = new ArrayList<>();
|
||||
|
||||
// 查询个人中介
|
||||
if (StringUtils.isEmpty(queryDTO.getIntermediaryType()) || "1".equals(queryDTO.getIntermediaryType())) {
|
||||
LambdaQueryWrapper<CcdiBizIntermediary> personWrapper = buildPersonQueryWrapper(queryDTO);
|
||||
List<CcdiBizIntermediary> personList = bizIntermediaryMapper.selectList(personWrapper);
|
||||
personList.forEach(person -> resultList.add(convertPersonToVO(person)));
|
||||
}
|
||||
|
||||
// 查询机构中介
|
||||
if (StringUtils.isEmpty(queryDTO.getIntermediaryType()) || "2".equals(queryDTO.getIntermediaryType())) {
|
||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> entityWrapper = buildEntityQueryWrapper(queryDTO);
|
||||
List<CcdiEnterpriseBaseInfo> entityList = enterpriseBaseInfoMapper.selectList(entityWrapper);
|
||||
entityList.forEach(entity -> resultList.add(convertEntityToVO(entity)));
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
```
|
||||
|
||||
**逻辑说明:**
|
||||
- 当 `intermediaryType` 为空时,查询两种类型
|
||||
- 当 `intermediaryType = "1"` 时,只查询个人中介
|
||||
- 当 `intermediaryType = "2"` 时,只查询机构中介
|
||||
|
||||
---
|
||||
|
||||
## 返回数据格式
|
||||
|
||||
### 个人中介
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"rows": [
|
||||
{
|
||||
"intermediaryId": 1,
|
||||
"name": "张三",
|
||||
"certificateNo": "110101199001011234",
|
||||
"intermediaryType": "1",
|
||||
"intermediaryTypeName": "个人",
|
||||
"status": "0",
|
||||
"statusName": "正常",
|
||||
"dataSource": "MANUAL",
|
||||
"dataSourceName": "手动录入",
|
||||
"createTime": "2026-02-04 10:00:00",
|
||||
"updateTime": "2026-02-04 10:00:00"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 机构中介
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"rows": [
|
||||
{
|
||||
"intermediaryId": 0,
|
||||
"name": "测试机构有限公司",
|
||||
"certificateNo": "91110000123456789X",
|
||||
"intermediaryType": "2",
|
||||
"intermediaryTypeName": "机构",
|
||||
"status": "0",
|
||||
"statusName": "正常",
|
||||
"dataSource": "MANUAL",
|
||||
"dataSourceName": "手动录入",
|
||||
"createTime": "2026-02-04 10:00:00",
|
||||
"updateTime": "2026-02-04 10:00:00"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 场景 | 旧实现 | 新实现 |
|
||||
|------|--------|--------|
|
||||
| 查询所有 | 查询2张表 | UNION ALL 查询2张表 |
|
||||
| 只查个人 | 查询2张表,应用层过滤 | 只查个人表 |
|
||||
| 只查机构 | 查询2张表,应用层过滤 | 只查机构表 |
|
||||
| 分页 | 查询全部,手动截取 | LIMIT/OFFSET 数据库分页 |
|
||||
|
||||
**性能提升:**
|
||||
- 只查个人/机构时,减少50%的数据库查询
|
||||
- 大数据量分页时,避免内存溢出
|
||||
- 网络传输量减少 90%+
|
||||
|
||||
---
|
||||
|
||||
## 测试用例
|
||||
|
||||
### 测试1: 查询所有中介
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**预期:** 返回个人和机构两种类型的数据
|
||||
|
||||
### 测试2: 只查询个人中介
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list?intermediaryType=1" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**预期:** 只返回个人中介数据
|
||||
|
||||
### 测试3: 只查询机构中介
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list?intermediaryType=2" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**预期:** 只返回机构中介数据
|
||||
|
||||
### 测试4: 分页查询个人中介
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/ccdi/intermediary/list?intermediaryType=1&pageNum=1&pageSize=10" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**预期:**
|
||||
- 返回第1页,最多10条个人中介数据
|
||||
- total 为个人中介的总数
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **类型过滤在数据库层面完成**,不是在应用层过滤
|
||||
2. **分页使用 MyBatis Plus 的自动分页**,SQL 自动添加 LIMIT/OFFSET
|
||||
3. **机构中介的 ID 为 0**,因为主键是字符串类型(社会信用代码)
|
||||
4. **查询时自动过滤 `ent_source='INTERMEDIARY'`**,确保只返回中介来源的企业
|
||||
BIN
doc/原型图-上传数据页面.png
Normal file
BIN
doc/原型图-上传数据页面.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 260 KiB |
821
doc/原型图开发设计文档.md
Normal file
821
doc/原型图开发设计文档.md
Normal file
@@ -0,0 +1,821 @@
|
||||
# 纪检初核系统 - 原型图开发设计文档
|
||||
|
||||
## 一、项目概述
|
||||
|
||||
### 1.1 项目背景
|
||||
本项目是一个**纪检初核系统**,用于对银行信贷部门员工进行初步核查,通过分析银行流水、征信报告、员工关系等数据,识别潜在的违规行为和风险。
|
||||
|
||||
### 1.2 项目目标
|
||||
- 支持多维度数据导入(流水、征信、员工关系)
|
||||
- 提供可配置的风险监测模型
|
||||
- 自动识别高风险人员并生成初核提示
|
||||
- 提供专项排查工作台进行深入分析
|
||||
- 支持关系图谱和资金流向分析
|
||||
|
||||
### 1.3 技术栈
|
||||
- **后端**: Spring Boot 3.5.8 + MyBatis 3.0.5 + MySQL 8.2.0
|
||||
- **前端**: Vue 2.6.12 + Element UI 2.15.14
|
||||
- **数据库**: MySQL(表前缀:ccdi_)
|
||||
|
||||
---
|
||||
|
||||
## 二、页面结构与功能分析
|
||||
|
||||
### 2.1 页面导航结构
|
||||
|
||||
```
|
||||
纪检初核系统
|
||||
├── 项目管理
|
||||
│ ├── 项目详情
|
||||
│ ├── 上传数据
|
||||
│ ├── 参数配置
|
||||
│ └── 初核提示
|
||||
├── 初核结果
|
||||
│ ├── 专项排查工作台(高风险)
|
||||
│ ├── 专项排查工作台(中风险)
|
||||
│ └── 专项排查
|
||||
└── 流水明细查询
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 页面1:上传数据
|
||||
|
||||
#### 功能描述
|
||||
支持在一个项目中上传多个主体/账户数据,进行汇总/独立分析。
|
||||
|
||||
#### 页面元素
|
||||
| 元素类型 | 元素名称/内容 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 项目信息 | 项目状态 | 显示项目当前状态(如:已完成) |
|
||||
| | 最后更新时间 | 显示项目最后更新时间 |
|
||||
| 上传模块1 | 流水导入 | 支持Excel、PDF格式文件批量上传 |
|
||||
| | | 占位符:拖拽文件到此处或点击上传 |
|
||||
| | | 支持格式:xlsx, xls, pdf |
|
||||
| 上传模块2 | 已上传流水查询 | 支持HTML格式 |
|
||||
| | | 占位符:拖拽文件到此处或点击上传 |
|
||||
| 上传模块3 | 征信导入 | 支持HTML格式征信报告解析 |
|
||||
| 上传模块4 | 员工家庭关系导入 | Excel模板上传员工家庭关系信息 |
|
||||
| | | 支持格式:xlsx, xls |
|
||||
| 名单库选择 | 高风险人员名单 | 复选框,显示人数(如68人) |
|
||||
| | 历史可疑人员名单 | 复选框,显示人数(如45人) |
|
||||
| | 监管关注名单 | 复选框,显示人数(如32人) |
|
||||
| 数据质量检查 | 数据完整性 | 进度条,显示百分比(如98.5%) |
|
||||
| | 格式一致性 | 进度条,显示百分比(如95.2%) |
|
||||
| | 余额连续性 | 进度条,显示百分比(如92.8%) |
|
||||
| | 检查结果 | 显示发现的问题数量 |
|
||||
| 操作按钮 | 拉取本行信息 | 触发拉取银行内部信息 |
|
||||
| | 生成报告 | 生成初核报告 |
|
||||
|
||||
#### 数据模型
|
||||
```sql
|
||||
-- 项目表
|
||||
CREATE TABLE ccdi_project (
|
||||
project_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_name VARCHAR(200) NOT NULL COMMENT '项目名称',
|
||||
project_status VARCHAR(50) COMMENT '项目状态',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(100),
|
||||
update_by VARCHAR(100),
|
||||
remark VARCHAR(500)
|
||||
) COMMENT '项目表';
|
||||
|
||||
-- 数据上传记录表
|
||||
CREATE TABLE ccdi_data_upload (
|
||||
upload_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
upload_type VARCHAR(50) COMMENT '上传类型:流水/征信/家庭关系',
|
||||
file_name VARCHAR(500) COMMENT '文件名',
|
||||
file_path VARCHAR(1000) COMMENT '文件路径',
|
||||
upload_status VARCHAR(50) COMMENT '上传状态',
|
||||
upload_time DATETIME COMMENT '上传时间',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(100)
|
||||
) COMMENT '数据上传记录表';
|
||||
|
||||
-- 名单库选择记录表
|
||||
CREATE TABLE ccdi_blacklist_selection (
|
||||
selection_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
blacklist_type VARCHAR(50) COMMENT '名单类型:高风险/历史可疑/监管关注',
|
||||
blacklist_id BIGINT COMMENT '名单ID',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '名单库选择记录表';
|
||||
|
||||
-- 数据质量检查表
|
||||
CREATE TABLE ccdi_data_quality (
|
||||
quality_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
check_item VARCHAR(100) COMMENT '检查项:完整性/一致性/连续性',
|
||||
check_result DECIMAL(5,2) COMMENT '检查结果百分比',
|
||||
issue_count INT COMMENT '问题数量',
|
||||
issue_detail TEXT COMMENT '问题详情',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '数据质量检查表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 页面2:参数配置
|
||||
|
||||
#### 功能描述
|
||||
配置风险监测模型的阈值参数。
|
||||
|
||||
#### 页面元素
|
||||
| 元素类型 | 元素名称/内容 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 模型名称 | 大额交易模型 | 下拉选择 |
|
||||
| 阈值参数配置表格 | | |
|
||||
| 表格列1 | 监测项 | 如:单笔交易额 |
|
||||
| 表格列2 | 描述 | 如:单笔超过该金额视为大额交易 |
|
||||
| 表格列3 | 阈值设置 | 输入框,如:50000 |
|
||||
| 表格列4 | 单位 | 如:元 |
|
||||
| 操作按钮 | 保存配置 | 保存当前配置 |
|
||||
| | 恢复默认 | 恢复默认值 |
|
||||
| | 一键导出配置 | 导出配置文件 |
|
||||
|
||||
#### 监测项配置
|
||||
1. **单笔交易额**: 50000元
|
||||
2. **累计交易额**: 5000000元
|
||||
3. **大额存现**: 200000元
|
||||
4. **短时多次存现**: 100000元/4小时
|
||||
5. **频繁转账**: 10次/日
|
||||
6. **转账频率**: 1000000元/日
|
||||
|
||||
#### 数据模型
|
||||
```sql
|
||||
-- 风险模型表
|
||||
CREATE TABLE ccdi_risk_model (
|
||||
model_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
model_name VARCHAR(200) NOT NULL COMMENT '模型名称',
|
||||
model_code VARCHAR(100) COMMENT '模型编码',
|
||||
status VARCHAR(50) DEFAULT 'active' COMMENT '状态',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(100),
|
||||
update_by VARCHAR(100)
|
||||
) COMMENT '风险模型表';
|
||||
|
||||
-- 模型参数配置表
|
||||
CREATE TABLE ccdi_model_parameter (
|
||||
parameter_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
model_id BIGINT COMMENT '模型ID',
|
||||
parameter_name VARCHAR(200) COMMENT '参数名称',
|
||||
parameter_code VARCHAR(100) COMMENT '参数编码',
|
||||
parameter_desc VARCHAR(500) COMMENT '参数描述',
|
||||
threshold_value DECIMAL(20,2) COMMENT '阈值',
|
||||
unit VARCHAR(50) COMMENT '单位',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) COMMENT '模型参数配置表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 页面3:初核提示
|
||||
|
||||
#### 功能描述
|
||||
展示初核结果的总体概况,包括人员风险分布、模型触发情况、可疑交易明细等。
|
||||
|
||||
#### 页面元素
|
||||
| 元素类型 | 元素名称/内容 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 统计卡片 | 总人数 | 显示总人数(如500) |
|
||||
| | 无预警人数 | 显示无预警人数(如432) |
|
||||
| | 低风险 | 显示低风险人数(如38) |
|
||||
| | 中风险 | 显示中风险人数(如20) |
|
||||
| | 高风险 | 显示高风险人数(如10) |
|
||||
| 模型触发情况表格 | 模型名称 | 如:大额交易监测 |
|
||||
| | 触发数 | 触发次数 |
|
||||
| | 触发人员 | 触发人员列表 |
|
||||
| | 操作 | 查看详情 |
|
||||
| 涉疑交易明细表 | 交易时间、可疑人员、关联人、关联员工、关系、摘要/交易类型、交易金额、操作 | |
|
||||
| 高风险人员清单 | 姓名、身份证号、所属部门、风险评分、触发模型数、核心异常点、操作 | 复选框支持批量操作 |
|
||||
| 中风险人员TOP10 | 姓名、身份证号、所属部门、触发模型、触发模型数、操作 | |
|
||||
| 异常账户清单 | 账户号、开户人姓名、开户银行、异常类型、异常发生时间、状态、操作 | |
|
||||
| 涉及违法人员清单表 | 姓名、身份证号、失信被执行人、刑事判决、行政处罚、公安涉案记录、限制高消费、违法信息更新时间、操作 | |
|
||||
| 筛选条件 | 姓名/工号搜索 | 输入框 |
|
||||
| | 部门筛选 | 下拉选择 |
|
||||
| | 风险等级筛选 | 下拉选择(全部/高风险/中风险/低风险) |
|
||||
| | 可疑人员类型筛选 | 下拉选择(全部/名单库命中/模型规则命中) |
|
||||
| | 模型筛选 | 复选框(大额交易/可疑财产/频繁转账等) |
|
||||
| | 模型筛选逻辑 | 单选:同时触发以上模型/触发任意模型 |
|
||||
| 批量操作 | 批量生成报告 | |
|
||||
| | 批量导出证据 | |
|
||||
| | 批量添加到关注列表 | |
|
||||
|
||||
#### 数据模型
|
||||
```sql
|
||||
-- 人员风险评分表
|
||||
CREATE TABLE ccdi_person_risk_score (
|
||||
score_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
person_id BIGINT COMMENT '人员ID',
|
||||
person_name VARCHAR(100) COMMENT '姓名',
|
||||
id_card VARCHAR(50) COMMENT '身份证号',
|
||||
department VARCHAR(200) COMMENT '所属部门',
|
||||
risk_level VARCHAR(50) COMMENT '风险等级:高/中/低',
|
||||
risk_score INT COMMENT '风险评分',
|
||||
trigger_model_count INT COMMENT '触发模型数量',
|
||||
core_issue VARCHAR(500) COMMENT '核心异常点',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '人员风险评分表';
|
||||
|
||||
-- 模型触发记录表
|
||||
CREATE TABLE ccdi_model_trigger_record (
|
||||
trigger_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
model_id BIGINT COMMENT '模型ID',
|
||||
model_name VARCHAR(200) COMMENT '模型名称',
|
||||
trigger_count INT COMMENT '触发次数',
|
||||
trigger_persons TEXT COMMENT '触发人员列表',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '模型触发记录表';
|
||||
|
||||
-- 涉疑交易明细表
|
||||
CREATE TABLE ccdi_suspicious_transaction (
|
||||
transaction_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
transaction_time DATETIME COMMENT '交易时间',
|
||||
suspicious_person VARCHAR(100) COMMENT '可疑人员',
|
||||
related_person VARCHAR(100) COMMENT '关联人',
|
||||
related_employee VARCHAR(100) COMMENT '关联员工',
|
||||
relationship VARCHAR(100) COMMENT '关系',
|
||||
transaction_type VARCHAR(200) COMMENT '摘要/交易类型',
|
||||
transaction_amount DECIMAL(20,2) COMMENT '交易金额',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '涉嫌交易明细表';
|
||||
|
||||
-- 异常账户表
|
||||
CREATE TABLE ccdi_abnormal_account (
|
||||
account_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
account_no VARCHAR(100) COMMENT '账户号',
|
||||
account_holder VARCHAR(100) COMMENT '开户人姓名',
|
||||
bank_name VARCHAR(200) COMMENT '开户银行',
|
||||
abnormal_type VARCHAR(100) COMMENT '异常类型',
|
||||
abnormal_time DATETIME COMMENT '异常发生时间',
|
||||
account_status VARCHAR(50) COMMENT '状态',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '异常账户表';
|
||||
|
||||
-- 违法人员信息表
|
||||
CREATE TABLE ccdi_illegal_person_info (
|
||||
info_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
person_id BIGINT COMMENT '人员ID',
|
||||
person_name VARCHAR(100) COMMENT '姓名',
|
||||
id_card VARCHAR(50) COMMENT '身份证号',
|
||||
is_dishonesty_executor VARCHAR(10) COMMENT '是否失信被执行人',
|
||||
is_criminal_penalty VARCHAR(10) COMMENT '是否有刑事判决',
|
||||
is_administrative_penalty VARCHAR(10) COMMENT '是否有行政处罚',
|
||||
is_police_case VARCHAR(10) COMMENT '是否有公安涉案记录',
|
||||
is_limit_consumption VARCHAR(10) COMMENT '是否限制高消费',
|
||||
update_time DATETIME COMMENT '违法信息更新时间',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '违法人员信息表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 页面4:专项排查工作台-高风险
|
||||
|
||||
#### 功能描述
|
||||
针对高风险人员的详细排查工作台。
|
||||
|
||||
#### 页面元素
|
||||
| 元素类型 | 元素名称/内容 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 排查对象信息 | 排查对象 | 如:李四 |
|
||||
| | 姓名、工号、部门、职级、入职时间、风险等级、所属项目 | |
|
||||
| 触发模型列表 | 触发模型(5个) | |
|
||||
| | 大额交易监测 | 3笔 > 50万 |
|
||||
| | 频繁转账监测 | 1小时25笔 |
|
||||
| | 关联交易排查 | 配偶账户频繁交易 |
|
||||
| | 异常销户监测 | 1个账户突然销户 |
|
||||
| | 疑似赌博交易 | 涉赌商户5笔 |
|
||||
| 初核评分 | 风险评分 | 如:85分(高风险阈值:60分) |
|
||||
| 异常详情-大额交易 | 交易时间、本方账号/主体、对方名称/账户、摘要/交易类型、交易金额、标记状态 | 标记状态下拉:标记正常/标记可疑/确认异常 |
|
||||
| 异常详情-频繁转账 | 时间段、总笔数、总金额、主要对手、模式特征、核查建议 | |
|
||||
| 异常详情-关联交易 | 关联人、关联账户、交易特征、异常点、需核实 | |
|
||||
| 排查工具箱 | 查看完整流水、查看征信报告、查看资产信息、关系图谱分析、资金流向分析、导出所有证据、添加到案例库 | |
|
||||
| 排查进度标签页 | 异常明细、资产分析、征信摘要、关系人图谱、资金流向 | |
|
||||
| 操作按钮 | 生成报告、生成排查报告、标记为案例、关注 | |
|
||||
|
||||
#### 数据模型
|
||||
```sql
|
||||
-- 排查对象表
|
||||
CREATE TABLE ccdi_investigation_object (
|
||||
object_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
person_id BIGINT COMMENT '人员ID',
|
||||
person_name VARCHAR(100) COMMENT '姓名',
|
||||
employee_no VARCHAR(100) COMMENT '工号',
|
||||
department VARCHAR(200) COMMENT '部门',
|
||||
position_level VARCHAR(100) COMMENT '职级',
|
||||
entry_date DATE COMMENT '入职时间',
|
||||
risk_level VARCHAR(50) COMMENT '风险等级',
|
||||
risk_score INT COMMENT '风险评分',
|
||||
investigation_status VARCHAR(50) COMMENT '排查状态',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) COMMENT '排查对象表';
|
||||
|
||||
-- 排查触发模型表
|
||||
CREATE TABLE ccdi_investigation_trigger_model (
|
||||
trigger_model_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
object_id BIGINT COMMENT '排查对象ID',
|
||||
model_id BIGINT COMMENT '模型ID',
|
||||
model_name VARCHAR(200) COMMENT '模型名称',
|
||||
trigger_desc VARCHAR(500) COMMENT '触发描述',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '排查触发模型表';
|
||||
|
||||
-- 异常交易明细表
|
||||
CREATE TABLE ccdi_abnormal_transaction_detail (
|
||||
detail_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
object_id BIGINT COMMENT '排查对象ID',
|
||||
transaction_time DATETIME COMMENT '交易时间',
|
||||
own_account VARCHAR(200) COMMENT '本方账号/主体',
|
||||
counterparty VARCHAR(200) COMMENT '对方名称/账户',
|
||||
transaction_type VARCHAR(200) COMMENT '摘要/交易类型',
|
||||
transaction_amount DECIMAL(20,2) COMMENT '交易金额',
|
||||
mark_status VARCHAR(50) COMMENT '标记状态:正常/可疑/异常',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '异常交易明细表';
|
||||
|
||||
-- 排查进度表
|
||||
CREATE TABLE ccdi_investigation_progress (
|
||||
progress_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
object_id BIGINT COMMENT '排查对象ID',
|
||||
progress_type VARCHAR(100) COMMENT '进度类型:流水分析/征信分析/资产比对/人工核实',
|
||||
progress_status VARCHAR(50) COMMENT '进度状态',
|
||||
complete_time DATETIME COMMENT '完成时间',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '排查进度表';
|
||||
|
||||
-- 关注列表表
|
||||
CREATE TABLE ccdi_attention_list (
|
||||
attention_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
object_id BIGINT COMMENT '排查对象ID',
|
||||
person_id BIGINT COMMENT '人员ID',
|
||||
attention_type VARCHAR(50) COMMENT '关注类型',
|
||||
create_by VARCHAR(100) COMMENT '创建人',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '关注列表表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.6 页面5:专项排查
|
||||
|
||||
#### 功能描述
|
||||
员工详查分析功能,包括资产收入分析、图谱分析、采购查询等。
|
||||
|
||||
#### 页面元素
|
||||
| 元素类型 | 元素名称/内容 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 查询条件 | 身份证号 | 输入框 |
|
||||
| | 开始日期、结束日期 | 日期选择器 |
|
||||
| | 查询、重置 | 按钮 |
|
||||
| 详查结果 | 详查结果描述 | 如:收入+负债远低于资产 |
|
||||
| 基本信息 | 姓名、身份证号、资产/收入比 | |
|
||||
| 收入分析 | 工资收入、其他收入 | 显示金额和百分比 |
|
||||
| 本人资产分析 | 房产、存款、其他 | 显示金额和百分比 |
|
||||
| 配偶资产分析 | 房产、车产、其他 | 显示金额和百分比 |
|
||||
| 负债分析 | 房贷、其他贷款 | 显示金额和百分比 |
|
||||
| 汇总信息 | 本人+配偶资产合计、总负债 | |
|
||||
| 图谱分析标签页 | 关系人图谱、资金流图谱、实控账户图谱 | |
|
||||
| 关系人图谱 | 姓名搜索框、生成图谱按钮 | |
|
||||
| | 可视化图谱 | 显示配偶、对外投资、股东、高管关联等 |
|
||||
| | 操作按钮 | 展开所有关联、仅显示直接关联、导出图谱、筛选、刷新 |
|
||||
| 采购查询表格 | 序号、采购事项名称、交易日期、采购金额、供应商名称、对方账号、联系人、关联员工 | |
|
||||
| 扩展查询标签页 | 采购查询、人员调动查询、招聘查询 | |
|
||||
| 采购查询条件 | 采购时间范围、关联员工 | |
|
||||
|
||||
#### 数据模型
|
||||
```sql
|
||||
-- 员工资产分析表
|
||||
CREATE TABLE ccdi_employee_asset_analysis (
|
||||
analysis_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
person_id BIGINT COMMENT '人员ID',
|
||||
person_name VARCHAR(100) COMMENT '姓名',
|
||||
id_card VARCHAR(50) COMMENT '身份证号',
|
||||
asset_income_ratio DECIMAL(10,2) COMMENT '资产/收入比',
|
||||
annual_income DECIMAL(20,2) COMMENT '年收入',
|
||||
own_asset DECIMAL(20,2) COMMENT '本人资产',
|
||||
spouse_asset DECIMAL(20,2) COMMENT '配偶资产',
|
||||
total_asset DECIMAL(20,2) COMMENT '本人+配偶资产合计',
|
||||
total_liability DECIMAL(20,2) COMMENT '总负债',
|
||||
income_salary DECIMAL(20,2) COMMENT '工资收入',
|
||||
income_other DECIMAL(20,2) COMMENT '其他收入',
|
||||
asset_house DECIMAL(20,2) COMMENT '房产',
|
||||
asset_deposit DECIMAL(20,2) COMMENT '存款',
|
||||
asset_other DECIMAL(20,2) COMMENT '其他',
|
||||
liability_mortgage DECIMAL(20,2) COMMENT '房贷',
|
||||
liability_loan DECIMAL(20,2) COMMENT '其他贷款',
|
||||
spouse_asset_house DECIMAL(20,2) COMMENT '配偶房产',
|
||||
spouse_asset_car DECIMAL(20,2) COMMENT '配偶车产',
|
||||
spouse_asset_other DECIMAL(20,2) COMMENT '配偶其他',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '员工资产分析表';
|
||||
|
||||
-- 关系人图谱表
|
||||
CREATE TABLE ccdi_relationship_graph (
|
||||
graph_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
person_id BIGINT COMMENT '人员ID',
|
||||
related_person_name VARCHAR(100) COMMENT '关联人姓名',
|
||||
relationship_type VARCHAR(100) COMMENT '关系类型:配偶/对外投资/股东/高管关联',
|
||||
related_entity_name VARCHAR(200) COMMENT '关联实体名称',
|
||||
share_ratio DECIMAL(5,2) COMMENT '持股比例',
|
||||
position VARCHAR(200) COMMENT '职位',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '关系人图谱表';
|
||||
|
||||
-- 采购查询记录表
|
||||
CREATE TABLE ccdi_purchase_record (
|
||||
purchase_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
purchase_name VARCHAR(500) COMMENT '采购事项名称',
|
||||
transaction_date DATE COMMENT '交易日期',
|
||||
purchase_amount DECIMAL(20,2) COMMENT '采购金额',
|
||||
supplier_name VARCHAR(500) COMMENT '供应商名称',
|
||||
supplier_account VARCHAR(200) COMMENT '对方账号',
|
||||
contact_person VARCHAR(100) COMMENT '联系人',
|
||||
related_employee VARCHAR(100) COMMENT '关联员工',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '采购查询记录表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.7 页面6:专项排查工作台-中风险
|
||||
|
||||
#### 功能描述
|
||||
针对中风险人员的排查工作台,功能与高风险工作台类似,但风险等级不同。
|
||||
|
||||
#### 页面元素
|
||||
与高风险工作台结构相同,主要区别:
|
||||
- 风险等级显示为"中风险"
|
||||
- 初核评分可能较低
|
||||
- 触发模型数量可能较少
|
||||
|
||||
数据模型与高风险工作台共用。
|
||||
|
||||
---
|
||||
|
||||
### 2.8 页面7:流水明细查询
|
||||
|
||||
#### 功能描述
|
||||
查询和筛选银行流水明细。
|
||||
|
||||
#### 页面元素
|
||||
| 元素类型 | 元素名称/内容 | 说明 |
|
||||
|---------|--------------|------|
|
||||
| 筛选条件 | 交易时间范围 | 开始日期、结束日期 |
|
||||
| | 对方名称 | 输入框,支持空值筛选 |
|
||||
| | 摘要 | 输入框,支持空值筛选 |
|
||||
| | 分类 | 多选下拉 |
|
||||
| | 本方主体 | 多选下拉 |
|
||||
| | 本方银行 | 多选下拉 |
|
||||
| | 本方账户 | 多选下拉 |
|
||||
| | 交易金额 | 范围输入(最小~最大) |
|
||||
| | 对方账户 | 输入框,支持空值筛选 |
|
||||
| | 交易类型 | 输入框,支持空值筛选 |
|
||||
| | 剔除关联方与本方 | 复选框 |
|
||||
| | 查询、重置 | 按钮 |
|
||||
| 流水类型切换 | 全部、流入、流出 | 单选或Tab切换 |
|
||||
| 流水明细表格 | 交易时间、本行账户/主体、对方名称/账户、摘要/交易类型、交易金额、分类、操作 | 支持复选框 |
|
||||
| 表格操作 | 修改分类 | 下拉或弹窗 |
|
||||
| 底部操作栏 | 已筛选X笔流水,已选中X笔流水 | |
|
||||
| | 导出流水 | |
|
||||
| | 加入分析 | |
|
||||
| 标签页 | 流水、对手方 | |
|
||||
|
||||
#### 数据模型
|
||||
```sql
|
||||
-- 流水明细表
|
||||
CREATE TABLE ccdi_transaction_detail (
|
||||
detail_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
transaction_time DATETIME COMMENT '交易时间',
|
||||
own_account VARCHAR(200) COMMENT '本方账户/主体',
|
||||
own_bank VARCHAR(200) COMMENT '本方银行',
|
||||
counterparty_name VARCHAR(500) COMMENT '对方名称/账户',
|
||||
counterparty_account VARCHAR(200) COMMENT '对方账户',
|
||||
transaction_summary VARCHAR(500) COMMENT '摘要',
|
||||
transaction_type VARCHAR(200) COMMENT '交易类型',
|
||||
transaction_amount DECIMAL(20,2) COMMENT '交易金额',
|
||||
transaction_direction VARCHAR(50) COMMENT '交易方向:流入/流出',
|
||||
category VARCHAR(200) COMMENT '分类',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '流水明细表';
|
||||
|
||||
-- 流水分类表
|
||||
CREATE TABLE ccdi_transaction_category (
|
||||
category_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
category_code VARCHAR(100) COMMENT '分类编码',
|
||||
category_name VARCHAR(200) COMMENT '分类名称',
|
||||
parent_id BIGINT COMMENT '父分类ID',
|
||||
sort_order INT COMMENT '排序',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) COMMENT '流水分类表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、模块划分与开发建议
|
||||
|
||||
### 3.1 后端模块划分
|
||||
|
||||
```
|
||||
ruoyi-ccdi/ (新建模块)
|
||||
├── controller/
|
||||
│ ├── CcdiProjectController.java # 项目管理
|
||||
│ ├── CcdiDataUploadController.java # 数据上传
|
||||
│ ├── CcdiModelConfigController.java # 模型配置
|
||||
│ ├── CcdiPreliminaryCheckController.java # 初核提示
|
||||
│ ├── CcdiInvestigationController.java # 专项排查工作台
|
||||
│ ├── CcdiSpecialCheckController.java # 专项排查
|
||||
│ └── CcdiTransactionController.java # 流水明细查询
|
||||
├── service/
|
||||
│ ├── ICcdiProjectService.java
|
||||
│ ├── ICcdiDataUploadService.java
|
||||
│ ├── ICcdiModelConfigService.java
|
||||
│ ├── ICcdiPreliminaryCheckService.java
|
||||
│ ├── ICcdiInvestigationService.java
|
||||
│ ├── ICcdiSpecialCheckService.java
|
||||
│ └── ICcdiTransactionService.java
|
||||
├── mapper/
|
||||
│ ├── CcdiProjectMapper.java
|
||||
│ ├── CcdiDataUploadMapper.java
|
||||
│ ├── CcdiModelConfigMapper.java
|
||||
│ ├── CcdiPreliminaryCheckMapper.java
|
||||
│ ├── CcdiInvestigationMapper.java
|
||||
│ ├── CcdiSpecialCheckMapper.java
|
||||
│ └── CcdiTransactionMapper.java
|
||||
├── domain/
|
||||
│ ├── CcdiProject.java
|
||||
│ ├── CcdiDataUpload.java
|
||||
│ ├── CcdiModelConfig.java
|
||||
│ ├── CcdiPersonRiskScore.java
|
||||
│ ├── CcdiInvestigationObject.java
|
||||
│ └── ...
|
||||
├── dto/
|
||||
│ ├── CcdiProjectQueryDTO.java
|
||||
│ ├── CcdiDataUploadDTO.java
|
||||
│ ├── CcdiModelConfigDTO.java
|
||||
│ └── ...
|
||||
└── vo/
|
||||
├── CcdiProjectVO.java
|
||||
├── CcdiPreliminaryCheckVO.java
|
||||
├── CcdiInvestigationVO.java
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 3.2 前端模块划分
|
||||
|
||||
```
|
||||
ruoyi-ui/src/views/ccdi/
|
||||
├── project/
|
||||
│ ├── index.vue # 项目列表
|
||||
│ ├── detail.vue # 项目详情
|
||||
│ ├── upload.vue # 上传数据
|
||||
│ └── components/
|
||||
│ ├── UploadCard.vue # 上传卡片组件
|
||||
│ ├── QualityCheck.vue # 数据质量检查组件
|
||||
│ └── BlacklistSelect.vue # 名单库选择组件
|
||||
├── model/
|
||||
│ ├── config.vue # 参数配置
|
||||
│ └── components/
|
||||
│ └── ModelConfigTable.vue # 模型配置表格组件
|
||||
├── preliminary/
|
||||
│ ├── index.vue # 初核提示
|
||||
│ └── components/
|
||||
│ ├── RiskStatistics.vue # 风险统计卡片
|
||||
│ ├── ModelTriggerTable.vue # 模型触发表格
|
||||
│ ├── SuspiciousTransactionTable.vue # 涉疑交易表格
|
||||
│ └── PersonRiskList.vue # 人员风险列表
|
||||
├── investigation/
|
||||
│ ├── high-risk.vue # 高风险工作台
|
||||
│ ├── mid-risk.vue # 中风险工作台
|
||||
│ └── components/
|
||||
│ ├── ObjectInfo.vue # 排查对象信息
|
||||
│ ├── AbnormalTransaction.vue # 异常交易明细
|
||||
│ ├── InvestigationTools.vue # 排查工具箱
|
||||
│ └── InvestigationTabs.vue # 排查进度标签页
|
||||
├── special/
|
||||
│ ├── index.vue # 专项排查
|
||||
│ └── components/
|
||||
│ ├── AssetAnalysis.vue # 资产分析
|
||||
│ ├── RelationshipGraph.vue # 关系人图谱
|
||||
│ └── PurchaseTable.vue # 采购查询表格
|
||||
└── transaction/
|
||||
└── index.vue # 流水明细查询
|
||||
```
|
||||
|
||||
### 3.3 开发顺序建议
|
||||
|
||||
1. **第一阶段:基础数据管理**
|
||||
- 项目管理(创建、查询、更新)
|
||||
- 数据上传功能
|
||||
- 数据质量检查
|
||||
|
||||
2. **第二阶段:模型配置**
|
||||
- 风险模型配置
|
||||
- 模型参数配置
|
||||
- 模型触发规则
|
||||
|
||||
3. **第三阶段:初核分析**
|
||||
- 初核提示页面
|
||||
- 风险评分计算
|
||||
- 人员风险分类
|
||||
|
||||
4. **第四阶段:排查工作台**
|
||||
- 高风险工作台
|
||||
- 中风险工作台
|
||||
- 排查进度跟踪
|
||||
|
||||
5. **第五阶段:专项排查**
|
||||
- 员工详查分析
|
||||
- 资产收入分析
|
||||
- 关系图谱分析
|
||||
- 采购查询
|
||||
|
||||
6. **第六阶段:流水查询**
|
||||
- 流水明细查询
|
||||
- 多维度筛选
|
||||
- 流水分类管理
|
||||
|
||||
---
|
||||
|
||||
## 四、关键技术要点
|
||||
|
||||
### 4.1 文件上传处理
|
||||
- 支持Excel、PDF、HTML多种格式
|
||||
- 需要实现文件解析功能
|
||||
- 大文件上传需要分片处理
|
||||
- 上传进度显示
|
||||
|
||||
### 4.2 数据质量检查
|
||||
- 数据完整性检查
|
||||
- 格式一致性检查
|
||||
- 余额连续性检查
|
||||
- 异常数据识别
|
||||
|
||||
### 4.3 风险评分模型
|
||||
- 可配置的风险模型
|
||||
- 可配置的阈值参数
|
||||
- 多模型触发计算
|
||||
- 风险等级分类
|
||||
|
||||
### 4.4 图谱可视化
|
||||
- 关系人图谱展示
|
||||
- 资金流向图谱
|
||||
- 实控账户图谱
|
||||
- 图谱交互操作
|
||||
|
||||
### 4.5 数据导出
|
||||
- 支持多种导出格式
|
||||
- 大数据量导出优化
|
||||
- 批量导出功能
|
||||
|
||||
---
|
||||
|
||||
## 五、接口设计建议
|
||||
|
||||
### 5.1 项目管理接口
|
||||
|
||||
```
|
||||
POST /ccdi/project/list # 项目列表查询
|
||||
GET /ccdi/project/{id} # 项目详情
|
||||
POST /ccdi/project # 新增项目
|
||||
PUT /ccdi/project # 更新项目
|
||||
DELETE /ccdi/project/{id} # 删除项目
|
||||
```
|
||||
|
||||
### 5.2 数据上传接口
|
||||
|
||||
```
|
||||
POST /ccdi/upload/transaction # 上传流水文件
|
||||
POST /ccdi/upload/credit # 上传征信文件
|
||||
POST /ccdi/upload/relation # 上传家庭关系文件
|
||||
GET /ccdi/upload/progress/{id} # 查询上传进度
|
||||
POST /ccdi/upload/quality/check # 数据质量检查
|
||||
```
|
||||
|
||||
### 5.3 初核分析接口
|
||||
|
||||
```
|
||||
GET /ccdi/preliminary/statistics # 获取统计数据
|
||||
GET /ccdi/preliminary/model/trigger # 模型触发情况
|
||||
GET /ccdi/preliminary/transaction # 涉疑交易明细
|
||||
GET /ccdi/preliminary/person/list # 人员风险列表
|
||||
GET /ccdi/preliminary/abnormal/account # 异常账户列表
|
||||
POST /ccdi/preliminary/batch/report # 批量生成报告
|
||||
```
|
||||
|
||||
### 5.4 排查工作台接口
|
||||
|
||||
```
|
||||
GET /ccdi/investigation/object/{id} # 获取排查对象详情
|
||||
GET /ccdi/investigation/abnormal/{id} # 获取异常交易详情
|
||||
GET /ccdi/investigation/progress/{id} # 获取排查进度
|
||||
PUT /ccdi/investigation/mark/status # 标记状态
|
||||
POST /ccdi/investigation/report # 生成排查报告
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、数据库表汇总
|
||||
|
||||
| 序号 | 表名 | 说明 |
|
||||
|------|------|------|
|
||||
| 1 | ccdi_project | 项目表 |
|
||||
| 2 | ccdi_data_upload | 数据上传记录表 |
|
||||
| 3 | ccdi_blacklist_selection | 名单库选择记录表 |
|
||||
| 4 | ccdi_data_quality | 数据质量检查表 |
|
||||
| 5 | ccdi_risk_model | 风险模型表 |
|
||||
| 6 | ccdi_model_parameter | 模型参数配置表 |
|
||||
| 7 | ccdi_person_risk_score | 人员风险评分表 |
|
||||
| 8 | ccdi_model_trigger_record | 模型触发记录表 |
|
||||
| 9 | ccdi_suspicious_transaction | 涉嫌交易明细表 |
|
||||
| 10 | ccdi_abnormal_account | 异常账户表 |
|
||||
| 11 | ccdi_illegal_person_info | 违法人员信息表 |
|
||||
| 12 | ccdi_investigation_object | 排查对象表 |
|
||||
| 13 | ccdi_investigation_trigger_model | 排查触发模型表 |
|
||||
| 14 | ccdi_abnormal_transaction_detail | 异常交易明细表 |
|
||||
| 15 | ccdi_investigation_progress | 排查进度表 |
|
||||
| 16 | ccdi_attention_list | 关注列表表 |
|
||||
| 17 | ccdi_employee_asset_analysis | 员工资产分析表 |
|
||||
| 18 | ccdi_relationship_graph | 关系人图谱表 |
|
||||
| 19 | ccdi_purchase_record | 采购查询记录表 |
|
||||
| 20 | ccdi_transaction_detail | 流水明细表 |
|
||||
| 21 | ccdi_transaction_category | 流水分类表 |
|
||||
|
||||
---
|
||||
|
||||
## 七、前端组件建议
|
||||
|
||||
### 7.1 通用组件
|
||||
|
||||
```javascript
|
||||
// components/ccdi/
|
||||
├── UploadCard.vue # 文件上传卡片
|
||||
├── RiskStatisticsCard.vue # 风险统计卡片
|
||||
├── QualityProgressBar.vue # 质量检查进度条
|
||||
├── ModelTriggerTable.vue # 模型触发表格
|
||||
├── PersonRiskList.vue # 人员风险列表
|
||||
├── TransactionTable.vue # 交易明细表格
|
||||
├── RelationshipGraph.vue # 关系图谱组件
|
||||
└── FilterPanel.vue # 筛选面板组件
|
||||
```
|
||||
|
||||
### 7.2 图表组件
|
||||
|
||||
```javascript
|
||||
// 使用ECharts实现
|
||||
├── RiskDistributionChart.vue # 风险分布图
|
||||
├── ModelTriggerChart.vue # 模型触发图表
|
||||
├── AssetAnalysisChart.vue # 资产分析图表
|
||||
└── RelationshipGraphChart.vue # 关系图谱
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、开发注意事项
|
||||
|
||||
### 8.1 权限控制
|
||||
- 项目级权限控制
|
||||
- 数据访问权限
|
||||
- 敏感信息脱敏
|
||||
|
||||
### 8.2 性能优化
|
||||
- 大数据量查询分页
|
||||
- 索引优化
|
||||
- 缓存策略
|
||||
|
||||
### 8.3 数据安全
|
||||
- 敏感数据加密
|
||||
- 操作日志记录
|
||||
- 数据备份
|
||||
|
||||
### 8.4 用户体验
|
||||
- 加载状态提示
|
||||
- 操作反馈
|
||||
- 错误提示
|
||||
|
||||
---
|
||||
|
||||
## 九、后续扩展方向
|
||||
|
||||
1. **智能分析**:引入机器学习算法,提高风险识别准确率
|
||||
2. **移动端适配**:开发移动端应用,支持移动办公
|
||||
3. **报表中心**:自定义报表功能
|
||||
4. **预警机制**:实时预警通知
|
||||
5. **案例库管理**:典型案例沉淀和复用
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建时间**: 2025-01-30
|
||||
**最后更新**: 2025-01-30
|
||||
326
doc/后端枚举字段说明.md
Normal file
326
doc/后端枚举字段说明.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# 后端枚举字段说明
|
||||
|
||||
## 概述
|
||||
|
||||
后端只返回枚举代码值,不返回枚举名称。前端需要根据代码值进行转换显示。
|
||||
|
||||
---
|
||||
|
||||
## API 返回的枚举字段
|
||||
|
||||
### 1. 中介类型 (intermediaryType)
|
||||
|
||||
| 代码值 | 说明 |
|
||||
|--------|------|
|
||||
| `1` | 个人中介 |
|
||||
| `2` | 机构中介 |
|
||||
|
||||
**前端转换示例:**
|
||||
```javascript
|
||||
const getIntermediaryTypeName = (type) => {
|
||||
const map = {
|
||||
'1': '个人',
|
||||
'2': '机构'
|
||||
}
|
||||
return map[type] || '未知'
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 状态 (status)
|
||||
|
||||
| 代码值 | 说明 |
|
||||
|--------|------|
|
||||
| `0` | 正常 |
|
||||
| `1` | 停用 |
|
||||
|
||||
**前端转换示例:**
|
||||
```javascript
|
||||
const getStatusName = (status) => {
|
||||
const map = {
|
||||
'0': '正常',
|
||||
'1': '停用'
|
||||
}
|
||||
return map[status] || '未知'
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据来源 (dataSource / date_source)
|
||||
|
||||
| 代码值 | 说明 |
|
||||
|--------|------|
|
||||
| `MANUAL` | 手动录入 |
|
||||
| `IMPORT` | 批量导入 |
|
||||
| `SYSTEM` | 系统同步 |
|
||||
| `API` | 接口获取 |
|
||||
|
||||
**前端转换示例:**
|
||||
```javascript
|
||||
const getDataSourceName = (source) => {
|
||||
const map = {
|
||||
'MANUAL': '手动录入',
|
||||
'IMPORT': '批量导入',
|
||||
'SYSTEM': '系统同步',
|
||||
'API': '接口获取'
|
||||
}
|
||||
return map[source] || '未知'
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 性别 (indivGender) - 个人中介
|
||||
|
||||
| 代码值 | 说明 |
|
||||
|--------|------|
|
||||
| `M` | 男 |
|
||||
| `F` | 女 |
|
||||
| `O` | 其他 |
|
||||
|
||||
**前端转换示例:**
|
||||
```javascript
|
||||
const getGenderName = (gender) => {
|
||||
const map = {
|
||||
'M': '男',
|
||||
'F': '女',
|
||||
'O': '其他'
|
||||
}
|
||||
return map[gender] || '未知'
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 证件类型 (indivCertType)
|
||||
|
||||
常用证件类型代码:
|
||||
- `身份证` - 身份证
|
||||
- `护照` - 护照
|
||||
- `港澳通行证` - 港澳通行证
|
||||
- `台湾通行证` - 台湾通行证
|
||||
|
||||
---
|
||||
|
||||
## API 返回数据示例
|
||||
|
||||
### 列表查询响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"rows": [
|
||||
{
|
||||
"intermediaryId": 1,
|
||||
"name": "张三",
|
||||
"certificateNo": "110101199001011234",
|
||||
"intermediaryType": "1",
|
||||
"status": "0",
|
||||
"dataSource": "MANUAL",
|
||||
"createTime": "2026-02-04 10:00:00",
|
||||
"updateTime": "2026-02-04 10:00:00"
|
||||
},
|
||||
{
|
||||
"intermediaryId": 0,
|
||||
"name": "测试机构有限公司",
|
||||
"certificateNo": "91110000123456789X",
|
||||
"intermediaryType": "2",
|
||||
"status": "0",
|
||||
"dataSource": "MANUAL",
|
||||
"createTime": "2026-02-04 10:00:00",
|
||||
"updateTime": "2026-02-04 10:00:00"
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 个人中介详情响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"intermediaryId": 1,
|
||||
"name": "张三",
|
||||
"certificateNo": "110101199001011234",
|
||||
"intermediaryType": "1",
|
||||
"status": "0",
|
||||
"dataSource": "MANUAL",
|
||||
"remark": "测试数据",
|
||||
"indivType": "中介",
|
||||
"indivSubType": "本人",
|
||||
"indivGender": "M",
|
||||
"indivCertType": "身份证",
|
||||
"indivPhone": "13800138000",
|
||||
"indivWechat": "test_wx001",
|
||||
"indivAddress": "北京市朝阳区测试路123号",
|
||||
"indivCompany": "测试公司",
|
||||
"indivPosition": "测试员",
|
||||
"createTime": "2026-02-04 10:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 机构中介详情响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"intermediaryId": 0,
|
||||
"name": "测试机构有限公司",
|
||||
"certificateNo": "91110000123456789X",
|
||||
"intermediaryType": "2",
|
||||
"status": "0",
|
||||
"dataSource": "MANUAL",
|
||||
"remark": "机构中介测试数据",
|
||||
"corpCreditCode": "91110000123456789X",
|
||||
"corpType": "有限责任公司",
|
||||
"corpNature": "民营企业",
|
||||
"corpIndustryCategory": "制造业",
|
||||
"corpIndustry": "通用设备制造业",
|
||||
"corpEstablishDate": "2020-01-01",
|
||||
"corpAddress": "北京市海淀区测试大街456号",
|
||||
"corpLegalRep": "李四",
|
||||
"corpLegalCertType": "身份证",
|
||||
"corpLegalCertNo": "110101198001011234",
|
||||
"createTime": "2026-02-04 10:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端 Vue 组件示例
|
||||
|
||||
### 枚举转换工具函数
|
||||
|
||||
```javascript
|
||||
// utils/enums.js
|
||||
export const IntermediaryType = {
|
||||
PERSON: '1',
|
||||
ENTITY: '2',
|
||||
getName: (type) => {
|
||||
const map = {
|
||||
'1': '个人',
|
||||
'2': '机构'
|
||||
}
|
||||
return map[type] || '未知'
|
||||
}
|
||||
}
|
||||
|
||||
export const IntermediaryStatus = {
|
||||
NORMAL: '0',
|
||||
DISABLED: '1',
|
||||
getName: (status) => {
|
||||
const map = {
|
||||
'0': '正常',
|
||||
'1': '停用'
|
||||
}
|
||||
return map[status] || '未知'
|
||||
}
|
||||
}
|
||||
|
||||
export const DataSource = {
|
||||
MANUAL: 'MANUAL',
|
||||
IMPORT: 'IMPORT',
|
||||
SYSTEM: 'SYSTEM',
|
||||
API: 'API',
|
||||
getName: (source) => {
|
||||
const map = {
|
||||
'MANUAL': '手动录入',
|
||||
'IMPORT': '批量导入',
|
||||
'SYSTEM': '系统同步',
|
||||
'API': '接口获取'
|
||||
}
|
||||
return map[source] || '未知'
|
||||
}
|
||||
}
|
||||
|
||||
export const Gender = {
|
||||
MALE: 'M',
|
||||
FEMALE: 'F',
|
||||
OTHER: 'O',
|
||||
getName: (gender) => {
|
||||
const map = {
|
||||
'M': '男',
|
||||
'F': '女',
|
||||
'O': '其他'
|
||||
}
|
||||
return map[gender] || '未知'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 表格列使用枚举
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column prop="name" label="姓名" />
|
||||
|
||||
<el-table-column prop="intermediaryType" label="中介类型">
|
||||
<template #default="{ row }">
|
||||
{{ IntermediaryType.getName(row.intermediaryType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||
{{ IntermediaryStatus.getName(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="dataSource" label="数据来源">
|
||||
<template #default="{ row }">
|
||||
{{ DataSource.getName(row.dataSource) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IntermediaryType, IntermediaryStatus, DataSource } from '@/utils/enums'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
IntermediaryType,
|
||||
IntermediaryStatus,
|
||||
DataSource,
|
||||
tableData: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 表单下拉框使用枚举
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-form :model="form">
|
||||
<el-form-item label="中介类型" prop="intermediaryType">
|
||||
<el-select v-model="form.intermediaryType" placeholder="请选择中介类型">
|
||||
<el-option label="个人" value="1" />
|
||||
<el-option label="机构" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="0">正常</el-radio>
|
||||
<el-radio label="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **后端只返回代码值**,前端负责转换为显示名称
|
||||
2. **前端下拉框的 value 应该使用代码值**(如 '1', '2', '0' 等)
|
||||
3. **建议在前端统一维护枚举映射关系**,避免硬编码
|
||||
4. **新增枚举值时**,只需要前端更新映射表即可,后端无需修改
|
||||
5. **国际化支持**:前端可以根据语言切换返回不同的名称
|
||||
@@ -1,241 +0,0 @@
|
||||
# ✅ Form-Data 修复完成报告
|
||||
|
||||
## 修复日期
|
||||
2026-03-03
|
||||
## 修复人员
|
||||
Claude Code
|
||||
## 修复状态
|
||||
✅ **已完成** - 所有接口已改为 form-data 方式,测试全部通过
|
||||
|
||||
---
|
||||
|
||||
## 📝 问题说明
|
||||
|
||||
用户指出:**接口参数应该通过 form-data 进行传输**
|
||||
|
||||
原代码使用 JSON body (`json=`) 方式传输参数,但接口文档要求使用 **form-data** (`application/x-www-form-urlencoded`) 方式传输。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 修复内容
|
||||
|
||||
### 1. 修改接口参数接收方式
|
||||
|
||||
将所有接口从 `json=` 改为使用 FastAPI 的 `Form` 参数
|
||||
|
||||
#### 修改前:
|
||||
```python
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(request: GetTokenRequest):
|
||||
# 接收 JSON body
|
||||
...
|
||||
```
|
||||
|
||||
#### 修改后:
|
||||
```python
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(
|
||||
projectNo: str = Form(..., description="项目编号"),
|
||||
entityName: str = Form(..., description="项目名称"),
|
||||
userId: str = Form(..., description="操作人员编号"),
|
||||
# ... 其他参数
|
||||
):
|
||||
# 构建请求对象
|
||||
request = GetTokenRequest(
|
||||
projectNo=projectNo,
|
||||
entityName=entityName,
|
||||
...
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
### 2. 修改的接口列表
|
||||
|
||||
| 接口 | 路径 | 修改内容 |
|
||||
|------|------|---------|
|
||||
| 1 | `/account/common/getToken` | ✅ 15个 Form 参数 |
|
||||
| 2 | `/watson/api/project/remoteUploadSplitFile` | ✅ 已使用 Form,| 3 | `/watson/api/project/getJZFileOrZjrcuFile` | ✅ 7个 Form 参数 |
|
||||
| 4 | `/watson/api/project/upload/getpendings` | ✅ 2个 Form 参数 |
|
||||
| 5 | `/watson/api/project/batchDeleteUploadFile` | ✅ 3个 Form 参数 |
|
||||
| 6 | `/watson/api/project/getBSByLogId` | ✅ 4个 Form 参数 |
|
||||
|
||||
### 3. 修改的文件
|
||||
|
||||
#### 核心代码
|
||||
1. **routers/api.py** - 所有接口改为使用 Form 参数
|
||||
- 接口1: 15个 Form 参数
|
||||
- 接口3: 7个 Form 参数
|
||||
- 接口4: 2个 Form 参数
|
||||
- 接口5: 3个 Form 参数(支持逗号分隔的字符串)
|
||||
- 接口6: 4个 Form 参数
|
||||
|
||||
#### 测试代码
|
||||
2. **tests/conftest.py** - 更新测试 fixture
|
||||
3. **tests/test_api.py** - 更新单元测试
|
||||
- 将 `json=` 改为 `data=`
|
||||
4. **tests/integration/test_full_workflow.py** - 更新集成测试
|
||||
- 将所有 `json=` 改为 `data=`
|
||||
|
||||
#### 文档
|
||||
5. **README.md** - 更新使用示例
|
||||
- 将示例代码改为使用 `data=` 参数
|
||||
|
||||
---
|
||||
|
||||
## ✅ 测试结果
|
||||
|
||||
```bash
|
||||
============================= test session starts =============================
|
||||
platform win32 -- Python 3.13.12, pytest-9.0.2, pluggy-1.6.0
|
||||
rootdir: D:\ccdi\ccdi\.claude\worktrees\lsfx-mock-server\lsfx-mock-server
|
||||
plugins: anyio-4.12.1, cov-7.0.0
|
||||
collected 7 items
|
||||
|
||||
tests/integration/test_full_workflow.py::test_complete_workflow PASSED [ 14%]
|
||||
tests/integration/test_full_workflow.py::test_all_error_codes PASSED [ 28%]
|
||||
tests/integration/test_full_workflow.py::test_pagination PASSED [ 42%]
|
||||
tests/test_api.py::test_root_endpoint PASSED [ 57%]
|
||||
tests/test_api.py::test_health_check PASSED [ 71%]
|
||||
tests/test_api.py::test_get_token_success PASSED [ 85%]
|
||||
tests/test_api.py::test_get_token_error_40101 PASSED [100%]
|
||||
|
||||
======================== 7 passed, 1 warning in 0.08s =========================
|
||||
```
|
||||
|
||||
**结论**: ✅ **所有 7 个测试用例通过**
|
||||
|
||||
---
|
||||
|
||||
## 📖 使用示例
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
# ✅ 正确方式:使用 data 参数
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
data={ # 使用 data 参数,不是 json
|
||||
"projectNo": "test_project_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "your_secret_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### curl 命令
|
||||
```bash
|
||||
# ✅ 使用 form-data 方式
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "projectNo=test_project_001" \
|
||||
-d "entityName=测试企业" \
|
||||
-d "userId=902001" \
|
||||
-d "userName=902001" \
|
||||
-d "appId=remote_app" \
|
||||
-d "appSecretCode=your_secret_code" \
|
||||
-d "role=VIEWER" \
|
||||
-d "orgCode=902000" \
|
||||
-d "departmentCode=902000"
|
||||
```
|
||||
|
||||
### Swagger UI
|
||||
访问 http://localhost:8000/docs,Swagger UI 会自动显示正确的参数格式(form-data)。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 关键改进
|
||||
|
||||
### 1. 正确的 Content-Type
|
||||
- **修改前**: `application/json`
|
||||
- **修改后**: `application/x-www-form-urlencoded`
|
||||
|
||||
### 2. 参数传递方式
|
||||
- **修改前**: 使用 `json={}` 参数
|
||||
- **修改后**: 使用 `data={}` 参数
|
||||
|
||||
### 3. FastAPI 自动处理
|
||||
FastAPI 会自动:
|
||||
- 解析 form-data 格式的参数
|
||||
- 进行类型转换
|
||||
- 生成正确的 Swagger 文档
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要提示
|
||||
|
||||
### 1. 不向后兼容
|
||||
❌ **此修复不向后兼容**
|
||||
|
||||
所有调用这些接口的客户端需要:
|
||||
- 将 `json=` 改为 `data=`
|
||||
- 将 `Content-Type` 从 `application/json` 改为 `application/x-www-form-urlencoded`
|
||||
|
||||
### 2. 数组参数处理
|
||||
对于接口5(删除文件),`logIds` 参数:
|
||||
- **传递方式**: 逗号分隔的字符串,如 `"10001,10002,10003"`
|
||||
- **后端处理**: 自动解析为整数列表
|
||||
|
||||
### 3. 可选参数
|
||||
可选参数可以:
|
||||
- 不传递
|
||||
- 传递空值
|
||||
- 传递默认值
|
||||
|
||||
---
|
||||
|
||||
## 📊 对比总结
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|------|--------|--------|
|
||||
| **参数格式** | JSON body | Form-data |
|
||||
| **Content-Type** | application/json | application/x-www-form-urlencoded |
|
||||
| **Python requests** | `json={}` | `data={}` |
|
||||
| **curl** | `-H "Content-Type: application/json" -d '{...}'` | `-d "key=value"` |
|
||||
| **Swagger UI** | Request body | Form data |
|
||||
| **测试状态** | ❌ 2 failed | ✅ 7 passed |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修复验证清单
|
||||
|
||||
- [x] 将所有接口改为使用 Form 参数
|
||||
- [x] 更新 GetToken 接口(15个参数)
|
||||
- [x] 更新 FetchInnerFlow 接口(7个参数)
|
||||
- [x] 更新 CheckParseStatus 接口(2个参数)
|
||||
- [x] 更新 DeleteFiles 接口(3个参数)
|
||||
- [x] 更新 GetBankStatement 接口(4个参数)
|
||||
- [x] 更新所有测试代码
|
||||
- [x] 运行测试通过(7/7 passed)
|
||||
- [x] 更新 README.md 示例
|
||||
- [x] 创建修复文档
|
||||
|
||||
---
|
||||
|
||||
## 📄 相关文档
|
||||
|
||||
1. **接口参数检查报告.md** - 参数对比分析
|
||||
2. **修复总结.md** - 参数修复记录
|
||||
3. **form-data修复说明.md** - 本次修复说明
|
||||
|
||||
---
|
||||
|
||||
## 🎉 修复结论
|
||||
|
||||
**状态**: ✅ **修复完成**
|
||||
|
||||
所有接口已改为使用 form-data 方式传输参数,与接口文档要求完全一致。Mock 服务器现在完全符合真实接口的调用方式。
|
||||
|
||||
**下一步**: 可以开始使用修复后的 Mock 服务器进行开发和测试。请确保所有客户端代码使用 `data=` 参数而不是 `json=` 参数。
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: Claude Code
|
||||
**修复日期**: 2026-03-03
|
||||
**版本**: v1.2.0
|
||||
145
form-data修复说明.md
145
form-data修复说明.md
@@ -1,145 +0,0 @@
|
||||
# 📋 Form-Data 修复说明
|
||||
|
||||
## 修复日期
|
||||
2026-03-03
|
||||
|
||||
## 问题描述
|
||||
原代码中使用 JSON body 方式传输参数,但接口文档要求使用 form-data (application/x-www-form-urlencoded) 方式传输。
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 修改接口参数接收方式
|
||||
将所有接口从 `json=` 改为 `data=` 或 `Form=`
|
||||
|
||||
**修改的文件:**
|
||||
- `routers/api.py` - 所有接口改为使用 Form 参数
|
||||
|
||||
- `models/request.py` - 更新请求模型
|
||||
- `tests/` - 所有测试代码更新为使用 data 参数
|
||||
- `README.md` - 更新示例代码
|
||||
|
||||
### 2. 修改的接口
|
||||
|
||||
#### 接口1: 获取Token
|
||||
- **修改前**: 使用 `json=GetTokenRequest` 接收 JSON body
|
||||
- **修改后**: 使用 Form 参数分别接收每个字段
|
||||
|
||||
```python
|
||||
# 修改前
|
||||
async def get_token(request: GetTokenRequest):
|
||||
...
|
||||
|
||||
# 修改后
|
||||
async def get_token(
|
||||
projectNo: str = Form(...),
|
||||
entityName: str = Form(...),
|
||||
userId: str = Form(...),
|
||||
# ... 其他参数
|
||||
):
|
||||
# 构建请求对象
|
||||
request = GetTokenRequest(...)
|
||||
...
|
||||
```
|
||||
|
||||
#### 接口3: 拉取行内流水
|
||||
- **修改前**: 使用 `json=FetchInnerFlowRequest`
|
||||
- **修改后**: 使用 Form 参数
|
||||
|
||||
#### 接口4: 检查解析状态
|
||||
- **修改前**: 使用 `json=CheckParseStatusRequest`
|
||||
- **修改后**: 使用 Form 参数
|
||||
|
||||
#### 接口5: 删除文件
|
||||
- **修改前**: 使用 `json=DeleteFilesRequest`
|
||||
- **修改后**: 使用 Form 参数
|
||||
- **特殊处理**: `logIds` 从数组改为逗号分隔的字符串
|
||||
|
||||
```python
|
||||
# 前端传递: logIds=10001,10002,10003
|
||||
# 后端处理:
|
||||
log_id_list = [int(id.strip()) for id in logIds.split(",")]
|
||||
```
|
||||
|
||||
#### 接口6: 获取银行流水
|
||||
- **修改前**: 使用 `json=GetBankStatementRequest`
|
||||
- **修改后**: 使用 Form 参数
|
||||
|
||||
### 3. 测试代码更新
|
||||
所有测试从 `json=` 改为 `data=`
|
||||
```python
|
||||
# 修改前
|
||||
response = client.post("/account/common/getToken", json=request_data)
|
||||
|
||||
# 修改后
|
||||
response = client.post("/account/common/getToken", data=request_data)
|
||||
```
|
||||
### 4. 文档更新
|
||||
README.md 中的示例代码更新为使用 `data=` 参数:
|
||||
```python
|
||||
# 修改前
|
||||
json={
|
||||
"projectNo": "test_project_001",
|
||||
...
|
||||
}
|
||||
|
||||
# 修改后
|
||||
data={
|
||||
"projectNo": "test_project_001",
|
||||
...
|
||||
}
|
||||
```
|
||||
## 测试结果
|
||||
✅ **所有测试通过 (7/7)**
|
||||
```bash
|
||||
======================== 7 passed, 1 warning in 0.08s =========================
|
||||
```
|
||||
## 使用示例
|
||||
### curl 请求
|
||||
```bash
|
||||
# 使用 form-data 方式
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "projectNo=test_project_001" \
|
||||
-d "entityName=测试企业" \
|
||||
-d "userId=902001" \
|
||||
-d "userName=902001" \
|
||||
-d "appId=remote_app" \
|
||||
-d "appSecretCode=your_secret_code" \
|
||||
-d "role=VIEWER" \
|
||||
-d "orgCode=902000" \
|
||||
-d "departmentCode=902000"
|
||||
```
|
||||
### Python requests 请求
|
||||
```python
|
||||
# 使用 form-data 方式
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
data={
|
||||
"projectNo": "test_project_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "your_secret_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
```
|
||||
## 注意事项
|
||||
1. **Content-Type**: 所有接口现在使用 `application/x-www-form-urlencoded`
|
||||
2. **数组参数**: 如 `logIds` 需要传递逗号分隔的字符串,如: `"10001,10002,10003"`
|
||||
3. **可选参数**: 可选参数可以不传或传空值
|
||||
4. **测试验证**: 所有 7 个测试用例全部通过
|
||||
## 影响范围
|
||||
- ✅ 不向后兼容
|
||||
- ⚠️ 所有调用这些接口的客户端需要更新为使用 form-data 方式
|
||||
- ⚠️ Swagger UI 会自动显示正确的参数格式
|
||||
## 修复验证
|
||||
- [x] 修改所有接口使用 Form 参数
|
||||
- [x] 更新所有测试代码
|
||||
- [x] 运行测试通过 (7/7)
|
||||
- [x] 创建修复文档
|
||||
## 修复状态
|
||||
✅ **已完成** - 所有接口已改为 form-data 方式,测试全部通过
|
||||
276
form-data最终确认.md
276
form-data最终确认.md
@@ -1,276 +0,0 @@
|
||||
# ✅ Form-Data 参数传输修复最终确认
|
||||
|
||||
## 修复日期
|
||||
2026-03-03
|
||||
## 修复状态
|
||||
✅ **已完成并验证** - 所有接口使用 form-data 传输,测试全部通过
|
||||
|
||||
---
|
||||
|
||||
## 📋 问题澄清
|
||||
|
||||
**用户反馈**:
|
||||
1. "接口参数应该通过 form-data 进行传输" ✅
|
||||
2. "接口还是使用 json 传输 检查代码" ❓
|
||||
3. "我调用接口的时候要用 formdata 传参 为什么改回 json?????" ✅
|
||||
|
||||
**结论**: 接口应该使用 **form-data** 传输参数,而不是 JSON body
|
||||
|
||||
---
|
||||
|
||||
## ✅ 最终实现
|
||||
|
||||
### 所有接口都使用 Form 参数
|
||||
|
||||
| 接口 | 路径 | 参数数量 | 传输方式 | 状态 |
|
||||
|------|------|---------|---------|------|
|
||||
| 1 | `/account/common/getToken` | 15个 Form 参数 | form-data | ✅ |
|
||||
| 2 | `/watson/api/project/remoteUploadSplitFile` | 2个 (Form + File) | form-data | ✅ |
|
||||
| 3 | `/watson/api/project/getJZFileOrZjrcuFile` | 7个 Form 参数 | form-data | ✅ |
|
||||
| 4 | `/watson/api/project/upload/getpendings` | 2个 Form 参数 | form-data | ✅ |
|
||||
| 5 | `/watson/api/project/batchDeleteUploadFile` | 3个 Form 参数 | form-data | ✅ |
|
||||
| 6 | `/watson/api/project/getBSByLogId` | 4个 Form 参数 | form-data | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 代码实现
|
||||
|
||||
### 接口1示例(getToken)
|
||||
|
||||
```python
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(
|
||||
projectNo: str = Form(..., description="项目编号"),
|
||||
entityName: str = Form(..., description="项目名称"),
|
||||
userId: str = Form(..., description="操作人员编号"),
|
||||
userName: str = Form(..., description="操作人员姓名"),
|
||||
appId: str = Form("remote_app", description="应用ID"),
|
||||
appSecretCode: str = Form(..., description="安全码"),
|
||||
role: str = Form("VIEWER", description="角色"),
|
||||
orgCode: str = Form(..., description="行社机构号"),
|
||||
entityId: Optional[str] = Form(None, description="企业统信码"),
|
||||
xdRelatedPersons: Optional[str] = Form(None, description="信贷关联人"),
|
||||
jzDataDateId: str = Form("0", description="金综链流水日期"),
|
||||
innerBSStartDateId: str = Form("0", description="行内流水开始日期"),
|
||||
innerBSEndDateId: str = Form("0", description="行内流水结束日期"),
|
||||
analysisType: str = Form("-1", description="分析类型"),
|
||||
departmentCode: str = Form(..., description="机构编码"),
|
||||
):
|
||||
# 构建请求对象并处理
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 测试验证
|
||||
|
||||
```bash
|
||||
======================== 7 passed, 1 warning in 0.06s =========================
|
||||
```
|
||||
|
||||
**所有 7 个测试用例通过** ✅
|
||||
|
||||
---
|
||||
|
||||
## 📖 使用示例
|
||||
|
||||
### ✅ Python requests(正确方式)
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 使用 data 参数发送 form-data
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
data={ # ✅ 使用 data 参数
|
||||
"projectNo": "test_project_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "your_secret_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
|
||||
# 不使用 json 参数!
|
||||
# response = requests.post(..., json={...}) # ❌ 错误方式
|
||||
```
|
||||
|
||||
### ✅ curl 命令(正确方式)
|
||||
|
||||
```bash
|
||||
# 使用 -d 参数发送 form-data
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-d "projectNo=test_001" \
|
||||
-d "entityName=测试企业" \
|
||||
-d "userId=902001" \
|
||||
-d "userName=902001" \
|
||||
-d "appId=remote_app" \
|
||||
-d "appSecretCode=your_code" \
|
||||
-d "role=VIEWER" \
|
||||
-d "orgCode=902000" \
|
||||
-d "departmentCode=902000"
|
||||
```
|
||||
|
||||
### ✅ JavaScript fetch(正确方式)
|
||||
|
||||
```javascript
|
||||
// 使用 FormData 对象
|
||||
const formData = new FormData();
|
||||
formData.append('projectNo', 'test_001');
|
||||
formData.append('entityName', '测试企业');
|
||||
formData.append('userId', '902001');
|
||||
formData.append('userName', '902001');
|
||||
formData.append('appId', 'remote_app');
|
||||
formData.append('appSecretCode', 'your_code');
|
||||
formData.append('role', 'VIEWER');
|
||||
formData.append('orgCode', '902000');
|
||||
formData.append('departmentCode', '902000');
|
||||
|
||||
fetch('http://localhost:8000/account/common/getToken', {
|
||||
method: 'POST',
|
||||
body: formData // ✅ 使用 FormData
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 常见错误
|
||||
|
||||
### ❌ 错误方式1:使用 JSON
|
||||
|
||||
```python
|
||||
# ❌ 错误:使用 json 参数
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
json={ # ❌ 不支持 JSON
|
||||
"projectNo": "test_001",
|
||||
...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**结果**: 422 Unprocessable Entity
|
||||
|
||||
### ❌ 错误方式2:使用 Content-Type: application/json
|
||||
|
||||
```bash
|
||||
# ❌ 错误:设置 JSON Content-Type
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"projectNo":"test_001",...}'
|
||||
```
|
||||
|
||||
**结果**: 422 Unprocessable Entity
|
||||
|
||||
---
|
||||
|
||||
## 📊 Content-Type 对比
|
||||
|
||||
| 方式 | Content-Type | Python requests | curl | FastAPI 参数 |
|
||||
|------|-------------|----------------|------|-------------|
|
||||
| **JSON** | `application/json` | `json={}` | `-H "Content-Type: application/json" -d '{...}'` | `request: Model` |
|
||||
| **Form-Data** | `application/x-www-form-urlencoded` | `data={}` | `-d "key=value"` | `Form(...)` |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 修复验证清单
|
||||
|
||||
- [x] 接口1(getToken)使用 15个 Form 参数
|
||||
- [x] 接口2(upload_file)使用 Form + File
|
||||
- [x] 接口3(fetch_inner_flow)使用 7个 Form 参数
|
||||
- [x] 接口4(check_parse_status)使用 2个 Form 参数
|
||||
- [x] 接口5(delete_files)使用 3个 Form 参数
|
||||
- [x] 接口6(get_bank_statement)使用 4个 Form 参数
|
||||
- [x] 所有测试代码使用 `data=` 参数
|
||||
- [x] 所有测试通过(7/7 passed)
|
||||
- [x] 文档已更新
|
||||
|
||||
---
|
||||
|
||||
## 🔍 如何验证
|
||||
|
||||
### 方法1: 查看 Swagger UI
|
||||
|
||||
1. 启动服务器: `python main.py`
|
||||
2. 访问: http://localhost:8000/docs
|
||||
3. 查看任何接口的 "Request body" 部分
|
||||
4. 应该显示 "Form data" 而不是 "JSON"
|
||||
|
||||
### 方法2: 运行测试
|
||||
|
||||
```bash
|
||||
cd lsfx-mock-server
|
||||
python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
应该看到: `7 passed`
|
||||
|
||||
### 方法3: 手动测试
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-d "projectNo=test_001" \
|
||||
-d "entityName=测试企业" \
|
||||
-d "userId=902001" \
|
||||
-d "userName=902001" \
|
||||
-d "appId=remote_app" \
|
||||
-d "appSecretCode=test_code" \
|
||||
-d "role=VIEWER" \
|
||||
-d "orgCode=902000" \
|
||||
-d "departmentCode=902000"
|
||||
```
|
||||
|
||||
应该返回成功的 JSON 响应
|
||||
|
||||
---
|
||||
|
||||
## 📄 修复文件
|
||||
|
||||
### 修改的文件
|
||||
|
||||
1. **routers/api.py** - 所有接口使用 Form 参数
|
||||
2. **tests/** - 所有测试使用 data 参数
|
||||
3. **README.md** - 示例代码更新
|
||||
|
||||
### 生成的文档
|
||||
|
||||
1. **接口参数检查报告.md** - 参数对比
|
||||
2. **修复总结.md** - 参数修复
|
||||
3. **form-data修复说明.md** - form-data 修复
|
||||
4. **form-data修复完成报告.md** - 完成报告
|
||||
5. **form-data最终确认.md** - 最终确认(本文档)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 修复总结
|
||||
|
||||
✅ **状态**: 修复完成并验证
|
||||
|
||||
✅ **实现**: 所有6个接口都使用 form-data 传输
|
||||
|
||||
✅ **测试**: 7个测试全部通过
|
||||
|
||||
✅ **文档**: README.md 已更新示例
|
||||
|
||||
✅ **验证**: Swagger UI 自动显示正确的参数格式
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步
|
||||
|
||||
Mock 服务器已准备就绪!可以开始使用:
|
||||
|
||||
1. **启动服务器**: `python main.py`
|
||||
2. **访问文档**: http://localhost:8000/docs
|
||||
3. **测试接口**: 使用 `data={}` 参数(Python)或 `-d "key=value"`(curl)
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: Claude Code
|
||||
**修复日期**: 2026-03-03
|
||||
**版本**: v1.3.0
|
||||
**状态**: ✅ 已完成并验证
|
||||
@@ -1,16 +0,0 @@
|
||||
# 应用配置
|
||||
APP_NAME=流水分析Mock服务
|
||||
APP_VERSION=1.0.0
|
||||
DEBUG=true
|
||||
|
||||
# 服务器配置
|
||||
HOST=0.0.0.0
|
||||
PORT=8000
|
||||
|
||||
# 模拟配置
|
||||
PARSE_DELAY_SECONDS=4
|
||||
MAX_FILE_SIZE=10485760
|
||||
|
||||
# 初始ID配置
|
||||
INITIAL_PROJECT_ID=1000
|
||||
INITIAL_LOG_ID=10000
|
||||
45
lsfx-mock-server/.gitignore
vendored
45
lsfx-mock-server/.gitignore
vendored
@@ -1,45 +0,0 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual Environment
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -1,19 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制项目文件
|
||||
COPY . .
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
|
||||
# 启动命令
|
||||
CMD ["python", "main.py"]
|
||||
@@ -1,244 +0,0 @@
|
||||
# 流水分析 Mock 服务器
|
||||
|
||||
基于 Python + FastAPI 的独立 Mock 服务器,用于模拟流水分析平台的 7 个核心接口。
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- ✅ **完整的接口模拟** - 实现所有 7 个核心接口
|
||||
- ✅ **文件解析延迟** - 使用 FastAPI 后台任务模拟 4 秒解析延迟
|
||||
- ✅ **错误场景触发** - 通过 `error_XXXX` 标记触发所有 8 个错误码
|
||||
- ✅ **自动 API 文档** - Swagger UI 和 ReDoc 自动生成
|
||||
- ✅ **配置驱动** - JSON 模板文件,易于修改响应数据
|
||||
- ✅ **零配置启动** - 开箱即用,无需数据库
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 启动服务
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
或使用 uvicorn(支持热重载):
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### 3. 访问 API 文档
|
||||
|
||||
- **Swagger UI**: http://localhost:8000/docs
|
||||
- **ReDoc**: http://localhost:8000/redoc
|
||||
|
||||
## 📖 使用示例
|
||||
|
||||
### 正常流程
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 1. 获取 Token
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
json={
|
||||
"projectNo": "test_project_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
token_data = response.json()
|
||||
project_id = token_data["data"]["projectId"]
|
||||
|
||||
# 2. 上传文件
|
||||
files = {"file": ("test.csv", open("test.csv", "rb"), "text/csv")}
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/remoteUploadSplitFile",
|
||||
files=files,
|
||||
data={"groupId": project_id}
|
||||
)
|
||||
log_id = response.json()["data"]["uploadLogList"][0]["logId"]
|
||||
|
||||
# 3. 轮询检查解析状态
|
||||
import time
|
||||
for i in range(10):
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/upload/getpendings",
|
||||
json={"groupId": project_id, "inprogressList": str(log_id)}
|
||||
)
|
||||
result = response.json()
|
||||
if not result["data"]["parsing"]:
|
||||
print("解析完成")
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
# 4. 获取银行流水
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/getBSByLogId",
|
||||
json={
|
||||
"groupId": project_id,
|
||||
"logId": log_id,
|
||||
"pageNow": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 错误场景测试
|
||||
|
||||
```python
|
||||
# 触发 40101 错误(appId错误)
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
json={
|
||||
"projectNo": "test_error_40101", # 包含错误标记
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
# 返回: {"code": "40101", "message": "appId错误", ...}
|
||||
```
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 环境变量
|
||||
|
||||
创建 `.env` 文件(参考 `.env.example`):
|
||||
|
||||
```bash
|
||||
# 应用配置
|
||||
APP_NAME=流水分析Mock服务
|
||||
APP_VERSION=1.0.0
|
||||
DEBUG=true
|
||||
|
||||
# 服务器配置
|
||||
HOST=0.0.0.0
|
||||
PORT=8000
|
||||
|
||||
# 模拟配置
|
||||
PARSE_DELAY_SECONDS=4
|
||||
MAX_FILE_SIZE=10485760
|
||||
```
|
||||
|
||||
### 响应模板
|
||||
|
||||
修改 `config/responses/` 下的 JSON 文件可以自定义响应数据:
|
||||
|
||||
- `token.json` - Token 响应模板
|
||||
- `upload.json` - 上传文件响应模板
|
||||
- `parse_status.json` - 解析状态响应模板
|
||||
- `bank_statement.json` - 银行流水响应模板
|
||||
|
||||
## 🐳 Docker 部署
|
||||
|
||||
### 使用 Docker
|
||||
|
||||
```bash
|
||||
# 构建镜像
|
||||
docker build -t lsfx-mock-server .
|
||||
|
||||
# 运行容器
|
||||
docker run -d -p 8000:8000 --name lsfx-mock lsfx-mock-server
|
||||
```
|
||||
|
||||
### 使用 Docker Compose
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
lsfx-mock-server/
|
||||
├── main.py # 应用入口
|
||||
├── config/
|
||||
│ ├── settings.py # 全局配置
|
||||
│ └── responses/ # 响应模板
|
||||
├── models/
|
||||
│ ├── request.py # 请求模型
|
||||
│ └── response.py # 响应模型
|
||||
├── services/
|
||||
│ ├── token_service.py # Token 管理
|
||||
│ ├── file_service.py # 文件上传和解析
|
||||
│ └── statement_service.py # 流水数据管理
|
||||
├── routers/
|
||||
│ └── api.py # API 路由
|
||||
├── utils/
|
||||
│ ├── error_simulator.py # 错误模拟
|
||||
│ └── response_builder.py # 响应构建器
|
||||
└── tests/ # 测试套件
|
||||
```
|
||||
|
||||
## 🧪 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
pytest tests/ -v
|
||||
|
||||
# 生成覆盖率报告
|
||||
pytest tests/ -v --cov=. --cov-report=html
|
||||
```
|
||||
|
||||
## 🔌 API 接口列表
|
||||
|
||||
| 接口 | 方法 | 路径 | 描述 |
|
||||
|------|------|------|------|
|
||||
| 1 | POST | `/account/common/getToken` | 获取 Token |
|
||||
| 2 | POST | `/watson/api/project/remoteUploadSplitFile` | 上传文件 |
|
||||
| 3 | POST | `/watson/api/project/getJZFileOrZjrcuFile` | 拉取行内流水 |
|
||||
| 4 | POST | `/watson/api/project/upload/getpendings` | 检查解析状态 |
|
||||
| 5 | POST | `/watson/api/project/batchDeleteUploadFile` | 删除文件 |
|
||||
| 6 | POST | `/watson/api/project/getBSByLogId` | 获取银行流水 |
|
||||
|
||||
## ⚠️ 错误码列表
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|--------|------|
|
||||
| 40101 | appId错误 |
|
||||
| 40102 | appSecretCode错误 |
|
||||
| 40104 | 可使用项目次数为0,无法创建项目 |
|
||||
| 40105 | 只读模式下无法新建项目 |
|
||||
| 40106 | 错误的分析类型,不在规定的取值范围内 |
|
||||
| 40107 | 当前系统不支持的分析类型 |
|
||||
| 40108 | 当前用户所属行社无权限 |
|
||||
| 501014 | 无行内流水文件 |
|
||||
|
||||
## 🛠️ 开发指南
|
||||
|
||||
### 添加新接口
|
||||
|
||||
1. 在 `models/request.py` 和 `models/response.py` 中添加模型
|
||||
2. 在 `services/` 中添加服务类
|
||||
3. 在 `routers/api.py` 中添加路由
|
||||
4. 在 `config/responses/` 中添加响应模板
|
||||
5. 编写测试
|
||||
|
||||
### 修改响应数据
|
||||
|
||||
直接编辑 `config/responses/` 下的 JSON 文件,重启服务即可生效。
|
||||
|
||||
## 📝 License
|
||||
|
||||
MIT
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
Binary file not shown.
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"success_response": {
|
||||
"code": "200",
|
||||
"data": {
|
||||
"bankStatementList": [
|
||||
{
|
||||
"accountId": 0,
|
||||
"accountMaskNo": "101015251071645",
|
||||
"accountingDate": "2024-02-01",
|
||||
"accountingDateId": 20240201,
|
||||
"archivingFlag": 0,
|
||||
"attachments": 0,
|
||||
"balanceAmount": 4814.82,
|
||||
"bank": "ZJRCU",
|
||||
"bankComments": "",
|
||||
"bankStatementId": 12847662,
|
||||
"bankTrxNumber": "1a10458dd5c3366d7272285812d434fc",
|
||||
"batchId": 19135,
|
||||
"cashType": "1",
|
||||
"commentsNum": 0,
|
||||
"crAmount": 0,
|
||||
"cretNo": "230902199012261247",
|
||||
"currency": "CNY",
|
||||
"customerAccountMaskNo": "597671502",
|
||||
"customerBank": "",
|
||||
"customerId": -1,
|
||||
"customerName": "小店",
|
||||
"customerReference": "",
|
||||
"downPaymentFlag": 0,
|
||||
"drAmount": 245.8,
|
||||
"exceptionType": "",
|
||||
"groupId": 16238,
|
||||
"internalFlag": 0,
|
||||
"leId": 16308,
|
||||
"leName": "张传伟",
|
||||
"overrideBsId": 0,
|
||||
"paymentMethod": "",
|
||||
"sourceCatalogId": 0,
|
||||
"split": 0,
|
||||
"subBankstatementId": 0,
|
||||
"toDoFlag": 0,
|
||||
"transAmount": 245.8,
|
||||
"transFlag": "P",
|
||||
"transTypeId": 0,
|
||||
"transformAmount": 0,
|
||||
"transformCrAmount": 0,
|
||||
"transformDrAmount": 0,
|
||||
"transfromBalanceAmount": 0,
|
||||
"trxBalance": 0,
|
||||
"trxDate": "2024-02-01 10:33:44",
|
||||
"userMemo": "财付通消费_小店"
|
||||
},
|
||||
{
|
||||
"accountId": 0,
|
||||
"accountMaskNo": "101015251071645",
|
||||
"accountingDate": "2024-02-02",
|
||||
"accountingDateId": 20240202,
|
||||
"archivingFlag": 0,
|
||||
"attachments": 0,
|
||||
"balanceAmount": 5000.00,
|
||||
"bank": "ZJRCU",
|
||||
"bankComments": "",
|
||||
"bankStatementId": 12847663,
|
||||
"bankTrxNumber": "2b20568ee6d4477e8383396923e545gd",
|
||||
"batchId": 19135,
|
||||
"cashType": "1",
|
||||
"commentsNum": 0,
|
||||
"crAmount": 185.18,
|
||||
"cretNo": "230902199012261247",
|
||||
"currency": "CNY",
|
||||
"customerAccountMaskNo": "123456789",
|
||||
"customerBank": "",
|
||||
"customerId": -1,
|
||||
"customerName": "支付宝",
|
||||
"customerReference": "",
|
||||
"downPaymentFlag": 0,
|
||||
"drAmount": 0,
|
||||
"exceptionType": "",
|
||||
"groupId": 16238,
|
||||
"internalFlag": 0,
|
||||
"leId": 16308,
|
||||
"leName": "张传伟",
|
||||
"overrideBsId": 0,
|
||||
"paymentMethod": "",
|
||||
"sourceCatalogId": 0,
|
||||
"split": 0,
|
||||
"subBankstatementId": 0,
|
||||
"toDoFlag": 0,
|
||||
"transAmount": 185.18,
|
||||
"transFlag": "R",
|
||||
"transTypeId": 0,
|
||||
"transformAmount": 0,
|
||||
"transformCrAmount": 0,
|
||||
"transformDrAmount": 0,
|
||||
"transfromBalanceAmount": 0,
|
||||
"trxBalance": 0,
|
||||
"trxDate": "2024-02-02 14:22:18",
|
||||
"userMemo": "支付宝转账_支付宝"
|
||||
}
|
||||
],
|
||||
"totalCount": 131
|
||||
},
|
||||
"status": "200",
|
||||
"successResponse": true
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"success_response": {
|
||||
"code": "200",
|
||||
"data": {
|
||||
"parsing": false,
|
||||
"pendingList": [
|
||||
{
|
||||
"accountNoList": [],
|
||||
"bankName": "ZJRCU",
|
||||
"dataTypeInfo": ["CSV", ","],
|
||||
"downloadFileName": "230902199012261247_20260201_20260201_1772096608615.csv",
|
||||
"enterpriseNameList": [],
|
||||
"filePackageId": "cde6c7cf5cab48e8892f0c1c36b2aa7d",
|
||||
"fileSize": 53101,
|
||||
"fileUploadBy": 448,
|
||||
"fileUploadByUserName": "admin@support.com",
|
||||
"fileUploadTime": "2026-02-27 09:50:18",
|
||||
"isSplit": 0,
|
||||
"leId": 16210,
|
||||
"logId": "{log_id}",
|
||||
"logMeta": "{\"lostHeader\":[],\"balanceAmount\":true}",
|
||||
"logType": "bankstatement",
|
||||
"loginLeId": 16210,
|
||||
"lostHeader": [],
|
||||
"realBankName": "ZJRCU",
|
||||
"rows": 0,
|
||||
"source": "http",
|
||||
"status": -5,
|
||||
"templateName": "ZJRCU_T251114",
|
||||
"totalRecords": 131,
|
||||
"trxDateEndId": 20240228,
|
||||
"trxDateStartId": 20240201,
|
||||
"uploadFileName": "230902199012261247_20260201_20260201_1772096608615.csv",
|
||||
"uploadStatusDesc": "data.wait.confirm.newaccount"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": "200",
|
||||
"successResponse": true
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"success_response": {
|
||||
"code": "200",
|
||||
"data": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.mock_token_{project_id}",
|
||||
"projectId": "{project_id}",
|
||||
"projectNo": "{project_no}",
|
||||
"entityName": "{entity_name}",
|
||||
"analysisType": 0
|
||||
},
|
||||
"message": "create.token.success",
|
||||
"status": "200",
|
||||
"successResponse": true
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"success_response": {
|
||||
"code": "200",
|
||||
"data": {
|
||||
"accountsOfLog": {
|
||||
"{log_id}": [
|
||||
{
|
||||
"bank": "BSX",
|
||||
"accountName": "测试账户",
|
||||
"accountNo": "6222021234567890",
|
||||
"currency": "CNY"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uploadLogList": [
|
||||
{
|
||||
"accountNoList": [],
|
||||
"bankName": "BSX",
|
||||
"dataTypeInfo": ["CSV", ","],
|
||||
"downloadFileName": "测试流水.csv",
|
||||
"enterpriseNameList": [],
|
||||
"filePackageId": "14b13103010e4d32b5406c764cfe3644",
|
||||
"fileSize": 46724,
|
||||
"fileUploadBy": 448,
|
||||
"fileUploadByUserName": "admin@support.com",
|
||||
"fileUploadTime": "{upload_time}",
|
||||
"leId": 10724,
|
||||
"logId": "{log_id}",
|
||||
"logMeta": "{\"lostHeader\":[],\"balanceAmount\":true}",
|
||||
"logType": "bankstatement",
|
||||
"loginLeId": 10724,
|
||||
"realBankName": "BSX",
|
||||
"rows": 0,
|
||||
"source": "http",
|
||||
"status": -5,
|
||||
"templateName": "BSX_T240925",
|
||||
"totalRecords": 280,
|
||||
"trxDateEndId": 20240905,
|
||||
"trxDateStartId": 20230914,
|
||||
"uploadFileName": "测试流水.csv",
|
||||
"uploadStatusDesc": "data.wait.confirm.newaccount"
|
||||
}
|
||||
],
|
||||
"uploadStatus": 1
|
||||
},
|
||||
"status": "200",
|
||||
"successResponse": true
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""全局配置类"""
|
||||
|
||||
# 应用配置
|
||||
APP_NAME: str = "流水分析Mock服务"
|
||||
APP_VERSION: str = "1.0.0"
|
||||
DEBUG: bool = True
|
||||
|
||||
# 服务器配置
|
||||
HOST: str = "0.0.0.0"
|
||||
PORT: int = 8000
|
||||
|
||||
# 模拟配置
|
||||
PARSE_DELAY_SECONDS: int = 4 # 文件解析延迟秒数
|
||||
MAX_FILE_SIZE: int = 10485760 # 10MB
|
||||
|
||||
# 测试数据配置
|
||||
INITIAL_PROJECT_ID: int = 1000
|
||||
INITIAL_LOG_ID: int = 10000
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -1,17 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
lsfx-mock-server:
|
||||
build: .
|
||||
container_name: lsfx-mock-server
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- APP_NAME=流水分析Mock服务
|
||||
- APP_VERSION=1.0.0
|
||||
- DEBUG=true
|
||||
- HOST=0.0.0.0
|
||||
- PORT=8000
|
||||
- PARSE_DELAY_SECONDS=4
|
||||
- MAX_FILE_SIZE=10485760
|
||||
restart: unless-stopped
|
||||
@@ -1,80 +0,0 @@
|
||||
"""
|
||||
流水分析Mock服务器 - 主应用入口
|
||||
|
||||
基于 FastAPI 实现的 Mock 服务器,用于模拟流水分析平台的 7 个核心接口
|
||||
"""
|
||||
from fastapi import FastAPI
|
||||
from routers import api
|
||||
from config.settings import settings
|
||||
|
||||
# 创建 FastAPI 应用实例
|
||||
app = FastAPI(
|
||||
title=settings.APP_NAME,
|
||||
description="""
|
||||
## 流水分析 Mock 服务器
|
||||
|
||||
模拟流水分析平台的 7 个核心接口,用于开发和测试。
|
||||
|
||||
### 主要功能
|
||||
|
||||
- **Token管理** - 创建项目并获取访问Token
|
||||
- **文件上传** - 上传流水文件,支持异步解析(4秒延迟)
|
||||
- **行内流水** - 拉取行内流水数据
|
||||
- **解析状态** - 轮询检查文件解析状态
|
||||
- **文件删除** - 批量删除上传的文件
|
||||
- **流水查询** - 分页获取银行流水数据
|
||||
|
||||
### 错误模拟
|
||||
|
||||
在请求参数中包含 `error_XXXX` 标记可触发对应的错误响应。
|
||||
|
||||
例如:`projectNo: "test_error_40101"` 将返回 40101 错误。
|
||||
|
||||
### 使用方式
|
||||
|
||||
1. 获取Token: POST /account/common/getToken
|
||||
2. 上传文件: POST /watson/api/project/remoteUploadSplitFile
|
||||
3. 轮询解析状态: POST /watson/api/project/upload/getpendings
|
||||
4. 获取流水: POST /watson/api/project/getBSByLogId
|
||||
""",
|
||||
version=settings.APP_VERSION,
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
)
|
||||
|
||||
# 包含 API 路由
|
||||
app.include_router(api.router, tags=["流水分析接口"])
|
||||
|
||||
|
||||
@app.get("/", summary="服务根路径")
|
||||
async def root():
|
||||
"""服务根路径,返回基本信息"""
|
||||
return {
|
||||
"service": settings.APP_NAME,
|
||||
"version": settings.APP_VERSION,
|
||||
"swagger_docs": "/docs",
|
||||
"redoc": "/redoc",
|
||||
"status": "running",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health", summary="健康检查")
|
||||
async def health_check():
|
||||
"""健康检查端点"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": settings.APP_NAME,
|
||||
"version": settings.APP_VERSION,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
# 启动服务器
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=settings.HOST,
|
||||
port=settings.PORT,
|
||||
log_level="debug" if settings.DEBUG else "info",
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
# Models package
|
||||
@@ -1,53 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class GetTokenRequest(BaseModel):
|
||||
"""获取Token请求模型"""
|
||||
projectNo: str = Field(..., description="项目编号,格式:902000_当前时间戳")
|
||||
entityName: str = Field(..., description="项目名称")
|
||||
userId: str = Field(..., description="操作人员编号,固定值")
|
||||
userName: str = Field(..., description="操作人员姓名,固定值")
|
||||
appId: str = Field("remote_app", description="应用ID,固定值")
|
||||
appSecretCode: str = Field(..., description="安全码,md5(projectNo + '_' + entityName + '_' + dXj6eHRmPv)")
|
||||
role: str = Field("VIEWER", description="角色,固定值")
|
||||
orgCode: str = Field(..., description="行社机构号,固定值")
|
||||
entityId: Optional[str] = Field(None, description="企业统信码或个人身份证号")
|
||||
xdRelatedPersons: Optional[str] = Field(None, description="信贷关联人信息")
|
||||
jzDataDateId: Optional[str] = Field("0", description="拉取指定日期推送过来的金综链流水")
|
||||
innerBSStartDateId: Optional[str] = Field("0", description="拉取行内流水开始日期")
|
||||
innerBSEndDateId: Optional[str] = Field("0", description="拉取行内流水结束日期")
|
||||
analysisType: str = Field("-1", description="分析类型,固定值")
|
||||
departmentCode: str = Field(..., description="客户经理所属营业部/分理处的机构编码")
|
||||
|
||||
|
||||
class FetchInnerFlowRequest(BaseModel):
|
||||
"""拉取行内流水请求模型"""
|
||||
groupId: int = Field(..., description="项目id")
|
||||
customerNo: str = Field(..., description="客户身份证号")
|
||||
dataChannelCode: str = Field(..., description="校验码")
|
||||
requestDateId: int = Field(..., description="发起请求的时间")
|
||||
dataStartDateId: int = Field(..., description="拉取开始日期")
|
||||
dataEndDateId: int = Field(..., description="拉取结束日期")
|
||||
uploadUserId: int = Field(..., description="柜员号")
|
||||
|
||||
|
||||
class CheckParseStatusRequest(BaseModel):
|
||||
"""检查文件解析状态请求模型"""
|
||||
groupId: int = Field(..., description="项目id")
|
||||
inprogressList: str = Field(..., description="文件id列表,逗号分隔")
|
||||
|
||||
|
||||
class GetBankStatementRequest(BaseModel):
|
||||
"""获取银行流水请求模型"""
|
||||
groupId: int = Field(..., description="项目id")
|
||||
logId: int = Field(..., description="文件id")
|
||||
pageNow: int = Field(..., description="当前页码")
|
||||
pageSize: int = Field(..., description="查询条数")
|
||||
|
||||
|
||||
class DeleteFilesRequest(BaseModel):
|
||||
"""删除文件请求模型"""
|
||||
groupId: int = Field(..., description="项目id")
|
||||
logIds: List[int] = Field(..., description="文件id数组")
|
||||
userId: int = Field(..., description="用户柜员号")
|
||||
@@ -1,187 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
|
||||
# ==================== Token相关模型 ====================
|
||||
|
||||
class TokenData(BaseModel):
|
||||
"""Token数据"""
|
||||
token: str = Field(..., description="token")
|
||||
projectId: int = Field(..., description="见知项目Id")
|
||||
projectNo: str = Field(..., description="项目编号")
|
||||
entityName: str = Field(..., description="项目名称")
|
||||
analysisType: int = Field(0, description="分析类型")
|
||||
|
||||
|
||||
class GetTokenResponse(BaseModel):
|
||||
"""获取Token响应"""
|
||||
code: str = Field("200", description="返回码")
|
||||
data: Optional[TokenData] = Field(None, description="返回数据")
|
||||
message: str = Field("create.token.success", description="返回消息")
|
||||
status: str = Field("200", description="状态")
|
||||
successResponse: bool = Field(True, description="是否成功响应")
|
||||
|
||||
|
||||
# ==================== 文件上传相关模型 ====================
|
||||
|
||||
class AccountInfo(BaseModel):
|
||||
"""账户信息"""
|
||||
bank: str = Field(..., description="银行")
|
||||
accountName: str = Field(..., description="账户名称")
|
||||
accountNo: str = Field(..., description="账号")
|
||||
currency: str = Field(..., description="币种")
|
||||
|
||||
|
||||
class UploadLogItem(BaseModel):
|
||||
"""上传日志项"""
|
||||
accountNoList: List[str] = Field(default=[], description="账号列表")
|
||||
bankName: str = Field(..., description="银行名称")
|
||||
dataTypeInfo: List[str] = Field(default=[], description="数据类型信息")
|
||||
downloadFileName: str = Field(..., description="下载文件名")
|
||||
enterpriseNameList: List[str] = Field(default=[], description="企业名称列表")
|
||||
filePackageId: str = Field(..., description="文件包ID")
|
||||
fileSize: int = Field(..., description="文件大小")
|
||||
fileUploadBy: int = Field(..., description="上传者ID")
|
||||
fileUploadByUserName: str = Field(..., description="上传者用户名")
|
||||
fileUploadTime: str = Field(..., description="上传时间")
|
||||
leId: int = Field(..., description="企业ID")
|
||||
logId: int = Field(..., description="日志ID")
|
||||
logMeta: str = Field(..., description="日志元数据")
|
||||
logType: str = Field(..., description="日志类型")
|
||||
loginLeId: int = Field(..., description="登录企业ID")
|
||||
realBankName: str = Field(..., description="真实银行名称")
|
||||
rows: int = Field(0, description="行数")
|
||||
source: str = Field(..., description="来源")
|
||||
status: int = Field(-5, description="状态值")
|
||||
templateName: str = Field(..., description="模板名称")
|
||||
totalRecords: int = Field(0, description="总记录数")
|
||||
trxDateEndId: int = Field(..., description="交易结束日期ID")
|
||||
trxDateStartId: int = Field(..., description="交易开始日期ID")
|
||||
uploadFileName: str = Field(..., description="上传文件名")
|
||||
uploadStatusDesc: str = Field(..., description="上传状态描述")
|
||||
|
||||
|
||||
class UploadFileResponse(BaseModel):
|
||||
"""上传文件响应"""
|
||||
code: str = Field("200", description="返回码")
|
||||
data: Optional[Dict[str, Any]] = Field(None, description="返回数据")
|
||||
status: str = Field("200", description="状态")
|
||||
successResponse: bool = Field(True, description="是否成功响应")
|
||||
|
||||
|
||||
# ==================== 检查解析状态相关模型 ====================
|
||||
|
||||
class PendingItem(BaseModel):
|
||||
"""待处理项"""
|
||||
accountNoList: List[str] = Field(default=[], description="账号列表")
|
||||
bankName: str = Field(..., description="银行名称")
|
||||
dataTypeInfo: List[str] = Field(default=[], description="数据类型信息")
|
||||
downloadFileName: str = Field(..., description="下载文件名")
|
||||
enterpriseNameList: List[str] = Field(default=[], description="企业名称列表")
|
||||
filePackageId: str = Field(..., description="文件包ID")
|
||||
fileSize: int = Field(..., description="文件大小")
|
||||
fileUploadBy: int = Field(..., description="上传者ID")
|
||||
fileUploadByUserName: str = Field(..., description="上传者用户名")
|
||||
fileUploadTime: str = Field(..., description="上传时间")
|
||||
isSplit: int = Field(0, description="是否分割")
|
||||
leId: int = Field(..., description="企业ID")
|
||||
logId: int = Field(..., description="日志ID")
|
||||
logMeta: str = Field(..., description="日志元数据")
|
||||
logType: str = Field(..., description="日志类型")
|
||||
loginLeId: int = Field(..., description="登录企业ID")
|
||||
lostHeader: List[str] = Field(default=[], description="丢失的头部")
|
||||
realBankName: str = Field(..., description="真实银行名称")
|
||||
rows: int = Field(0, description="行数")
|
||||
source: str = Field(..., description="来源")
|
||||
status: int = Field(-5, description="状态值")
|
||||
templateName: str = Field(..., description="模板名称")
|
||||
totalRecords: int = Field(0, description="总记录数")
|
||||
trxDateEndId: int = Field(..., description="交易结束日期ID")
|
||||
trxDateStartId: int = Field(..., description="交易开始日期ID")
|
||||
uploadFileName: str = Field(..., description="上传文件名")
|
||||
uploadStatusDesc: str = Field(..., description="上传状态描述")
|
||||
|
||||
|
||||
class CheckParseStatusResponse(BaseModel):
|
||||
"""检查解析状态响应"""
|
||||
code: str = Field("200", description="返回码")
|
||||
data: Optional[Dict[str, Any]] = Field(None, description="返回数据,包含parsing和pendingList")
|
||||
status: str = Field("200", description="状态")
|
||||
successResponse: bool = Field(True, description="是否成功响应")
|
||||
|
||||
|
||||
# ==================== 银行流水相关模型 ====================
|
||||
|
||||
class BankStatementItem(BaseModel):
|
||||
"""银行流水项"""
|
||||
accountId: int = Field(0, description="账号ID")
|
||||
accountMaskNo: str = Field(..., description="账号")
|
||||
accountingDate: str = Field(..., description="记账日期")
|
||||
accountingDateId: int = Field(..., description="记账日期ID")
|
||||
archivingFlag: int = Field(0, description="归档标志")
|
||||
attachments: int = Field(0, description="附件数")
|
||||
balanceAmount: float = Field(..., description="余额")
|
||||
bank: str = Field(..., description="银行")
|
||||
bankComments: str = Field("", description="银行注释")
|
||||
bankStatementId: int = Field(..., description="流水ID")
|
||||
bankTrxNumber: str = Field(..., description="银行交易号")
|
||||
batchId: int = Field(..., description="批次ID")
|
||||
cashType: str = Field("1", description="现金类型")
|
||||
commentsNum: int = Field(0, description="评论数")
|
||||
crAmount: float = Field(0, description="贷方金额")
|
||||
cretNo: str = Field(..., description="证件号")
|
||||
currency: str = Field("CNY", description="币种")
|
||||
customerAccountMaskNo: str = Field(..., description="客户账号")
|
||||
customerBank: str = Field("", description="客户银行")
|
||||
customerId: int = Field(-1, description="客户ID")
|
||||
customerName: str = Field(..., description="客户名称")
|
||||
customerReference: str = Field("", description="客户参考")
|
||||
downPaymentFlag: int = Field(0, description="首付标志")
|
||||
drAmount: float = Field(0, description="借方金额")
|
||||
exceptionType: str = Field("", description="异常类型")
|
||||
groupId: int = Field(0, description="项目ID")
|
||||
internalFlag: int = Field(0, description="内部标志")
|
||||
leId: int = Field(..., description="企业ID")
|
||||
leName: str = Field(..., description="企业名称")
|
||||
overrideBsId: int = Field(0, description="覆盖流水ID")
|
||||
paymentMethod: str = Field("", description="支付方式")
|
||||
sourceCatalogId: int = Field(0, description="来源目录ID")
|
||||
split: int = Field(0, description="分割")
|
||||
subBankstatementId: int = Field(0, description="子流水ID")
|
||||
toDoFlag: int = Field(0, description="待办标志")
|
||||
transAmount: float = Field(..., description="交易金额")
|
||||
transFlag: str = Field("P", description="交易标志")
|
||||
transTypeId: int = Field(0, description="交易类型ID")
|
||||
transformAmount: int = Field(0, description="转换金额")
|
||||
transformCrAmount: int = Field(0, description="转换贷方金额")
|
||||
transformDrAmount: int = Field(0, description="转换借方金额")
|
||||
transfromBalanceAmount: int = Field(0, description="转换余额")
|
||||
trxBalance: int = Field(0, description="交易余额")
|
||||
trxDate: str = Field(..., description="交易日期")
|
||||
userMemo: str = Field(..., description="用户备注")
|
||||
|
||||
|
||||
class GetBankStatementResponse(BaseModel):
|
||||
"""获取银行流水响应"""
|
||||
code: str = Field("200", description="返回码")
|
||||
data: Optional[Dict[str, Any]] = Field(None, description="返回数据,包含bankStatementList和totalCount")
|
||||
status: str = Field("200", description="状态")
|
||||
successResponse: bool = Field(True, description="是否成功响应")
|
||||
|
||||
|
||||
# ==================== 其他响应模型 ====================
|
||||
|
||||
class FetchInnerFlowResponse(BaseModel):
|
||||
"""拉取行内流水响应"""
|
||||
code: str = Field("200", description="返回码")
|
||||
data: Optional[Dict[str, Any]] = Field(None, description="返回数据")
|
||||
status: str = Field("200", description="状态")
|
||||
successResponse: bool = Field(True, description="是否成功响应")
|
||||
|
||||
|
||||
class DeleteFilesResponse(BaseModel):
|
||||
"""删除文件响应"""
|
||||
code: str = Field("200", description="返回码")
|
||||
data: Optional[Dict[str, str]] = Field(None, description="返回数据")
|
||||
status: str = Field("200", description="状态")
|
||||
successResponse: bool = Field(True, description="是否成功响应")
|
||||
@@ -1,8 +0,0 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
pydantic==2.5.0
|
||||
pydantic-settings==2.1.0
|
||||
python-multipart==0.0.6
|
||||
pytest>=7.0.0
|
||||
pytest-cov>=4.0.0
|
||||
httpx>=0.25.0
|
||||
@@ -1 +0,0 @@
|
||||
# Routers package
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,165 +0,0 @@
|
||||
from fastapi import APIRouter, BackgroundTasks, UploadFile, File, Form
|
||||
from services.token_service import TokenService
|
||||
from services.file_service import FileService
|
||||
from services.statement_service import StatementService
|
||||
from utils.error_simulator import ErrorSimulator
|
||||
from typing import List, Optional
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter()
|
||||
|
||||
# 初始化服务实例
|
||||
token_service = TokenService()
|
||||
file_service = FileService()
|
||||
statement_service = StatementService()
|
||||
|
||||
|
||||
# ==================== 接口1:获取Token ====================
|
||||
@router.post("/account/common/getToken")
|
||||
async def get_token(
|
||||
projectNo: str = Form(..., description="项目编号,格式:902000_当前时间戳"),
|
||||
entityName: str = Form(..., description="项目名称"),
|
||||
userId: str = Form(..., description="操作人员编号,固定值"),
|
||||
userName: str = Form(..., description="操作人员姓名,固定值"),
|
||||
appId: str = Form("remote_app", description="应用ID,固定值"),
|
||||
appSecretCode: str = Form(..., description="安全码"),
|
||||
role: str = Form("VIEWER", description="角色,固定值"),
|
||||
orgCode: str = Form(..., description="行社机构号,固定值"),
|
||||
entityId: Optional[str] = Form(None, description="企业统信码或个人身份证号"),
|
||||
xdRelatedPersons: Optional[str] = Form(None, description="信贷关联人信息"),
|
||||
jzDataDateId: str = Form("0", description="拉取指定日期推送过来的金综链流水"),
|
||||
innerBSStartDateId: str = Form("0", description="拉取行内流水开始日期"),
|
||||
innerBSEndDateId: str = Form("0", description="拉取行内流水结束日期"),
|
||||
analysisType: str = Form("-1", description="分析类型,固定值"),
|
||||
departmentCode: str = Form(..., description="客户经理所属营业部/分理处的机构编码"),
|
||||
):
|
||||
"""创建项目并获取访问Token
|
||||
|
||||
如果 projectNo 包含 error_XXXX 标记,将返回对应的错误响应
|
||||
"""
|
||||
# 检测错误标记
|
||||
error_code = ErrorSimulator.detect_error_marker(projectNo)
|
||||
if error_code:
|
||||
return ErrorSimulator.build_error_response(error_code)
|
||||
|
||||
# 构建请求数据字典
|
||||
request_data = {
|
||||
"projectNo": projectNo,
|
||||
"entityName": entityName,
|
||||
"userId": userId,
|
||||
"userName": userName,
|
||||
"appId": appId,
|
||||
"appSecretCode": appSecretCode,
|
||||
"role": role,
|
||||
"orgCode": orgCode,
|
||||
"entityId": entityId,
|
||||
"xdRelatedPersons": xdRelatedPersons,
|
||||
"jzDataDateId": jzDataDateId,
|
||||
"innerBSStartDateId": innerBSStartDateId,
|
||||
"innerBSEndDateId": innerBSEndDateId,
|
||||
"analysisType": analysisType,
|
||||
"departmentCode": departmentCode,
|
||||
}
|
||||
|
||||
# 正常流程
|
||||
return token_service.create_token(request_data)
|
||||
|
||||
|
||||
# ==================== 接口2:上传文件 ====================
|
||||
@router.post("/watson/api/project/remoteUploadSplitFile")
|
||||
async def upload_file(
|
||||
background_tasks: BackgroundTasks,
|
||||
groupId: int = Form(..., description="项目ID"),
|
||||
file: UploadFile = File(..., description="流水文件"),
|
||||
):
|
||||
"""上传流水文件
|
||||
|
||||
文件将立即返回,并在后台延迟4秒完成解析
|
||||
"""
|
||||
return await file_service.upload_file(groupId, file, background_tasks)
|
||||
|
||||
|
||||
# ==================== 接口3:拉取行内流水 ====================
|
||||
@router.post("/watson/api/project/getJZFileOrZjrcuFile")
|
||||
async def fetch_inner_flow(
|
||||
groupId: int = Form(..., description="项目id"),
|
||||
customerNo: str = Form(..., description="客户身份证号"),
|
||||
dataChannelCode: str = Form(..., description="校验码"),
|
||||
requestDateId: int = Form(..., description="发起请求的时间"),
|
||||
dataStartDateId: int = Form(..., description="拉取开始日期"),
|
||||
dataEndDateId: int = Form(..., description="拉取结束日期"),
|
||||
uploadUserId: int = Form(..., description="柜员号"),
|
||||
):
|
||||
"""拉取行内流水
|
||||
|
||||
如果 customerNo 包含 error_XXXX 标记,将返回对应的错误响应
|
||||
"""
|
||||
# 检测错误标记
|
||||
error_code = ErrorSimulator.detect_error_marker(customerNo)
|
||||
if error_code:
|
||||
return ErrorSimulator.build_error_response(error_code)
|
||||
|
||||
# 构建请求字典
|
||||
request_data = {
|
||||
"groupId": groupId,
|
||||
"customerNo": customerNo,
|
||||
"dataChannelCode": dataChannelCode,
|
||||
"requestDateId": requestDateId,
|
||||
"dataStartDateId": dataStartDateId,
|
||||
"dataEndDateId": dataEndDateId,
|
||||
"uploadUserId": uploadUserId,
|
||||
}
|
||||
|
||||
# 正常流程
|
||||
return file_service.fetch_inner_flow(request_data)
|
||||
|
||||
|
||||
# ==================== 接口4:检查文件解析状态 ====================
|
||||
@router.post("/watson/api/project/upload/getpendings")
|
||||
async def check_parse_status(
|
||||
groupId: int = Form(..., description="项目id"),
|
||||
inprogressList: str = Form(..., description="文件id列表,逗号分隔"),
|
||||
):
|
||||
"""检查文件解析状态
|
||||
|
||||
返回文件是否还在解析中(parsing字段)
|
||||
"""
|
||||
return file_service.check_parse_status(groupId, inprogressList)
|
||||
|
||||
|
||||
# ==================== 接口5:删除文件 ====================
|
||||
@router.post("/watson/api/project/batchDeleteUploadFile")
|
||||
async def delete_files(
|
||||
groupId: int = Form(..., description="项目id"),
|
||||
logIds: str = Form(..., description="文件id数组,逗号分隔,如: 10001,10002"),
|
||||
userId: int = Form(..., description="用户柜员号"),
|
||||
):
|
||||
"""批量删除上传的文件
|
||||
|
||||
根据logIds列表删除对应的文件记录
|
||||
"""
|
||||
# 将逗号分隔的字符串转换为整数列表
|
||||
log_id_list = [int(id.strip()) for id in logIds.split(",")]
|
||||
return file_service.delete_files(groupId, log_id_list, userId)
|
||||
|
||||
|
||||
# ==================== 接口6:获取银行流水 ====================
|
||||
@router.post("/watson/api/project/getBSByLogId")
|
||||
async def get_bank_statement(
|
||||
groupId: int = Form(..., description="项目id"),
|
||||
logId: int = Form(..., description="文件id"),
|
||||
pageNow: int = Form(..., description="当前页码"),
|
||||
pageSize: int = Form(..., description="查询条数"),
|
||||
):
|
||||
"""获取银行流水列表
|
||||
|
||||
支持分页查询(pageNow, pageSize)
|
||||
"""
|
||||
# 构建请求字典
|
||||
request_data = {
|
||||
"groupId": groupId,
|
||||
"logId": logId,
|
||||
"pageNow": pageNow,
|
||||
"pageSize": pageSize,
|
||||
}
|
||||
return statement_service.get_bank_statement(request_data)
|
||||
@@ -1 +0,0 @@
|
||||
# Services package
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,150 +0,0 @@
|
||||
from fastapi import BackgroundTasks, UploadFile
|
||||
from utils.response_builder import ResponseBuilder
|
||||
from config.settings import settings
|
||||
from typing import Dict, List, Union
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class FileService:
|
||||
"""文件上传和解析服务"""
|
||||
|
||||
def __init__(self):
|
||||
self.file_records = {} # logId -> record
|
||||
self.parsing_status = {} # logId -> is_parsing
|
||||
self.log_counter = settings.INITIAL_LOG_ID
|
||||
|
||||
async def upload_file(
|
||||
self, group_id: int, file: UploadFile, background_tasks: BackgroundTasks
|
||||
) -> Dict:
|
||||
"""上传文件并启动后台解析任务
|
||||
|
||||
Args:
|
||||
group_id: 项目ID
|
||||
file: 上传的文件
|
||||
background_tasks: FastAPI后台任务
|
||||
|
||||
Returns:
|
||||
上传响应字典
|
||||
"""
|
||||
# 生成唯一logId
|
||||
self.log_counter += 1
|
||||
log_id = self.log_counter
|
||||
|
||||
# 获取当前时间
|
||||
upload_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 立即存储文件记录(初始状态:解析中)
|
||||
self.file_records[log_id] = {
|
||||
"logId": log_id,
|
||||
"groupId": group_id,
|
||||
"status": -5,
|
||||
"uploadStatusDesc": "parsing",
|
||||
"uploadFileName": file.filename,
|
||||
"fileSize": 0, # 简化处理
|
||||
"bankName": "MOCK",
|
||||
"uploadTime": upload_time,
|
||||
}
|
||||
|
||||
# 标记为解析中
|
||||
self.parsing_status[log_id] = True
|
||||
|
||||
# 启动后台任务,延迟解析
|
||||
background_tasks.add_task(
|
||||
self._simulate_parsing, log_id, settings.PARSE_DELAY_SECONDS
|
||||
)
|
||||
|
||||
# 构建响应
|
||||
response = ResponseBuilder.build_success_response(
|
||||
"upload", log_id=log_id, upload_time=upload_time
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def _simulate_parsing(self, log_id: int, delay_seconds: int):
|
||||
"""后台任务:模拟文件解析过程
|
||||
|
||||
Args:
|
||||
log_id: 日志ID
|
||||
delay_seconds: 延迟秒数
|
||||
"""
|
||||
time.sleep(delay_seconds)
|
||||
|
||||
# 解析完成,更新状态
|
||||
if log_id in self.file_records:
|
||||
self.file_records[log_id]["uploadStatusDesc"] = (
|
||||
"data.wait.confirm.newaccount"
|
||||
)
|
||||
self.parsing_status[log_id] = False
|
||||
|
||||
def check_parse_status(self, group_id: int, inprogress_list: str) -> Dict:
|
||||
"""检查文件解析状态
|
||||
|
||||
Args:
|
||||
group_id: 项目ID
|
||||
inprogress_list: 文件ID列表(逗号分隔)
|
||||
|
||||
Returns:
|
||||
解析状态响应字典
|
||||
"""
|
||||
# 解析logId列表
|
||||
log_ids = [int(x.strip()) for x in inprogress_list.split(",") if x.strip()]
|
||||
|
||||
# 检查是否还在解析中
|
||||
is_parsing = any(
|
||||
self.parsing_status.get(log_id, False) for log_id in log_ids
|
||||
)
|
||||
|
||||
# 获取待处理列表
|
||||
pending_list = [
|
||||
self.file_records[log_id]
|
||||
for log_id in log_ids
|
||||
if log_id in self.file_records
|
||||
]
|
||||
|
||||
return {
|
||||
"code": "200",
|
||||
"data": {"parsing": is_parsing, "pendingList": pending_list},
|
||||
"status": "200",
|
||||
"successResponse": True,
|
||||
}
|
||||
|
||||
def delete_files(self, group_id: int, log_ids: List[int], user_id: int) -> Dict:
|
||||
"""删除文件
|
||||
|
||||
Args:
|
||||
group_id: 项目ID
|
||||
log_ids: 文件ID列表
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
删除响应字典
|
||||
"""
|
||||
# 删除文件记录
|
||||
for log_id in log_ids:
|
||||
self.file_records.pop(log_id, None)
|
||||
self.parsing_status.pop(log_id, None)
|
||||
|
||||
return {
|
||||
"code": "200",
|
||||
"data": {"message": "delete.files.success"},
|
||||
"status": "200",
|
||||
"successResponse": True,
|
||||
}
|
||||
|
||||
def fetch_inner_flow(self, request: Union[Dict, object]) -> Dict:
|
||||
"""拉取行内流水(模拟无数据场景)
|
||||
|
||||
Args:
|
||||
request: 拉取流水请求(可以是字典或对象)
|
||||
|
||||
Returns:
|
||||
流水响应字典
|
||||
"""
|
||||
# 模拟无行内流水文件场景
|
||||
return {
|
||||
"code": "200",
|
||||
"data": {"code": "501014", "message": "无行内流水文件"},
|
||||
"status": "200",
|
||||
"successResponse": True,
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
from utils.response_builder import ResponseBuilder
|
||||
from typing import Dict, Union
|
||||
|
||||
|
||||
class StatementService:
|
||||
"""流水数据服务"""
|
||||
|
||||
def get_bank_statement(self, request: Union[Dict, object]) -> Dict:
|
||||
"""获取银行流水列表
|
||||
|
||||
Args:
|
||||
request: 获取银行流水请求(可以是字典或对象)
|
||||
|
||||
Returns:
|
||||
银行流水响应字典
|
||||
"""
|
||||
# 支持 dict 或对象
|
||||
if isinstance(request, dict):
|
||||
page_now = request.get("pageNow", 1)
|
||||
page_size = request.get("pageSize", 10)
|
||||
else:
|
||||
page_now = request.pageNow
|
||||
page_size = request.pageSize
|
||||
|
||||
# 加载模板
|
||||
template = ResponseBuilder.load_template("bank_statement")
|
||||
statements = template["success_response"]["data"]["bankStatementList"]
|
||||
total_count = len(statements)
|
||||
|
||||
# 模拟分页
|
||||
start = (page_now - 1) * page_size
|
||||
end = start + page_size
|
||||
page_data = statements[start:end]
|
||||
|
||||
return {
|
||||
"code": "200",
|
||||
"data": {"bankStatementList": page_data, "totalCount": total_count},
|
||||
"status": "200",
|
||||
"successResponse": True,
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
from models.request import GetTokenRequest
|
||||
from utils.response_builder import ResponseBuilder
|
||||
from config.settings import settings
|
||||
from typing import Dict, Union
|
||||
|
||||
|
||||
class TokenService:
|
||||
"""Token管理服务"""
|
||||
|
||||
def __init__(self):
|
||||
self.project_counter = settings.INITIAL_PROJECT_ID
|
||||
self.tokens = {} # projectId -> token_data
|
||||
|
||||
def create_token(self, request: Union[GetTokenRequest, Dict]) -> Dict:
|
||||
"""创建Token
|
||||
|
||||
Args:
|
||||
request: 获取Token请求(可以是 GetTokenRequest 对象或字典)
|
||||
|
||||
Returns:
|
||||
Token响应字典
|
||||
"""
|
||||
# 支持 dict 或 GetTokenRequest 对象
|
||||
if isinstance(request, dict):
|
||||
project_no = request.get("projectNo")
|
||||
entity_name = request.get("entityName")
|
||||
else:
|
||||
project_no = request.projectNo
|
||||
entity_name = request.entityName
|
||||
|
||||
# 生成唯一项目ID
|
||||
self.project_counter += 1
|
||||
project_id = self.project_counter
|
||||
|
||||
# 构建响应
|
||||
response = ResponseBuilder.build_success_response(
|
||||
"token",
|
||||
project_id=project_id,
|
||||
project_no=project_no,
|
||||
entity_name=entity_name
|
||||
)
|
||||
|
||||
# 存储token信息
|
||||
self.tokens[project_id] = response.get("data")
|
||||
|
||||
return response
|
||||
|
||||
def get_project(self, project_id: int) -> Dict:
|
||||
"""获取项目信息
|
||||
|
||||
Args:
|
||||
project_id: 项目ID
|
||||
|
||||
Returns:
|
||||
项目信息字典
|
||||
"""
|
||||
return self.tokens.get(project_id)
|
||||
@@ -1 +0,0 @@
|
||||
# Tests package
|
||||
@@ -1,34 +0,0 @@
|
||||
"""
|
||||
Pytest 配置和共享 fixtures
|
||||
"""
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到 sys.path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from main import app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""创建测试客户端"""
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_token_request():
|
||||
"""示例 Token 请求 - 返回 form-data 格式的数据"""
|
||||
return {
|
||||
"projectNo": "test_project_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# Integration tests package
|
||||
@@ -1,125 +0,0 @@
|
||||
"""
|
||||
集成测试 - 完整的接口调用流程测试
|
||||
"""
|
||||
import pytest
|
||||
import time
|
||||
|
||||
|
||||
def test_complete_workflow(client):
|
||||
"""测试完整的接口调用流程"""
|
||||
# 1. 获取 Token
|
||||
response = client.post(
|
||||
"/account/common/getToken",
|
||||
data={
|
||||
"projectNo": "integration_test_001",
|
||||
"entityName": "集成测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
token_data = response.json()
|
||||
assert token_data["code"] == "200"
|
||||
project_id = token_data["data"]["projectId"]
|
||||
token = token_data["data"]["token"]
|
||||
assert token is not None
|
||||
|
||||
# 2. 上传文件(模拟)
|
||||
# 注意:在测试环境中,我们跳过实际的文件上传,直接测试其他接口
|
||||
|
||||
# 3. 检查解析状态
|
||||
response = client.post(
|
||||
"/watson/api/project/upload/getpendings",
|
||||
data={"groupId": project_id, "inprogressList": "10001"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
status_data = response.json()
|
||||
assert "parsing" in status_data["data"]
|
||||
|
||||
# 4. 获取银行流水
|
||||
response = client.post(
|
||||
"/watson/api/project/getBSByLogId",
|
||||
data={
|
||||
"groupId": project_id,
|
||||
"logId": 10001,
|
||||
"pageNow": 1,
|
||||
"pageSize": 10,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
statement_data = response.json()
|
||||
assert statement_data["code"] == "200"
|
||||
assert "bankStatementList" in statement_data["data"]
|
||||
assert "totalCount" in statement_data["data"]
|
||||
|
||||
|
||||
def test_all_error_codes(client):
|
||||
"""测试所有错误码"""
|
||||
error_codes = ["40101", "40102", "40104", "40105", "40106", "40107", "40108"]
|
||||
|
||||
for error_code in error_codes:
|
||||
response = client.post(
|
||||
"/account/common/getToken",
|
||||
data={
|
||||
"projectNo": f"test_error_{error_code}",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["code"] == error_code, f"错误码 {error_code} 未正确触发"
|
||||
assert data["successResponse"] == False
|
||||
|
||||
|
||||
def test_pagination(client):
|
||||
"""测试分页功能"""
|
||||
# 获取 Token
|
||||
response = client.post(
|
||||
"/account/common/getToken",
|
||||
data={
|
||||
"projectNo": "pagination_test",
|
||||
"entityName": "分页测试",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
},
|
||||
)
|
||||
project_id = response.json()["data"]["projectId"]
|
||||
|
||||
# 测试第一页
|
||||
response = client.post(
|
||||
"/watson/api/project/getBSByLogId",
|
||||
data={"groupId": project_id, "logId": 10001, "pageNow": 1, "pageSize": 1},
|
||||
)
|
||||
page1 = response.json()
|
||||
|
||||
# 测试第二页
|
||||
response = client.post(
|
||||
"/watson/api/project/getBSByLogId",
|
||||
data={"groupId": project_id, "logId": 10001, "pageNow": 2, "pageSize": 1},
|
||||
)
|
||||
page2 = response.json()
|
||||
|
||||
# 验证总记录数相同
|
||||
assert page1["data"]["totalCount"] == page2["data"]["totalCount"]
|
||||
|
||||
# 验证页码不同
|
||||
if page1["data"]["totalCount"] > 1:
|
||||
assert len(page1["data"]["bankStatementList"]) == 1
|
||||
assert len(page2["data"]["bankStatementList"]) >= 0
|
||||
@@ -1,50 +0,0 @@
|
||||
"""
|
||||
API 端点测试
|
||||
"""
|
||||
|
||||
|
||||
def test_root_endpoint(client):
|
||||
"""测试根路径"""
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "running"
|
||||
assert "swagger_docs" in data
|
||||
|
||||
|
||||
def test_health_check(client):
|
||||
"""测试健康检查端点"""
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "healthy"
|
||||
|
||||
|
||||
def test_get_token_success(client, sample_token_request):
|
||||
"""测试获取 Token - 成功场景"""
|
||||
response = client.post("/account/common/getToken", data=sample_token_request)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["code"] == "200"
|
||||
assert "token" in data["data"]
|
||||
assert "projectId" in data["data"]
|
||||
|
||||
|
||||
def test_get_token_error_40101(client):
|
||||
"""测试获取 Token - 错误场景 40101"""
|
||||
request_data = {
|
||||
"projectNo": "test_error_40101",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
}
|
||||
response = client.post("/account/common/getToken", data=request_data)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["code"] == "40101"
|
||||
assert data["successResponse"] == False
|
||||
@@ -1 +0,0 @@
|
||||
# Utils package
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,49 +0,0 @@
|
||||
from typing import Dict, Optional
|
||||
import re
|
||||
|
||||
|
||||
class ErrorSimulator:
|
||||
"""错误场景模拟器"""
|
||||
|
||||
# 错误码映射表
|
||||
ERROR_CODES = {
|
||||
"40101": {"code": "40101", "message": "appId错误"},
|
||||
"40102": {"code": "40102", "message": "appSecretCode错误"},
|
||||
"40104": {"code": "40104", "message": "可使用项目次数为0,无法创建项目"},
|
||||
"40105": {"code": "40105", "message": "只读模式下无法新建项目"},
|
||||
"40106": {"code": "40106", "message": "错误的分析类型,不在规定的取值范围内"},
|
||||
"40107": {"code": "40107", "message": "当前系统不支持的分析类型"},
|
||||
"40108": {"code": "40108", "message": "当前用户所属行社无权限"},
|
||||
"501014": {"code": "501014", "message": "无行内流水文件"},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def detect_error_marker(value: str) -> Optional[str]:
|
||||
"""检测字符串中的错误标记
|
||||
|
||||
规则:如果字符串包含 error_XXXX,则返回 XXXX
|
||||
例如:
|
||||
- "project_error_40101" -> "40101"
|
||||
- "test_error_501014" -> "501014"
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
|
||||
pattern = r'error_(\d+)'
|
||||
match = re.search(pattern, value)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def build_error_response(error_code: str) -> Optional[Dict]:
|
||||
"""构建错误响应"""
|
||||
if error_code in ErrorSimulator.ERROR_CODES:
|
||||
error_info = ErrorSimulator.ERROR_CODES[error_code]
|
||||
return {
|
||||
"code": error_info["code"],
|
||||
"message": error_info["message"],
|
||||
"status": error_info["code"],
|
||||
"successResponse": False
|
||||
}
|
||||
return None
|
||||
@@ -1,69 +0,0 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
import copy
|
||||
|
||||
|
||||
class ResponseBuilder:
|
||||
"""响应构建器"""
|
||||
|
||||
TEMPLATE_DIR = Path(__file__).parent.parent / "config" / "responses"
|
||||
|
||||
@staticmethod
|
||||
def load_template(template_name: str) -> Dict:
|
||||
"""加载 JSON 模板
|
||||
|
||||
Args:
|
||||
template_name: 模板名称(不含.json扩展名)
|
||||
|
||||
Returns:
|
||||
模板字典
|
||||
"""
|
||||
file_path = ResponseBuilder.TEMPLATE_DIR / f"{template_name}.json"
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
@staticmethod
|
||||
def replace_placeholders(template: Dict, **kwargs) -> Dict:
|
||||
"""递归替换占位符
|
||||
|
||||
Args:
|
||||
template: 模板字典
|
||||
**kwargs: 占位符键值对
|
||||
|
||||
Returns:
|
||||
替换后的字典
|
||||
"""
|
||||
def replace_value(value):
|
||||
if isinstance(value, str):
|
||||
result = value
|
||||
for key, val in kwargs.items():
|
||||
placeholder = f"{{{key}}}"
|
||||
if placeholder in result:
|
||||
result = result.replace(placeholder, str(val))
|
||||
return result
|
||||
elif isinstance(value, dict):
|
||||
return {k: replace_value(v) for k, v in value.items()}
|
||||
elif isinstance(value, list):
|
||||
return [replace_value(item) for item in value]
|
||||
return value
|
||||
|
||||
# 深拷贝模板,避免修改原始数据
|
||||
return replace_value(copy.deepcopy(template))
|
||||
|
||||
@staticmethod
|
||||
def build_success_response(template_name: str, **kwargs) -> Dict:
|
||||
"""构建成功响应
|
||||
|
||||
Args:
|
||||
template_name: 模板名称
|
||||
**kwargs: 占位符键值对
|
||||
|
||||
Returns:
|
||||
响应字典
|
||||
"""
|
||||
template = ResponseBuilder.load_template(template_name)
|
||||
return ResponseBuilder.replace_placeholders(
|
||||
template["success_response"],
|
||||
**kwargs
|
||||
)
|
||||
@@ -25,7 +25,7 @@ spring:
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://116.62.17.81:3306/discipline-prelim-check?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://116.62.17.81:3306/ccdi?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: Kfcx@1234
|
||||
# 从库数据源
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.List;
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Tag(name = "DPC枚举接口", description = "中介黑名单相关枚举选项接口")
|
||||
@Tag(name = "枚举接口", description = "中介黑名单相关枚举选项接口")
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/enum")
|
||||
public class CcdiEnumController {
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
package com.ruoyi.ccdi.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.domain.CcdiIntermediaryBlacklist;
|
||||
import com.ruoyi.ccdi.domain.dto.*;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryBlacklistExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryBlacklistVO;
|
||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryBlacklistService;
|
||||
import com.ruoyi.ccdi.utils.EasyExcelUtil;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDomain;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.page.TableSupport;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
@Tag(name = "中介黑名单管理")
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/intermediary")
|
||||
public class CcdiIntermediaryBlacklistController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ICcdiIntermediaryBlacklistService intermediaryService;
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表
|
||||
*/
|
||||
@Operation(summary = "查询中介黑名单列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
// 使用MyBatis Plus分页
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
Page<CcdiIntermediaryBlacklist> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
|
||||
Page<CcdiIntermediaryBlacklistVO> result = intermediaryService.selectIntermediaryPage(page, queryDTO);
|
||||
return getDataTable(result.getRecords(), result.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出中介黑名单列表
|
||||
*/
|
||||
@Operation(summary = "导出中介黑名单列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:export')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
List<CcdiIntermediaryBlacklistExcel> list = intermediaryService.selectIntermediaryListForExport(queryDTO);
|
||||
EasyExcelUtil.exportExcel(response, list, CcdiIntermediaryBlacklistExcel.class, "中介黑名单");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取中介黑名单详细信息(根据类型返回不同结构)
|
||||
*/
|
||||
@Operation(summary = "获取中介黑名单详细信息")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:query')")
|
||||
@GetMapping(value = "/{intermediaryId}")
|
||||
public AjaxResult getInfo(@PathVariable Long intermediaryId) {
|
||||
return success(intermediaryService.selectIntermediaryDetailById(intermediaryId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增中介黑名单(已废弃,请使用类型专用接口)
|
||||
*/
|
||||
@Operation(summary = "新增中介黑名单(已废弃,请使用类型专用接口)")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:add')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
@Deprecated
|
||||
public AjaxResult add(@Validated @RequestBody CcdiIntermediaryBlacklistAddDTO addDTO) {
|
||||
return toAjax(intermediaryService.insertIntermediary(addDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增个人中介黑名单
|
||||
*/
|
||||
@Operation(summary = "新增个人中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:add')")
|
||||
@Log(title = "个人中介黑名单", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/person")
|
||||
public AjaxResult addPerson(@Validated @RequestBody CcdiIntermediaryPersonAddDTO addDTO) {
|
||||
return toAjax(intermediaryService.insertPersonIntermediary(addDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增机构中介黑名单
|
||||
*/
|
||||
@Operation(summary = "新增机构中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:add')")
|
||||
@Log(title = "机构中介黑名单", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/entity")
|
||||
public AjaxResult addEntity(@Validated @RequestBody CcdiIntermediaryEntityAddDTO addDTO) {
|
||||
return toAjax(intermediaryService.insertEntityIntermediary(addDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改中介黑名单
|
||||
*/
|
||||
@Operation(summary = "修改中介黑名单(已废弃,请使用类型专用接口)")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:edit')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
@Deprecated
|
||||
public AjaxResult edit(@Validated @RequestBody CcdiIntermediaryBlacklistEditDTO editDTO) {
|
||||
return toAjax(intermediaryService.updateIntermediary(editDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改个人中介黑名单
|
||||
*/
|
||||
@Operation(summary = "修改个人中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:edit')")
|
||||
@Log(title = "个人中介黑名单", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/person")
|
||||
public AjaxResult editPerson(@Validated @RequestBody CcdiIntermediaryPersonEditDTO editDTO) {
|
||||
return toAjax(intermediaryService.updatePersonIntermediary(editDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改机构中介黑名单
|
||||
*/
|
||||
@Operation(summary = "修改机构中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:edit')")
|
||||
@Log(title = "机构中介黑名单", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/entity")
|
||||
public AjaxResult editEntity(@Validated @RequestBody CcdiIntermediaryEntityEditDTO editDTO) {
|
||||
return toAjax(intermediaryService.updateEntityIntermediary(editDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除中介黑名单
|
||||
*/
|
||||
@Operation(summary = "删除中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:remove')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{intermediaryIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] intermediaryIds) {
|
||||
return toAjax(intermediaryService.deleteIntermediaryByIds(intermediaryIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载个人中介导入模板(带字典下拉框)
|
||||
*/
|
||||
@Operation(summary = "下载个人中介导入模板")
|
||||
@PostMapping("/importPersonTemplate")
|
||||
public void importPersonTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryPersonExcel.class, "个人中介黑名单");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载机构中介导入模板(带字典下拉框)
|
||||
*/
|
||||
@Operation(summary = "下载机构中介导入模板")
|
||||
@PostMapping("/importEntityTemplate")
|
||||
public void importEntityTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEntityExcel.class, "机构中介黑名单");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入个人中介黑名单
|
||||
*/
|
||||
@Operation(summary = "导入个人中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importPersonData")
|
||||
public AjaxResult importPersonData(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
|
||||
List<CcdiIntermediaryPersonExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiIntermediaryPersonExcel.class);
|
||||
String message = intermediaryService.importPersonIntermediary(list, updateSupport);
|
||||
return success(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入机构中介黑名单
|
||||
*/
|
||||
@Operation(summary = "导入机构中介黑名单")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
|
||||
@Log(title = "中介黑名单", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importEntityData")
|
||||
public AjaxResult importEntityData(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", defaultValue = "false") boolean updateSupport) throws Exception {
|
||||
List<CcdiIntermediaryEntityExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiIntermediaryEntityExcel.class);
|
||||
String message = intermediaryService.importEntityIntermediary(list, updateSupport);
|
||||
return success(message);
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单对象 dpc_intermediary_blacklist
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryBlacklist implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 中介ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 姓名/机构名称 */
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
private String intermediaryType;
|
||||
|
||||
/** 状态 */
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
// ============================================================
|
||||
// 个人类型字段 (以 indiv_ 前缀标识,individual 缩写)
|
||||
// ============================================================
|
||||
/** 人员类型(中介、职业背债人、房产中介等) */
|
||||
private String indivType;
|
||||
|
||||
/** 人员子类型(本人、配偶等) */
|
||||
private String indivSubType;
|
||||
|
||||
/** 性别(M男 F女 O其他) */
|
||||
private String indivGender;
|
||||
|
||||
/** 证件类型 */
|
||||
private String indivCertType;
|
||||
|
||||
/** 手机号码(加密存储) */
|
||||
private String indivPhone;
|
||||
|
||||
/** 微信号 */
|
||||
private String indivWechat;
|
||||
|
||||
/** 联系地址 */
|
||||
private String indivAddress;
|
||||
|
||||
/** 所在公司 */
|
||||
private String indivCompany;
|
||||
|
||||
/** 职位/职务 */
|
||||
private String indivPosition;
|
||||
|
||||
/** 关联人员ID */
|
||||
private String indivRelatedId;
|
||||
|
||||
/** 关联关系 */
|
||||
private String indivRelation;
|
||||
|
||||
// ============================================================
|
||||
// 机构类型字段 (以 corp_ 前缀标识,corporation 缩写)
|
||||
// ============================================================
|
||||
/** 统一社会信用代码 */
|
||||
private String corpCreditCode;
|
||||
|
||||
/** 主体类型(有限责任公司、股份有限公司等) */
|
||||
private String corpType;
|
||||
|
||||
/** 企业性质(国企、民企、外企等) */
|
||||
private String corpNature;
|
||||
|
||||
/** 行业分类 */
|
||||
private String corpIndustryCategory;
|
||||
|
||||
/** 所属行业 */
|
||||
private String corpIndustry;
|
||||
|
||||
/** 成立日期 */
|
||||
private Date corpEstablishDate;
|
||||
|
||||
/** 注册地址 */
|
||||
private String corpAddress;
|
||||
|
||||
/** 法定代表人 */
|
||||
private String corpLegalRep;
|
||||
|
||||
/** 法定代表人证件类型 */
|
||||
private String corpLegalCertType;
|
||||
|
||||
/** 法定代表人证件号码 */
|
||||
private String corpLegalCertNo;
|
||||
|
||||
/** 股东1 */
|
||||
@TableField("corp_shareholder_1")
|
||||
private String corpShareholder1;
|
||||
|
||||
/** 股东2 */
|
||||
@TableField("corp_shareholder_2")
|
||||
private String corpShareholder2;
|
||||
|
||||
/** 股东3 */
|
||||
@TableField("corp_shareholder_3")
|
||||
private String corpShareholder3;
|
||||
|
||||
/** 股东4 */
|
||||
@TableField("corp_shareholder_4")
|
||||
private String corpShareholder4;
|
||||
|
||||
/** 股东5 */
|
||||
@TableField("corp_shareholder_5")
|
||||
private String corpShareholder5;
|
||||
|
||||
// ============================================================
|
||||
// 通用字段
|
||||
// ============================================================
|
||||
/** 数据来源(MANUAL手动录入 SYSTEM系统同步 IMPORT批量导入 API接口获取) */
|
||||
private String dataSource;
|
||||
|
||||
/** 创建者 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单新增 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
public class CcdiIntermediaryBlacklistAddDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 姓名/机构名称 */
|
||||
@NotBlank(message = "姓名/机构名称不能为空")
|
||||
@Size(min = 1, max = 100, message = "姓名/机构名称长度不能超过100个字符")
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
@Size(max = 50, message = "证件号长度不能超过50个字符")
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
@NotBlank(message = "中介类型不能为空")
|
||||
private String intermediaryType;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCertificateNo() {
|
||||
return certificateNo;
|
||||
}
|
||||
|
||||
public void setCertificateNo(String certificateNo) {
|
||||
this.certificateNo = certificateNo;
|
||||
}
|
||||
|
||||
public String getIntermediaryType() {
|
||||
return intermediaryType;
|
||||
}
|
||||
|
||||
public void setIntermediaryType(String intermediaryType) {
|
||||
this.intermediaryType = intermediaryType;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单编辑 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
public class CcdiIntermediaryBlacklistEditDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 中介ID */
|
||||
@NotNull(message = "中介ID不能为空")
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 姓名/机构名称 */
|
||||
@NotBlank(message = "姓名/机构名称不能为空")
|
||||
@Size(min = 1, max = 100, message = "姓名/机构名称长度不能超过100个字符")
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
@Size(max = 50, message = "证件号长度不能超过50个字符")
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
@NotBlank(message = "中介类型不能为空")
|
||||
private String intermediaryType;
|
||||
|
||||
/** 状态 */
|
||||
@NotBlank(message = "状态不能为空")
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
// ============================================================
|
||||
// 个人类型字段 (以 indiv_ 前缀标识,individual 缩写)
|
||||
// ============================================================
|
||||
/** 人员类型(中介、职业背债人、房产中介等) */
|
||||
private String indivType;
|
||||
|
||||
/** 人员子类型(本人、配偶等) */
|
||||
private String indivSubType;
|
||||
|
||||
/** 性别(M男 F女 O其他) */
|
||||
private String indivGender;
|
||||
|
||||
/** 证件类型 */
|
||||
private String indivCertType;
|
||||
|
||||
/** 手机号码(加密存储) */
|
||||
private String indivPhone;
|
||||
|
||||
/** 微信号 */
|
||||
private String indivWechat;
|
||||
|
||||
/** 联系地址 */
|
||||
private String indivAddress;
|
||||
|
||||
/** 所在公司 */
|
||||
private String indivCompany;
|
||||
|
||||
/** 职位/职务 */
|
||||
private String indivPosition;
|
||||
|
||||
/** 关联人员ID */
|
||||
private String indivRelatedId;
|
||||
|
||||
/** 关联关系 */
|
||||
private String indivRelation;
|
||||
|
||||
// ============================================================
|
||||
// 机构类型字段 (以 corp_ 前缀标识,corporation 缩写)
|
||||
// ============================================================
|
||||
/** 统一社会信用代码 */
|
||||
private String corpCreditCode;
|
||||
|
||||
/** 主体类型(有限责任公司、股份有限公司等) */
|
||||
private String corpType;
|
||||
|
||||
/** 企业性质(国企、民企、外企等) */
|
||||
private String corpNature;
|
||||
|
||||
/** 行业分类 */
|
||||
private String corpIndustryCategory;
|
||||
|
||||
/** 所属行业 */
|
||||
private String corpIndustry;
|
||||
|
||||
/** 成立日期 */
|
||||
private Date corpEstablishDate;
|
||||
|
||||
/** 注册地址 */
|
||||
private String corpAddress;
|
||||
|
||||
/** 法定代表人 */
|
||||
private String corpLegalRep;
|
||||
|
||||
/** 法定代表人证件类型 */
|
||||
private String corpLegalCertType;
|
||||
|
||||
/** 法定代表人证件号码 */
|
||||
private String corpLegalCertNo;
|
||||
|
||||
/** 股东1 */
|
||||
private String corpShareholder1;
|
||||
|
||||
/** 股东2 */
|
||||
private String corpShareholder2;
|
||||
|
||||
/** 股东3 */
|
||||
private String corpShareholder3;
|
||||
|
||||
/** 股东4 */
|
||||
private String corpShareholder4;
|
||||
|
||||
/** 股东5 */
|
||||
private String corpShareholder5;
|
||||
|
||||
public Long getIntermediaryId() {
|
||||
return intermediaryId;
|
||||
}
|
||||
|
||||
public void setIntermediaryId(Long intermediaryId) {
|
||||
this.intermediaryId = intermediaryId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCertificateNo() {
|
||||
return certificateNo;
|
||||
}
|
||||
|
||||
public void setCertificateNo(String certificateNo) {
|
||||
this.certificateNo = certificateNo;
|
||||
}
|
||||
|
||||
public String getIntermediaryType() {
|
||||
return intermediaryType;
|
||||
}
|
||||
|
||||
public void setIntermediaryType(String intermediaryType) {
|
||||
this.intermediaryType = intermediaryType;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getIndivType() {
|
||||
return indivType;
|
||||
}
|
||||
|
||||
public void setIndivType(String indivType) {
|
||||
this.indivType = indivType;
|
||||
}
|
||||
|
||||
public String getIndivSubType() {
|
||||
return indivSubType;
|
||||
}
|
||||
|
||||
public void setIndivSubType(String indivSubType) {
|
||||
this.indivSubType = indivSubType;
|
||||
}
|
||||
|
||||
public String getIndivGender() {
|
||||
return indivGender;
|
||||
}
|
||||
|
||||
public void setIndivGender(String indivGender) {
|
||||
this.indivGender = indivGender;
|
||||
}
|
||||
|
||||
public String getIndivCertType() {
|
||||
return indivCertType;
|
||||
}
|
||||
|
||||
public void setIndivCertType(String indivCertType) {
|
||||
this.indivCertType = indivCertType;
|
||||
}
|
||||
|
||||
public String getIndivPhone() {
|
||||
return indivPhone;
|
||||
}
|
||||
|
||||
public void setIndivPhone(String indivPhone) {
|
||||
this.indivPhone = indivPhone;
|
||||
}
|
||||
|
||||
public String getIndivWechat() {
|
||||
return indivWechat;
|
||||
}
|
||||
|
||||
public void setIndivWechat(String indivWechat) {
|
||||
this.indivWechat = indivWechat;
|
||||
}
|
||||
|
||||
public String getIndivAddress() {
|
||||
return indivAddress;
|
||||
}
|
||||
|
||||
public void setIndivAddress(String indivAddress) {
|
||||
this.indivAddress = indivAddress;
|
||||
}
|
||||
|
||||
public String getIndivCompany() {
|
||||
return indivCompany;
|
||||
}
|
||||
|
||||
public void setIndivCompany(String indivCompany) {
|
||||
this.indivCompany = indivCompany;
|
||||
}
|
||||
|
||||
public String getIndivPosition() {
|
||||
return indivPosition;
|
||||
}
|
||||
|
||||
public void setIndivPosition(String indivPosition) {
|
||||
this.indivPosition = indivPosition;
|
||||
}
|
||||
|
||||
public String getIndivRelatedId() {
|
||||
return indivRelatedId;
|
||||
}
|
||||
|
||||
public void setIndivRelatedId(String indivRelatedId) {
|
||||
this.indivRelatedId = indivRelatedId;
|
||||
}
|
||||
|
||||
public String getIndivRelation() {
|
||||
return indivRelation;
|
||||
}
|
||||
|
||||
public void setIndivRelation(String indivRelation) {
|
||||
this.indivRelation = indivRelation;
|
||||
}
|
||||
|
||||
public String getCorpCreditCode() {
|
||||
return corpCreditCode;
|
||||
}
|
||||
|
||||
public void setCorpCreditCode(String corpCreditCode) {
|
||||
this.corpCreditCode = corpCreditCode;
|
||||
}
|
||||
|
||||
public String getCorpType() {
|
||||
return corpType;
|
||||
}
|
||||
|
||||
public void setCorpType(String corpType) {
|
||||
this.corpType = corpType;
|
||||
}
|
||||
|
||||
public String getCorpNature() {
|
||||
return corpNature;
|
||||
}
|
||||
|
||||
public void setCorpNature(String corpNature) {
|
||||
this.corpNature = corpNature;
|
||||
}
|
||||
|
||||
public String getCorpIndustryCategory() {
|
||||
return corpIndustryCategory;
|
||||
}
|
||||
|
||||
public void setCorpIndustryCategory(String corpIndustryCategory) {
|
||||
this.corpIndustryCategory = corpIndustryCategory;
|
||||
}
|
||||
|
||||
public String getCorpIndustry() {
|
||||
return corpIndustry;
|
||||
}
|
||||
|
||||
public void setCorpIndustry(String corpIndustry) {
|
||||
this.corpIndustry = corpIndustry;
|
||||
}
|
||||
|
||||
public Date getCorpEstablishDate() {
|
||||
return corpEstablishDate;
|
||||
}
|
||||
|
||||
public void setCorpEstablishDate(Date corpEstablishDate) {
|
||||
this.corpEstablishDate = corpEstablishDate;
|
||||
}
|
||||
|
||||
public String getCorpAddress() {
|
||||
return corpAddress;
|
||||
}
|
||||
|
||||
public void setCorpAddress(String corpAddress) {
|
||||
this.corpAddress = corpAddress;
|
||||
}
|
||||
|
||||
public String getCorpLegalRep() {
|
||||
return corpLegalRep;
|
||||
}
|
||||
|
||||
public void setCorpLegalRep(String corpLegalRep) {
|
||||
this.corpLegalRep = corpLegalRep;
|
||||
}
|
||||
|
||||
public String getCorpLegalCertType() {
|
||||
return corpLegalCertType;
|
||||
}
|
||||
|
||||
public void setCorpLegalCertType(String corpLegalCertType) {
|
||||
this.corpLegalCertType = corpLegalCertType;
|
||||
}
|
||||
|
||||
public String getCorpLegalCertNo() {
|
||||
return corpLegalCertNo;
|
||||
}
|
||||
|
||||
public void setCorpLegalCertNo(String corpLegalCertNo) {
|
||||
this.corpLegalCertNo = corpLegalCertNo;
|
||||
}
|
||||
|
||||
public String getCorpShareholder1() {
|
||||
return corpShareholder1;
|
||||
}
|
||||
|
||||
public void setCorpShareholder1(String corpShareholder1) {
|
||||
this.corpShareholder1 = corpShareholder1;
|
||||
}
|
||||
|
||||
public String getCorpShareholder2() {
|
||||
return corpShareholder2;
|
||||
}
|
||||
|
||||
public void setCorpShareholder2(String corpShareholder2) {
|
||||
this.corpShareholder2 = corpShareholder2;
|
||||
}
|
||||
|
||||
public String getCorpShareholder3() {
|
||||
return corpShareholder3;
|
||||
}
|
||||
|
||||
public void setCorpShareholder3(String corpShareholder3) {
|
||||
this.corpShareholder3 = corpShareholder3;
|
||||
}
|
||||
|
||||
public String getCorpShareholder4() {
|
||||
return corpShareholder4;
|
||||
}
|
||||
|
||||
public void setCorpShareholder4(String corpShareholder4) {
|
||||
this.corpShareholder4 = corpShareholder4;
|
||||
}
|
||||
|
||||
public String getCorpShareholder5() {
|
||||
return corpShareholder5;
|
||||
}
|
||||
|
||||
public void setCorpShareholder5(String corpShareholder5) {
|
||||
this.corpShareholder5 = corpShareholder5;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单查询 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
public class CcdiIntermediaryBlacklistQueryDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 姓名/机构名称(模糊查询) */
|
||||
private String name;
|
||||
|
||||
/** 证件号(精确查询) */
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
private String intermediaryType;
|
||||
|
||||
/** 状态 */
|
||||
private String status;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCertificateNo() {
|
||||
return certificateNo;
|
||||
}
|
||||
|
||||
public void setCertificateNo(String certificateNo) {
|
||||
this.certificateNo = certificateNo;
|
||||
}
|
||||
|
||||
public String getIntermediaryType() {
|
||||
return intermediaryType;
|
||||
}
|
||||
|
||||
public void setIntermediaryType(String intermediaryType) {
|
||||
this.intermediaryType = intermediaryType;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 机构中介新增 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryEntityAddDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 机构名称 */
|
||||
@NotBlank(message = "机构名称不能为空")
|
||||
@Size(min = 1, max = 100, message = "机构名称长度不能超过100个字符")
|
||||
private String name;
|
||||
|
||||
/** 统一社会信用代码 */
|
||||
@NotBlank(message = "统一社会信用代码不能为空")
|
||||
@Size(max = 18, message = "统一社会信用代码长度不能超过18个字符")
|
||||
private String corpCreditCode;
|
||||
|
||||
/** 主体类型 */
|
||||
@Size(max = 50, message = "主体类型长度不能超过50个字符")
|
||||
private String corpType;
|
||||
|
||||
/** 企业性质 */
|
||||
@Size(max = 50, message = "企业性质长度不能超过50个字符")
|
||||
private String corpNature;
|
||||
|
||||
/** 行业分类 */
|
||||
@Size(max = 100, message = "行业分类长度不能超过100个字符")
|
||||
private String corpIndustryCategory;
|
||||
|
||||
/** 所属行业 */
|
||||
@Size(max = 100, message = "所属行业长度不能超过100个字符")
|
||||
private String corpIndustry;
|
||||
|
||||
/** 成立日期 */
|
||||
private Date corpEstablishDate;
|
||||
|
||||
/** 注册地址 */
|
||||
@Size(max = 500, message = "注册地址长度不能超过500个字符")
|
||||
private String corpAddress;
|
||||
|
||||
/** 法定代表人 */
|
||||
@Size(max = 50, message = "法定代表人长度不能超过50个字符")
|
||||
private String corpLegalRep;
|
||||
|
||||
/** 法定代表人证件类型 */
|
||||
@Size(max = 30, message = "法定代表人证件类型长度不能超过30个字符")
|
||||
private String corpLegalCertType;
|
||||
|
||||
/** 法定代表人证件号码 */
|
||||
@Size(max = 30, message = "法定代表人证件号码长度不能超过30个字符")
|
||||
private String corpLegalCertNo;
|
||||
|
||||
/** 股东1 */
|
||||
@Size(max = 30, message = "股东1长度不能超过30个字符")
|
||||
private String corpShareholder1;
|
||||
|
||||
/** 股东2 */
|
||||
@Size(max = 30, message = "股东2长度不能超过30个字符")
|
||||
private String corpShareholder2;
|
||||
|
||||
/** 股东3 */
|
||||
@Size(max = 30, message = "股东3长度不能超过30个字符")
|
||||
private String corpShareholder3;
|
||||
|
||||
/** 股东4 */
|
||||
@Size(max = 30, message = "股东4长度不能超过30个字符")
|
||||
private String corpShareholder4;
|
||||
|
||||
/** 股东5 */
|
||||
@Size(max = 30, message = "股东5长度不能超过30个字符")
|
||||
private String corpShareholder5;
|
||||
|
||||
/** 状态 */
|
||||
@NotBlank(message = "状态不能为空")
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 机构中介编辑 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryEntityEditDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 中介ID */
|
||||
@NotNull(message = "中介ID不能为空")
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 机构名称 */
|
||||
@NotBlank(message = "机构名称不能为空")
|
||||
@Size(min = 1, max = 100, message = "机构名称长度不能超过100个字符")
|
||||
private String name;
|
||||
|
||||
/** 证件号(统一社会信用代码) */
|
||||
@Size(max = 50, message = "证件号长度不能超过50个字符")
|
||||
private String certificateNo;
|
||||
|
||||
/** 统一社会信用代码 */
|
||||
private String corpCreditCode;
|
||||
|
||||
/** 主体类型(有限责任公司、股份有限公司等) */
|
||||
private String corpType;
|
||||
|
||||
/** 企业性质(国企、民企、外企等) */
|
||||
private String corpNature;
|
||||
|
||||
/** 行业分类 */
|
||||
private String corpIndustryCategory;
|
||||
|
||||
/** 所属行业 */
|
||||
private String corpIndustry;
|
||||
|
||||
/** 成立日期 */
|
||||
private Date corpEstablishDate;
|
||||
|
||||
/** 注册地址 */
|
||||
private String corpAddress;
|
||||
|
||||
/** 法定代表人 */
|
||||
private String corpLegalRep;
|
||||
|
||||
/** 法定代表人证件类型 */
|
||||
private String corpLegalCertType;
|
||||
|
||||
/** 法定代表人证件号码 */
|
||||
private String corpLegalCertNo;
|
||||
|
||||
/** 股东1 */
|
||||
private String corpShareholder1;
|
||||
|
||||
/** 股东2 */
|
||||
private String corpShareholder2;
|
||||
|
||||
/** 股东3 */
|
||||
private String corpShareholder3;
|
||||
|
||||
/** 股东4 */
|
||||
private String corpShareholder4;
|
||||
|
||||
/** 股东5 */
|
||||
private String corpShareholder5;
|
||||
|
||||
/** 状态 */
|
||||
@NotBlank(message = "状态不能为空")
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 个人中介新增 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryPersonAddDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 姓名 */
|
||||
@NotBlank(message = "姓名不能为空")
|
||||
@Size(min = 1, max = 100, message = "姓名长度不能超过100个字符")
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
@NotBlank(message = "证件号不能为空")
|
||||
@Size(max = 50, message = "证件号长度不能超过50个字符")
|
||||
private String certificateNo;
|
||||
|
||||
/** 人员类型(中介、职业背债人、房产中介等) */
|
||||
@Size(max = 30, message = "人员类型长度不能超过30个字符")
|
||||
private String indivType;
|
||||
|
||||
/** 人员子类型(本人、配偶等) */
|
||||
@Size(max = 50, message = "人员子类型长度不能超过50个字符")
|
||||
private String indivSubType;
|
||||
|
||||
/** 性别(M男 F女 O其他) */
|
||||
@Size(max = 1, message = "性别长度不能超过1个字符")
|
||||
private String indivGender;
|
||||
|
||||
/** 证件类型 */
|
||||
@Size(max = 30, message = "证件类型长度不能超过30个字符")
|
||||
private String indivCertType;
|
||||
|
||||
/** 手机号码(加密存储) */
|
||||
@Size(max = 20, message = "手机号码长度不能超过20个字符")
|
||||
private String indivPhone;
|
||||
|
||||
/** 微信号 */
|
||||
@Size(max = 50, message = "微信号长度不能超过50个字符")
|
||||
private String indivWechat;
|
||||
|
||||
/** 联系地址 */
|
||||
@Size(max = 200, message = "联系地址长度不能超过200个字符")
|
||||
private String indivAddress;
|
||||
|
||||
/** 所在公司 */
|
||||
@Size(max = 100, message = "所在公司长度不能超过100个字符")
|
||||
private String indivCompany;
|
||||
|
||||
/** 职位/职务 */
|
||||
@Size(max = 100, message = "职位长度不能超过100个字符")
|
||||
private String indivPosition;
|
||||
|
||||
/** 关联人员ID */
|
||||
@Size(max = 20, message = "关联人员ID长度不能超过20个字符")
|
||||
private String indivRelatedId;
|
||||
|
||||
/** 关联关系 */
|
||||
@Size(max = 50, message = "关联关系长度不能超过50个字符")
|
||||
private String indivRelation;
|
||||
|
||||
/** 状态 */
|
||||
@NotBlank(message = "状态不能为空")
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 个人中介编辑 DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryPersonEditDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 中介ID */
|
||||
@NotNull(message = "中介ID不能为空")
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 姓名 */
|
||||
@NotBlank(message = "姓名不能为空")
|
||||
@Size(min = 1, max = 100, message = "姓名长度不能超过100个字符")
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
@Size(max = 50, message = "证件号长度不能超过50个字符")
|
||||
private String certificateNo;
|
||||
|
||||
/** 人员类型(中介、职业背债人、房产中介等) */
|
||||
private String indivType;
|
||||
|
||||
/** 人员子类型(本人、配偶等) */
|
||||
private String indivSubType;
|
||||
|
||||
/** 性别(M男 F女 O其他) */
|
||||
private String indivGender;
|
||||
|
||||
/** 证件类型 */
|
||||
private String indivCertType;
|
||||
|
||||
/** 手机号码(加密存储) */
|
||||
private String indivPhone;
|
||||
|
||||
/** 微信号 */
|
||||
private String indivWechat;
|
||||
|
||||
/** 联系地址 */
|
||||
private String indivAddress;
|
||||
|
||||
/** 所在公司 */
|
||||
private String indivCompany;
|
||||
|
||||
/** 职位/职务 */
|
||||
private String indivPosition;
|
||||
|
||||
/** 关联人员ID */
|
||||
private String indivRelatedId;
|
||||
|
||||
/** 关联关系 */
|
||||
private String indivRelation;
|
||||
|
||||
/** 状态 */
|
||||
@NotBlank(message = "状态不能为空")
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.excel;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.ruoyi.ccdi.utils.converter.IntermediaryStatusConverter;
|
||||
import com.ruoyi.ccdi.utils.converter.IntermediaryTypeConverter;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单Excel导入导出对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryBlacklistExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 姓名/机构名称 */
|
||||
@ExcelProperty(value = "姓名/机构名称", index = 0)
|
||||
@ColumnWidth(20)
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
@ExcelProperty(value = "证件号", index = 1)
|
||||
@ColumnWidth(20)
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
@ExcelProperty(value = "中介类型", converter = IntermediaryTypeConverter.class, index = 2)
|
||||
@ColumnWidth(15)
|
||||
private String intermediaryType;
|
||||
|
||||
/** 状态 */
|
||||
@ExcelProperty(value = "状态", converter = IntermediaryStatusConverter.class, index = 3)
|
||||
@ColumnWidth(10)
|
||||
private String status;
|
||||
|
||||
/** 备注 */
|
||||
@ExcelProperty(value = "备注", index = 4)
|
||||
@ColumnWidth(30)
|
||||
private String remark;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.excel;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.ruoyi.common.annotation.DictDropdown;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 机构中介黑名单Excel导入对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryEntityExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty(value = "机构名称", index = 0)
|
||||
@ColumnWidth(25)
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
||||
@ColumnWidth(20)
|
||||
private String corpCreditCode;
|
||||
|
||||
@ExcelProperty(value = "主体类型", index = 2)
|
||||
@ColumnWidth(20)
|
||||
@DictDropdown(dictType = "dpc_entity_type")
|
||||
private String corpType;
|
||||
|
||||
@ExcelProperty(value = "企业性质", index = 3)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "dpc_enterprise_nature")
|
||||
private String corpNature;
|
||||
|
||||
@ExcelProperty(value = "行业分类", index = 4)
|
||||
@ColumnWidth(15)
|
||||
private String corpIndustryCategory;
|
||||
|
||||
@ExcelProperty(value = "所属行业", index = 5)
|
||||
@ColumnWidth(15)
|
||||
private String corpIndustry;
|
||||
|
||||
@ExcelProperty(value = "成立日期", index = 6)
|
||||
@ColumnWidth(15)
|
||||
private String corpEstablishDate;
|
||||
|
||||
@ExcelProperty(value = "注册地址", index = 7)
|
||||
@ColumnWidth(40)
|
||||
private String corpAddress;
|
||||
|
||||
@ExcelProperty(value = "法定代表人", index = 8)
|
||||
@ColumnWidth(15)
|
||||
private String corpLegalRep;
|
||||
|
||||
@ExcelProperty(value = "法定代表人证件类型", index = 9)
|
||||
@ColumnWidth(20)
|
||||
private String corpLegalCertType;
|
||||
|
||||
@ExcelProperty(value = "法定代表人证件号码", index = 10)
|
||||
@ColumnWidth(20)
|
||||
private String corpLegalCertNo;
|
||||
|
||||
@ExcelProperty(value = "股东1", index = 11)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder1;
|
||||
|
||||
@ExcelProperty(value = "股东2", index = 12)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder2;
|
||||
|
||||
@ExcelProperty(value = "股东3", index = 13)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder3;
|
||||
|
||||
@ExcelProperty(value = "股东4", index = 14)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder4;
|
||||
|
||||
@ExcelProperty(value = "股东5", index = 15)
|
||||
@ColumnWidth(15)
|
||||
private String corpShareholder5;
|
||||
|
||||
@ExcelProperty(value = "备注", index = 16)
|
||||
@ColumnWidth(30)
|
||||
private String remark;
|
||||
|
||||
// 以下字段不在 Excel 中显示,由系统自动设置
|
||||
// private String status; // 默认:正常(0)
|
||||
// private String dataSource; // 默认:批量导入(IMPORT)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.excel;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||
import com.ruoyi.common.annotation.DictDropdown;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 个人中介黑名单Excel导入对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryPersonExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ExcelProperty(value = "姓名", index = 0)
|
||||
@ColumnWidth(15)
|
||||
private String name;
|
||||
|
||||
@ExcelProperty(value = "人员类型", index = 1)
|
||||
@ColumnWidth(15)
|
||||
private String indivType;
|
||||
|
||||
@ExcelProperty(value = "人员子类型", index = 2)
|
||||
@ColumnWidth(15)
|
||||
private String indivSubType;
|
||||
|
||||
@ExcelProperty(value = "性别", index = 3)
|
||||
@ColumnWidth(10)
|
||||
@DictDropdown(dictType = "dpc_indiv_gender")
|
||||
private String indivGender;
|
||||
|
||||
@ExcelProperty(value = "证件类型", index = 4)
|
||||
@ColumnWidth(15)
|
||||
@DictDropdown(dictType = "dpc_certificate_type")
|
||||
private String indivCertType;
|
||||
|
||||
@ExcelProperty(value = "证件号码", index = 5)
|
||||
@ColumnWidth(20)
|
||||
private String certificateNo;
|
||||
|
||||
@ExcelProperty(value = "手机号码", index = 6)
|
||||
@ColumnWidth(15)
|
||||
private String indivPhone;
|
||||
|
||||
@ExcelProperty(value = "微信号", index = 7)
|
||||
@ColumnWidth(15)
|
||||
private String indivWechat;
|
||||
|
||||
@ExcelProperty(value = "联系地址", index = 8)
|
||||
@ColumnWidth(30)
|
||||
private String indivAddress;
|
||||
|
||||
@ExcelProperty(value = "所在公司", index = 9)
|
||||
@ColumnWidth(20)
|
||||
private String indivCompany;
|
||||
|
||||
@ExcelProperty(value = "职位", index = 10)
|
||||
@ColumnWidth(15)
|
||||
private String indivPosition;
|
||||
|
||||
@ExcelProperty(value = "关联人员ID", index = 11)
|
||||
@ColumnWidth(15)
|
||||
private String indivRelatedId;
|
||||
|
||||
@ExcelProperty(value = "关联关系", index = 12)
|
||||
@ColumnWidth(15)
|
||||
private String indivRelation;
|
||||
|
||||
@ExcelProperty(value = "备注", index = 13)
|
||||
@ColumnWidth(30)
|
||||
private String remark;
|
||||
|
||||
// 以下字段不在 Excel 中显示,由系统自动设置
|
||||
// private String status; // 默认:正常(0)
|
||||
// private String dataSource; // 默认:批量导入(IMPORT)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单视图对象 VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryBlacklistVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 中介ID */
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 姓名/机构名称 */
|
||||
private String name;
|
||||
|
||||
/** 证件号 */
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
private String intermediaryType;
|
||||
|
||||
/** 中介类型名称(用于前端显示) */
|
||||
private String intermediaryTypeName;
|
||||
|
||||
/** 状态 */
|
||||
private String status;
|
||||
|
||||
/** 状态名称(用于前端显示) */
|
||||
private String statusName;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 机构中介黑名单详情 VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryEntityDetailVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// ============================================================
|
||||
// 核心字段
|
||||
// ============================================================
|
||||
/** 中介ID */
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 机构名称 */
|
||||
private String name;
|
||||
|
||||
/** 证件号码 */
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
private String intermediaryType;
|
||||
|
||||
/** 中介类型名称 */
|
||||
private String intermediaryTypeName;
|
||||
|
||||
/** 状态 */
|
||||
private String status;
|
||||
|
||||
/** 状态名称 */
|
||||
private String statusName;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 数据来源 */
|
||||
private String dataSource;
|
||||
|
||||
/** 数据来源名称 */
|
||||
private String dataSourceName;
|
||||
|
||||
// ============================================================
|
||||
// 机构专属字段
|
||||
// ============================================================
|
||||
/** 统一社会信用代码 */
|
||||
private String corpCreditCode;
|
||||
|
||||
/** 主体类型 */
|
||||
private String corpType;
|
||||
|
||||
/** 主体类型名称 */
|
||||
private String corpTypeName;
|
||||
|
||||
/** 企业性质 */
|
||||
private String corpNature;
|
||||
|
||||
/** 企业性质名称 */
|
||||
private String corpNatureName;
|
||||
|
||||
/** 行业分类 */
|
||||
private String corpIndustryCategory;
|
||||
|
||||
/** 所属行业 */
|
||||
private String corpIndustry;
|
||||
|
||||
/** 成立日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date corpEstablishDate;
|
||||
|
||||
/** 注册地址 */
|
||||
private String corpAddress;
|
||||
|
||||
/** 法定代表人 */
|
||||
private String corpLegalRep;
|
||||
|
||||
/** 法定代表人证件类型 */
|
||||
private String corpLegalCertType;
|
||||
|
||||
/** 法定代表人证件号码 */
|
||||
private String corpLegalCertNo;
|
||||
|
||||
/** 股东1 */
|
||||
private String corpShareholder1;
|
||||
|
||||
/** 股东2 */
|
||||
private String corpShareholder2;
|
||||
|
||||
/** 股东3 */
|
||||
private String corpShareholder3;
|
||||
|
||||
/** 股东4 */
|
||||
private String corpShareholder4;
|
||||
|
||||
/** 股东5 */
|
||||
private String corpShareholder5;
|
||||
|
||||
// ============================================================
|
||||
// 审计字段
|
||||
// ============================================================
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package com.ruoyi.ccdi.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 个人中介黑名单详情 VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Data
|
||||
public class CcdiIntermediaryPersonDetailVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// ============================================================
|
||||
// 核心字段
|
||||
// ============================================================
|
||||
/** 中介ID */
|
||||
private Long intermediaryId;
|
||||
|
||||
/** 姓名 */
|
||||
private String name;
|
||||
|
||||
/** 证件号码 */
|
||||
private String certificateNo;
|
||||
|
||||
/** 中介类型 */
|
||||
private String intermediaryType;
|
||||
|
||||
/** 中介类型名称 */
|
||||
private String intermediaryTypeName;
|
||||
|
||||
/** 状态 */
|
||||
private String status;
|
||||
|
||||
/** 状态名称 */
|
||||
private String statusName;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 数据来源 */
|
||||
private String dataSource;
|
||||
|
||||
/** 数据来源名称 */
|
||||
private String dataSourceName;
|
||||
|
||||
// ============================================================
|
||||
// 个人专属字段
|
||||
// ============================================================
|
||||
/** 人员类型 */
|
||||
private String indivType;
|
||||
|
||||
/** 人员子类型 */
|
||||
private String indivSubType;
|
||||
|
||||
/** 性别 */
|
||||
private String indivGender;
|
||||
|
||||
/** 性别名称 */
|
||||
private String indivGenderName;
|
||||
|
||||
/** 证件类型 */
|
||||
private String indivCertType;
|
||||
|
||||
/** 证件类型名称 */
|
||||
private String indivCertTypeName;
|
||||
|
||||
/** 手机号码 */
|
||||
private String indivPhone;
|
||||
|
||||
/** 微信号 */
|
||||
private String indivWechat;
|
||||
|
||||
/** 联系地址 */
|
||||
private String indivAddress;
|
||||
|
||||
/** 所在公司 */
|
||||
private String indivCompany;
|
||||
|
||||
/** 职位/职务 */
|
||||
private String indivPosition;
|
||||
|
||||
/** 关联人员ID */
|
||||
private String indivRelatedId;
|
||||
|
||||
/** 关联关系 */
|
||||
private String indivRelation;
|
||||
|
||||
// ============================================================
|
||||
// 审计字段
|
||||
// ============================================================
|
||||
/** 创建者 */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/** 更新者 */
|
||||
private String updateBy;
|
||||
|
||||
/** 更新时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.ruoyi.ccdi.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.ccdi.domain.CcdiIntermediaryBlacklist;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单 数据层
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
public interface CcdiIntermediaryBlacklistMapper extends BaseMapper<CcdiIntermediaryBlacklist> {
|
||||
|
||||
/**
|
||||
* 批量插入中介黑名单数据
|
||||
*
|
||||
* @param list 中介黑名单列表
|
||||
* @return 插入行数
|
||||
*/
|
||||
int batchInsert(@Param("list") List<CcdiIntermediaryBlacklist> list);
|
||||
|
||||
/**
|
||||
* 批量更新中介黑名单数据
|
||||
*
|
||||
* @param list 中介黑名单列表
|
||||
* @return 更新行数
|
||||
*/
|
||||
int batchUpdate(@Param("list") List<CcdiIntermediaryBlacklist> list);
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package com.ruoyi.ccdi.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.domain.CcdiIntermediaryBlacklist;
|
||||
import com.ruoyi.ccdi.domain.dto.*;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryBlacklistExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryBlacklistVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单 服务层
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
public interface ICcdiIntermediaryBlacklistService {
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介黑名单集合
|
||||
*/
|
||||
List<CcdiIntermediaryBlacklistVO> selectIntermediaryList(CcdiIntermediaryBlacklistQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 分页查询中介黑名单列表
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介黑名单VO分页结果
|
||||
*/
|
||||
Page<CcdiIntermediaryBlacklistVO> selectIntermediaryPage(Page<CcdiIntermediaryBlacklist> page, CcdiIntermediaryBlacklistQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表(用于导出)
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介黑名单Excel实体集合
|
||||
*/
|
||||
List<CcdiIntermediaryBlacklistExcel> selectIntermediaryListForExport(CcdiIntermediaryBlacklistQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 查询中介黑名单详细
|
||||
*
|
||||
* @param intermediaryId 中介ID
|
||||
* @return 中介黑名单VO
|
||||
*/
|
||||
CcdiIntermediaryBlacklistVO selectIntermediaryById(Long intermediaryId);
|
||||
|
||||
/**
|
||||
* 新增中介黑名单(通用接口,不推荐使用)
|
||||
*
|
||||
* @param addDTO 新增DTO
|
||||
* @return 结果
|
||||
* @deprecated 请使用 insertPersonIntermediary 或 insertEntityIntermediary 代替
|
||||
*/
|
||||
@Deprecated
|
||||
int insertIntermediary(CcdiIntermediaryBlacklistAddDTO addDTO);
|
||||
|
||||
/**
|
||||
* 新增个人中介黑名单
|
||||
*
|
||||
* @param addDTO 个人中介新增DTO
|
||||
* @return 结果
|
||||
*/
|
||||
int insertPersonIntermediary(CcdiIntermediaryPersonAddDTO addDTO);
|
||||
|
||||
/**
|
||||
* 新增机构中介黑名单
|
||||
*
|
||||
* @param addDTO 机构中介新增DTO
|
||||
* @return 结果
|
||||
*/
|
||||
int insertEntityIntermediary(CcdiIntermediaryEntityAddDTO addDTO);
|
||||
|
||||
/**
|
||||
* 修改中介黑名单(通用接口,不推荐使用)
|
||||
*
|
||||
* @param editDTO 编辑DTO
|
||||
* @return 结果
|
||||
* @deprecated 请使用 updatePersonIntermediary 或 updateEntityIntermediary 代替
|
||||
*/
|
||||
@Deprecated
|
||||
int updateIntermediary(CcdiIntermediaryBlacklistEditDTO editDTO);
|
||||
|
||||
/**
|
||||
* 修改个人中介黑名单
|
||||
*
|
||||
* @param editDTO 个人中介编辑DTO
|
||||
* @return 结果
|
||||
*/
|
||||
int updatePersonIntermediary(CcdiIntermediaryPersonEditDTO editDTO);
|
||||
|
||||
/**
|
||||
* 修改机构中介黑名单
|
||||
*
|
||||
* @param editDTO 机构中介编辑DTO
|
||||
* @return 结果
|
||||
*/
|
||||
int updateEntityIntermediary(CcdiIntermediaryEntityEditDTO editDTO);
|
||||
|
||||
/**
|
||||
* 批量删除中介黑名单
|
||||
*
|
||||
* @param intermediaryIds 需要删除的中介ID
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteIntermediaryByIds(Long[] intermediaryIds);
|
||||
|
||||
/**
|
||||
* 导入中介黑名单数据
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @return 结果
|
||||
*/
|
||||
String importIntermediary(List<CcdiIntermediaryBlacklistExcel> excelList, Boolean isUpdateSupport);
|
||||
|
||||
/**
|
||||
* 根据中介类型获取详情(返回不同类型)
|
||||
*
|
||||
* @param intermediaryId 中介ID
|
||||
* @return 个人返回 CcdiIntermediaryPersonDetailVO,机构返回 CcdiIntermediaryEntityDetailVO
|
||||
*/
|
||||
Object selectIntermediaryDetailById(Long intermediaryId);
|
||||
|
||||
/**
|
||||
* 导入个人中介数据
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @return 结果
|
||||
*/
|
||||
String importPersonIntermediary(List<CcdiIntermediaryPersonExcel> excelList, Boolean isUpdateSupport);
|
||||
|
||||
/**
|
||||
* 导入机构中介数据
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @return 结果
|
||||
*/
|
||||
String importEntityIntermediary(List<CcdiIntermediaryEntityExcel> excelList, Boolean isUpdateSupport);
|
||||
}
|
||||
@@ -1,741 +0,0 @@
|
||||
package com.ruoyi.ccdi.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.domain.CcdiIntermediaryBlacklist;
|
||||
import com.ruoyi.ccdi.domain.dto.*;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryBlacklistExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel;
|
||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryBlacklistVO;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryEntityDetailVO;
|
||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryPersonDetailVO;
|
||||
import com.ruoyi.ccdi.enums.DataSource;
|
||||
import com.ruoyi.ccdi.enums.Gender;
|
||||
import com.ruoyi.ccdi.enums.IntermediaryStatus;
|
||||
import com.ruoyi.ccdi.enums.IntermediaryType;
|
||||
import com.ruoyi.ccdi.mapper.CcdiIntermediaryBlacklistMapper;
|
||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryBlacklistService;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 中介人员黑名单 服务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-01-27
|
||||
*/
|
||||
@Service
|
||||
public class CcdiIntermediaryBlacklistServiceImpl implements ICcdiIntermediaryBlacklistService {
|
||||
|
||||
@Resource
|
||||
private CcdiIntermediaryBlacklistMapper intermediaryMapper;
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介黑名单集合
|
||||
*/
|
||||
@Override
|
||||
public List<CcdiIntermediaryBlacklistVO> selectIntermediaryList(CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
LambdaQueryWrapper<CcdiIntermediaryBlacklist> wrapper = buildQueryWrapper(queryDTO);
|
||||
List<CcdiIntermediaryBlacklist> list = intermediaryMapper.selectList(wrapper);
|
||||
return list.stream().map(this::convertToVO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询中介黑名单列表
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介黑名单VO分页结果
|
||||
*/
|
||||
@Override
|
||||
public Page<CcdiIntermediaryBlacklistVO> selectIntermediaryPage(Page<CcdiIntermediaryBlacklist> page, CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
LambdaQueryWrapper<CcdiIntermediaryBlacklist> wrapper = buildQueryWrapper(queryDTO);
|
||||
Page<CcdiIntermediaryBlacklist> resultPage = intermediaryMapper.selectPage(page, wrapper);
|
||||
|
||||
// 转换为VO
|
||||
Page<CcdiIntermediaryBlacklistVO> voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
|
||||
voPage.setRecords(resultPage.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()));
|
||||
voPage.setPages(resultPage.getPages());
|
||||
|
||||
return voPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询中介黑名单列表(用于导出)
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 中介黑名单Excel实体集合
|
||||
*/
|
||||
@Override
|
||||
public List<CcdiIntermediaryBlacklistExcel> selectIntermediaryListForExport(CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
LambdaQueryWrapper<CcdiIntermediaryBlacklist> wrapper = buildQueryWrapper(queryDTO);
|
||||
List<CcdiIntermediaryBlacklist> list = intermediaryMapper.selectList(wrapper);
|
||||
return list.stream().map(entity -> {
|
||||
CcdiIntermediaryBlacklistExcel excel = new CcdiIntermediaryBlacklistExcel();
|
||||
BeanUtils.copyProperties(entity, excel);
|
||||
return excel;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询中介黑名单详细
|
||||
*
|
||||
* @param intermediaryId 中介ID
|
||||
* @return 中介黑名单VO
|
||||
*/
|
||||
@Override
|
||||
public CcdiIntermediaryBlacklistVO selectIntermediaryById(Long intermediaryId) {
|
||||
CcdiIntermediaryBlacklist intermediary = intermediaryMapper.selectById(intermediaryId);
|
||||
return convertToVO(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增中介黑名单
|
||||
*
|
||||
* @param addDTO 新增DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public int insertIntermediary(CcdiIntermediaryBlacklistAddDTO addDTO) {
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(addDTO, intermediary);
|
||||
// 手动新增时,数据来源设置为 MANUAL
|
||||
intermediary.setDataSource("MANUAL");
|
||||
// 默认状态设置为正常
|
||||
intermediary.setStatus("0");
|
||||
return intermediaryMapper.insert(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增个人中介黑名单
|
||||
*
|
||||
* @param addDTO 个人中介新增DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertPersonIntermediary(CcdiIntermediaryPersonAddDTO addDTO) {
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(addDTO, intermediary);
|
||||
// 设置中介类型为个人
|
||||
intermediary.setIntermediaryType("1");
|
||||
// 手动新增时,数据来源设置为 MANUAL
|
||||
intermediary.setDataSource("MANUAL");
|
||||
return intermediaryMapper.insert(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增机构中介黑名单
|
||||
*
|
||||
* @param addDTO 机构中介新增DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertEntityIntermediary(CcdiIntermediaryEntityAddDTO addDTO) {
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(addDTO, intermediary);
|
||||
// 设置中介类型为机构
|
||||
intermediary.setIntermediaryType("2");
|
||||
// 证件号使用统一社会信用代码
|
||||
intermediary.setCertificateNo(addDTO.getCorpCreditCode());
|
||||
// 手动新增时,数据来源设置为 MANUAL
|
||||
intermediary.setDataSource("MANUAL");
|
||||
return intermediaryMapper.insert(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改中介黑名单
|
||||
*
|
||||
* @param editDTO 编辑DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public int updateIntermediary(CcdiIntermediaryBlacklistEditDTO editDTO) {
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(editDTO, intermediary);
|
||||
return intermediaryMapper.updateById(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改个人中介黑名单
|
||||
*
|
||||
* @param editDTO 个人中介编辑DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updatePersonIntermediary(CcdiIntermediaryPersonEditDTO editDTO) {
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(editDTO, intermediary);
|
||||
// 设置中介类型为个人
|
||||
intermediary.setIntermediaryType("1");
|
||||
// 清空机构专属字段
|
||||
clearEntityFields(intermediary);
|
||||
return intermediaryMapper.updateById(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改机构中介黑名单
|
||||
*
|
||||
* @param editDTO 机构中介编辑DTO
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateEntityIntermediary(CcdiIntermediaryEntityEditDTO editDTO) {
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(editDTO, intermediary);
|
||||
// 设置中介类型为机构
|
||||
intermediary.setIntermediaryType("2");
|
||||
// 清空个人专属字段
|
||||
clearPersonFields(intermediary);
|
||||
return intermediaryMapper.updateById(intermediary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空个人专属字段
|
||||
*/
|
||||
private void clearPersonFields(CcdiIntermediaryBlacklist intermediary) {
|
||||
intermediary.setIndivType(null);
|
||||
intermediary.setIndivSubType(null);
|
||||
intermediary.setIndivGender(null);
|
||||
intermediary.setIndivCertType(null);
|
||||
intermediary.setIndivPhone(null);
|
||||
intermediary.setIndivWechat(null);
|
||||
intermediary.setIndivAddress(null);
|
||||
intermediary.setIndivCompany(null);
|
||||
intermediary.setIndivPosition(null);
|
||||
intermediary.setIndivRelatedId(null);
|
||||
intermediary.setIndivRelation(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空机构专属字段
|
||||
*/
|
||||
private void clearEntityFields(CcdiIntermediaryBlacklist intermediary) {
|
||||
intermediary.setCorpCreditCode(null);
|
||||
intermediary.setCorpType(null);
|
||||
intermediary.setCorpNature(null);
|
||||
intermediary.setCorpIndustryCategory(null);
|
||||
intermediary.setCorpIndustry(null);
|
||||
intermediary.setCorpEstablishDate(null);
|
||||
intermediary.setCorpAddress(null);
|
||||
intermediary.setCorpLegalRep(null);
|
||||
intermediary.setCorpLegalCertType(null);
|
||||
intermediary.setCorpLegalCertNo(null);
|
||||
intermediary.setCorpShareholder1(null);
|
||||
intermediary.setCorpShareholder2(null);
|
||||
intermediary.setCorpShareholder3(null);
|
||||
intermediary.setCorpShareholder4(null);
|
||||
intermediary.setCorpShareholder5(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除中介黑名单
|
||||
*
|
||||
* @param intermediaryIds 需要删除的中介ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteIntermediaryByIds(Long[] intermediaryIds) {
|
||||
return intermediaryMapper.deleteBatchIds(List.of(intermediaryIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入中介黑名单数据
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public String importIntermediary(List<CcdiIntermediaryBlacklistExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (excelList == null || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
int successNum = 0;
|
||||
int failureNum = 0;
|
||||
StringBuilder successMsg = new StringBuilder();
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
|
||||
for (CcdiIntermediaryBlacklistExcel excel : excelList) {
|
||||
try {
|
||||
// 转换为AddDTO
|
||||
CcdiIntermediaryBlacklistAddDTO addDTO = new CcdiIntermediaryBlacklistAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
|
||||
// 验证数据
|
||||
validateIntermediaryData(addDTO);
|
||||
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
BeanUtils.copyProperties(addDTO, intermediary);
|
||||
|
||||
intermediaryMapper.insert(intermediary);
|
||||
successNum++;
|
||||
successMsg.append("<br/>").append(successNum).append("、").append(addDTO.getName()).append(" 导入成功");
|
||||
} catch (Exception e) {
|
||||
failureNum++;
|
||||
failureMsg.append("<br/>").append(failureNum).append("、").append(excel.getName()).append(" 导入失败:");
|
||||
failureMsg.append(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
} else {
|
||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条");
|
||||
return successMsg.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据中介类型获取详情(返回不同类型)
|
||||
*
|
||||
* @param intermediaryId 中介ID
|
||||
* @return 个人返回 CcdiIntermediaryPersonDetailVO,机构返回 CcdiIntermediaryEntityDetailVO
|
||||
*/
|
||||
@Override
|
||||
public Object selectIntermediaryDetailById(Long intermediaryId) {
|
||||
CcdiIntermediaryBlacklist intermediary = intermediaryMapper.selectById(intermediaryId);
|
||||
if (intermediary == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据中介类型返回不同的 VO
|
||||
if ("1".equals(intermediary.getIntermediaryType())) {
|
||||
// 个人类型
|
||||
return convertToPersonDetailVO(intermediary);
|
||||
} else {
|
||||
// 机构类型
|
||||
return convertToEntityDetailVO(intermediary);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入个人中介数据(批量插入优化版)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public String importPersonIntermediary(List<CcdiIntermediaryPersonExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (excelList == null || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 批量处理:先验证所有数据
|
||||
List<CcdiIntermediaryBlacklist> toInsertList = new ArrayList<>();
|
||||
List<CcdiIntermediaryBlacklist> toUpdateList = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
|
||||
// 批量查询已存在的记录(用于唯一性校验或更新支持)
|
||||
Set<String> existingCertNos = new HashSet<>();
|
||||
Map<String, Long> certNoToIdMap = new HashMap<>();
|
||||
for (CcdiIntermediaryPersonExcel excel : excelList) {
|
||||
if (StringUtils.isNotEmpty(excel.getCertificateNo())) {
|
||||
existingCertNos.add(excel.getCertificateNo());
|
||||
}
|
||||
}
|
||||
if (!existingCertNos.isEmpty()) {
|
||||
LambdaQueryWrapper<CcdiIntermediaryBlacklist> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CcdiIntermediaryBlacklist::getIntermediaryType, "1")
|
||||
.in(CcdiIntermediaryBlacklist::getCertificateNo, existingCertNos)
|
||||
.select(CcdiIntermediaryBlacklist::getIntermediaryId, CcdiIntermediaryBlacklist::getCertificateNo);
|
||||
List<CcdiIntermediaryBlacklist> existingList = intermediaryMapper.selectList(wrapper);
|
||||
for (CcdiIntermediaryBlacklist existing : existingList) {
|
||||
certNoToIdMap.put(existing.getCertificateNo(), existing.getIntermediaryId());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是更新模式,先进行唯一性校验
|
||||
if (!isUpdateSupport) {
|
||||
for (CcdiIntermediaryPersonExcel excel : excelList) {
|
||||
if (StringUtils.isNotEmpty(excel.getCertificateNo()) && certNoToIdMap.containsKey(excel.getCertificateNo())) {
|
||||
throw new RuntimeException("证件号 " + excel.getCertificateNo() + " 已存在,请勿重复导入");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理每条数据
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiIntermediaryPersonExcel excel = excelList.get(i);
|
||||
try {
|
||||
// 验证数据
|
||||
validatePersonIntermediaryData(excel);
|
||||
|
||||
// 转换为实体
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
intermediary.setName(excel.getName());
|
||||
intermediary.setCertificateNo(excel.getCertificateNo());
|
||||
intermediary.setIntermediaryType("1");
|
||||
intermediary.setStatus("0");
|
||||
intermediary.setDataSource("IMPORT");
|
||||
intermediary.setRemark(excel.getRemark());
|
||||
|
||||
// 个人专属字段
|
||||
intermediary.setIndivType(excel.getIndivType());
|
||||
intermediary.setIndivSubType(excel.getIndivSubType());
|
||||
intermediary.setIndivGender(excel.getIndivGender());
|
||||
intermediary.setIndivCertType(StringUtils.isNotEmpty(excel.getIndivCertType()) ? excel.getIndivCertType() : "身份证");
|
||||
intermediary.setIndivPhone(excel.getIndivPhone());
|
||||
intermediary.setIndivWechat(excel.getIndivWechat());
|
||||
intermediary.setIndivAddress(excel.getIndivAddress());
|
||||
intermediary.setIndivCompany(excel.getIndivCompany());
|
||||
intermediary.setIndivPosition(excel.getIndivPosition());
|
||||
intermediary.setIndivRelatedId(excel.getIndivRelatedId());
|
||||
intermediary.setIndivRelation(excel.getIndivRelation());
|
||||
|
||||
// 检查是否需要更新
|
||||
if (isUpdateSupport && StringUtils.isNotEmpty(excel.getCertificateNo()) && certNoToIdMap.containsKey(excel.getCertificateNo())) {
|
||||
intermediary.setIntermediaryId(certNoToIdMap.get(excel.getCertificateNo()));
|
||||
toUpdateList.add(intermediary);
|
||||
} else {
|
||||
toInsertList.add(intermediary);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errorMessages.add("第" + (i + 1) + "行导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 批量执行数据库操作
|
||||
int successNum = 0;
|
||||
int failureNum = errorMessages.size();
|
||||
|
||||
// 批量插入
|
||||
if (!toInsertList.isEmpty()) {
|
||||
intermediaryMapper.batchInsert(toInsertList);
|
||||
successNum += toInsertList.size();
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
if (!toUpdateList.isEmpty()) {
|
||||
intermediaryMapper.batchUpdate(toUpdateList);
|
||||
successNum += toUpdateList.size();
|
||||
}
|
||||
|
||||
// 构建失败消息
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
for (String error : errorMessages) {
|
||||
failureMsg.append("<br/>").append(error);
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
} else {
|
||||
return "恭喜您,数据已全部导入成功!共 " + successNum + " 条";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入机构中介数据(批量插入优化版)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public String importEntityIntermediary(List<CcdiIntermediaryEntityExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (excelList == null || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 批量处理:先验证所有数据
|
||||
List<CcdiIntermediaryBlacklist> toInsertList = new ArrayList<>();
|
||||
List<CcdiIntermediaryBlacklist> toUpdateList = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
|
||||
// 批量查询已存在的记录(用于唯一性校验或更新支持)
|
||||
Set<String> existingCreditCodes = new HashSet<>();
|
||||
Map<String, Long> creditCodeToIdMap = new HashMap<>();
|
||||
for (CcdiIntermediaryEntityExcel excel : excelList) {
|
||||
if (StringUtils.isNotEmpty(excel.getCorpCreditCode())) {
|
||||
existingCreditCodes.add(excel.getCorpCreditCode());
|
||||
}
|
||||
}
|
||||
if (!existingCreditCodes.isEmpty()) {
|
||||
LambdaQueryWrapper<CcdiIntermediaryBlacklist> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CcdiIntermediaryBlacklist::getIntermediaryType, "2")
|
||||
.in(CcdiIntermediaryBlacklist::getCorpCreditCode, existingCreditCodes)
|
||||
.select(CcdiIntermediaryBlacklist::getIntermediaryId, CcdiIntermediaryBlacklist::getCorpCreditCode);
|
||||
List<CcdiIntermediaryBlacklist> existingList = intermediaryMapper.selectList(wrapper);
|
||||
for (CcdiIntermediaryBlacklist existing : existingList) {
|
||||
creditCodeToIdMap.put(existing.getCorpCreditCode(), existing.getIntermediaryId());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是更新模式,先进行唯一性校验
|
||||
if (!isUpdateSupport) {
|
||||
for (CcdiIntermediaryEntityExcel excel : excelList) {
|
||||
if (StringUtils.isNotEmpty(excel.getCorpCreditCode()) && creditCodeToIdMap.containsKey(excel.getCorpCreditCode())) {
|
||||
throw new RuntimeException("统一社会信用代码 " + excel.getCorpCreditCode() + " 已存在,请勿重复导入");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
// 处理每条数据
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiIntermediaryEntityExcel excel = excelList.get(i);
|
||||
try {
|
||||
// 验证数据
|
||||
validateEntityIntermediaryData(excel);
|
||||
|
||||
// 转换为实体
|
||||
CcdiIntermediaryBlacklist intermediary = new CcdiIntermediaryBlacklist();
|
||||
intermediary.setName(excel.getName());
|
||||
// 对于机构中介,使用统一社会信用代码作为证件号
|
||||
intermediary.setCertificateNo(excel.getCorpCreditCode());
|
||||
intermediary.setIntermediaryType("2");
|
||||
intermediary.setStatus("0");
|
||||
intermediary.setDataSource("IMPORT");
|
||||
intermediary.setRemark(excel.getRemark());
|
||||
|
||||
// 机构专属字段
|
||||
intermediary.setCorpCreditCode(excel.getCorpCreditCode());
|
||||
intermediary.setCorpType(excel.getCorpType());
|
||||
intermediary.setCorpNature(excel.getCorpNature());
|
||||
intermediary.setCorpIndustryCategory(excel.getCorpIndustryCategory());
|
||||
intermediary.setCorpIndustry(excel.getCorpIndustry());
|
||||
|
||||
// 解析成立日期
|
||||
if (StringUtils.isNotEmpty(excel.getCorpEstablishDate())) {
|
||||
try {
|
||||
intermediary.setCorpEstablishDate(sdf.parse(excel.getCorpEstablishDate()));
|
||||
} catch (Exception e) {
|
||||
// 忽略日期解析错误
|
||||
}
|
||||
}
|
||||
|
||||
intermediary.setCorpAddress(excel.getCorpAddress());
|
||||
intermediary.setCorpLegalRep(excel.getCorpLegalRep());
|
||||
intermediary.setCorpLegalCertType(excel.getCorpLegalCertType());
|
||||
intermediary.setCorpLegalCertNo(excel.getCorpLegalCertNo());
|
||||
intermediary.setCorpShareholder1(excel.getCorpShareholder1());
|
||||
intermediary.setCorpShareholder2(excel.getCorpShareholder2());
|
||||
intermediary.setCorpShareholder3(excel.getCorpShareholder3());
|
||||
intermediary.setCorpShareholder4(excel.getCorpShareholder4());
|
||||
intermediary.setCorpShareholder5(excel.getCorpShareholder5());
|
||||
|
||||
// 检查是否需要更新
|
||||
if (isUpdateSupport && StringUtils.isNotEmpty(excel.getCorpCreditCode()) && creditCodeToIdMap.containsKey(excel.getCorpCreditCode())) {
|
||||
intermediary.setIntermediaryId(creditCodeToIdMap.get(excel.getCorpCreditCode()));
|
||||
toUpdateList.add(intermediary);
|
||||
} else {
|
||||
toInsertList.add(intermediary);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errorMessages.add("第" + (i + 1) + "行导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 批量执行数据库操作
|
||||
int successNum = 0;
|
||||
int failureNum = errorMessages.size();
|
||||
|
||||
// 批量插入
|
||||
if (!toInsertList.isEmpty()) {
|
||||
intermediaryMapper.batchInsert(toInsertList);
|
||||
successNum += toInsertList.size();
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
if (!toUpdateList.isEmpty()) {
|
||||
intermediaryMapper.batchUpdate(toUpdateList);
|
||||
successNum += toUpdateList.size();
|
||||
}
|
||||
|
||||
// 构建失败消息
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
for (String error : errorMessages) {
|
||||
failureMsg.append("<br/>").append(error);
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
if (failureNum > 0) {
|
||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
} else {
|
||||
return "恭喜您,数据已全部导入成功!共 " + successNum + " 条";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证个人中介数据
|
||||
*/
|
||||
private void validatePersonIntermediaryData(CcdiIntermediaryPersonExcel excel) {
|
||||
if (StringUtils.isEmpty(excel.getName())) {
|
||||
throw new RuntimeException("姓名不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(excel.getCertificateNo())) {
|
||||
throw new RuntimeException("证件号码不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证机构中介数据
|
||||
*/
|
||||
private void validateEntityIntermediaryData(CcdiIntermediaryEntityExcel excel) {
|
||||
if (StringUtils.isEmpty(excel.getName())) {
|
||||
throw new RuntimeException("机构名称不能为空");
|
||||
}
|
||||
// 验证统一社会信用代码不能为空(因为会用作 certificate_no 字段)
|
||||
if (StringUtils.isEmpty(excel.getCorpCreditCode())) {
|
||||
throw new RuntimeException("统一社会信用代码不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为个人详情 VO
|
||||
*/
|
||||
private CcdiIntermediaryPersonDetailVO convertToPersonDetailVO(CcdiIntermediaryBlacklist intermediary) {
|
||||
if (intermediary == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CcdiIntermediaryPersonDetailVO vo = new CcdiIntermediaryPersonDetailVO();
|
||||
// 复制基础字段
|
||||
vo.setIntermediaryId(intermediary.getIntermediaryId());
|
||||
vo.setName(intermediary.getName());
|
||||
vo.setCertificateNo(intermediary.getCertificateNo());
|
||||
vo.setIntermediaryType(intermediary.getIntermediaryType());
|
||||
vo.setStatus(intermediary.getStatus());
|
||||
vo.setRemark(intermediary.getRemark());
|
||||
vo.setDataSource(intermediary.getDataSource());
|
||||
vo.setCreateBy(intermediary.getCreateBy());
|
||||
vo.setCreateTime(intermediary.getCreateTime());
|
||||
vo.setUpdateBy(intermediary.getUpdateBy());
|
||||
vo.setUpdateTime(intermediary.getUpdateTime());
|
||||
|
||||
// 复制个人专属字段
|
||||
vo.setIndivType(intermediary.getIndivType());
|
||||
vo.setIndivSubType(intermediary.getIndivSubType());
|
||||
vo.setIndivGender(intermediary.getIndivGender());
|
||||
vo.setIndivCertType(intermediary.getIndivCertType());
|
||||
vo.setIndivPhone(intermediary.getIndivPhone());
|
||||
vo.setIndivWechat(intermediary.getIndivWechat());
|
||||
vo.setIndivAddress(intermediary.getIndivAddress());
|
||||
vo.setIndivCompany(intermediary.getIndivCompany());
|
||||
vo.setIndivPosition(intermediary.getIndivPosition());
|
||||
vo.setIndivRelatedId(intermediary.getIndivRelatedId());
|
||||
vo.setIndivRelation(intermediary.getIndivRelation());
|
||||
|
||||
// 设置枚举类型的名称
|
||||
vo.setIntermediaryTypeName(IntermediaryType.PERSON.getDesc());
|
||||
vo.setStatusName(IntermediaryStatus.getDescByCode(intermediary.getStatus()));
|
||||
vo.setDataSourceName(DataSource.getDescByCode(intermediary.getDataSource()));
|
||||
vo.setIndivGenderName(Gender.getDescByCode(intermediary.getIndivGender()));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为机构详情 VO
|
||||
*/
|
||||
private CcdiIntermediaryEntityDetailVO convertToEntityDetailVO(CcdiIntermediaryBlacklist intermediary) {
|
||||
if (intermediary == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CcdiIntermediaryEntityDetailVO vo = new CcdiIntermediaryEntityDetailVO();
|
||||
// 复制基础字段
|
||||
vo.setIntermediaryId(intermediary.getIntermediaryId());
|
||||
vo.setName(intermediary.getName());
|
||||
vo.setCertificateNo(intermediary.getCertificateNo());
|
||||
vo.setIntermediaryType(intermediary.getIntermediaryType());
|
||||
vo.setStatus(intermediary.getStatus());
|
||||
vo.setRemark(intermediary.getRemark());
|
||||
vo.setDataSource(intermediary.getDataSource());
|
||||
vo.setCreateBy(intermediary.getCreateBy());
|
||||
vo.setCreateTime(intermediary.getCreateTime());
|
||||
vo.setUpdateBy(intermediary.getUpdateBy());
|
||||
vo.setUpdateTime(intermediary.getUpdateTime());
|
||||
|
||||
// 复制机构专属字段
|
||||
vo.setCorpCreditCode(intermediary.getCorpCreditCode());
|
||||
vo.setCorpType(intermediary.getCorpType());
|
||||
vo.setCorpNature(intermediary.getCorpNature());
|
||||
vo.setCorpIndustryCategory(intermediary.getCorpIndustryCategory());
|
||||
vo.setCorpIndustry(intermediary.getCorpIndustry());
|
||||
vo.setCorpEstablishDate(intermediary.getCorpEstablishDate());
|
||||
vo.setCorpAddress(intermediary.getCorpAddress());
|
||||
vo.setCorpLegalRep(intermediary.getCorpLegalRep());
|
||||
vo.setCorpLegalCertType(intermediary.getCorpLegalCertType());
|
||||
vo.setCorpLegalCertNo(intermediary.getCorpLegalCertNo());
|
||||
vo.setCorpShareholder1(intermediary.getCorpShareholder1());
|
||||
vo.setCorpShareholder2(intermediary.getCorpShareholder2());
|
||||
vo.setCorpShareholder3(intermediary.getCorpShareholder3());
|
||||
vo.setCorpShareholder4(intermediary.getCorpShareholder4());
|
||||
vo.setCorpShareholder5(intermediary.getCorpShareholder5());
|
||||
|
||||
// 设置枚举类型的名称
|
||||
vo.setIntermediaryTypeName(IntermediaryType.ENTITY.getDesc());
|
||||
vo.setStatusName(IntermediaryStatus.getDescByCode(intermediary.getStatus()));
|
||||
vo.setDataSourceName(DataSource.getDescByCode(intermediary.getDataSource()));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*/
|
||||
private LambdaQueryWrapper<CcdiIntermediaryBlacklist> buildQueryWrapper(CcdiIntermediaryBlacklistQueryDTO queryDTO) {
|
||||
LambdaQueryWrapper<CcdiIntermediaryBlacklist> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(StringUtils.isNotEmpty(queryDTO.getName()), CcdiIntermediaryBlacklist::getName, queryDTO.getName())
|
||||
.like(StringUtils.isNotEmpty(queryDTO.getCertificateNo()), CcdiIntermediaryBlacklist::getCertificateNo, queryDTO.getCertificateNo())
|
||||
.eq(StringUtils.isNotEmpty(queryDTO.getIntermediaryType()), CcdiIntermediaryBlacklist::getIntermediaryType, queryDTO.getIntermediaryType())
|
||||
.eq(StringUtils.isNotEmpty(queryDTO.getStatus()), CcdiIntermediaryBlacklist::getStatus, queryDTO.getStatus())
|
||||
.orderByDesc(CcdiIntermediaryBlacklist::getCreateTime);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证中介数据
|
||||
*/
|
||||
private void validateIntermediaryData(CcdiIntermediaryBlacklistAddDTO addDTO) {
|
||||
// 验证必填字段
|
||||
if (StringUtils.isEmpty(addDTO.getName())) {
|
||||
throw new RuntimeException("姓名/机构名称不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getIntermediaryType())) {
|
||||
throw new RuntimeException("中介类型不能为空");
|
||||
}
|
||||
|
||||
// 验证中介类型
|
||||
if (!"1".equals(addDTO.getIntermediaryType()) && !"2".equals(addDTO.getIntermediaryType())) {
|
||||
throw new RuntimeException("中介类型只能填写'个人'或'机构'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为VO对象
|
||||
*/
|
||||
private CcdiIntermediaryBlacklistVO convertToVO(CcdiIntermediaryBlacklist intermediary) {
|
||||
if (intermediary == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CcdiIntermediaryBlacklistVO vo = new CcdiIntermediaryBlacklistVO();
|
||||
BeanUtils.copyProperties(intermediary, vo);
|
||||
|
||||
vo.setIntermediaryTypeName(IntermediaryType.getDescByCode(intermediary.getIntermediaryType()));
|
||||
vo.setStatusName(IntermediaryStatus.getDescByCode(intermediary.getStatus()));
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.ruoyi.ccdi.utils.converter;
|
||||
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 员工状态转换器
|
||||
* 0=在职, 1=离职
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class EmployeeStatusConverter implements Converter<String> {
|
||||
|
||||
private static final Map<String, String> CODE_TO_DESC = new HashMap<>();
|
||||
private static final Map<String, String> DESC_TO_CODE = new HashMap<>();
|
||||
|
||||
static {
|
||||
CODE_TO_DESC.put("0", "在职");
|
||||
CODE_TO_DESC.put("1", "离职");
|
||||
DESC_TO_CODE.put("在职", "0");
|
||||
DESC_TO_CODE.put("离职", "1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> supportJavaTypeKey() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
return CellDataTypeEnum.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
String value = cellData.getStringValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
// 支持中文和代码两种格式
|
||||
if (DESC_TO_CODE.containsKey(value)) {
|
||||
return DESC_TO_CODE.get(value);
|
||||
}
|
||||
// 如果是纯数字,直接返回
|
||||
if (value.matches("\\d")) {
|
||||
return value;
|
||||
}
|
||||
throw new IllegalArgumentException("无效的员工状态: " + value + ", 请使用: 在职/离职 或 0/1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
String desc = CODE_TO_DESC.getOrDefault(value, value);
|
||||
return new WriteCellData<>(desc);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.ruoyi.ccdi.utils.handler;
|
||||
|
||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.DataValidation;
|
||||
import org.apache.poi.ss.usermodel.DataValidationHelper;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
|
||||
/**
|
||||
* 员工状态下拉框处理器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class EmployeeStatusSheetWriteHandler implements SheetWriteHandler {
|
||||
|
||||
@Override
|
||||
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
|
||||
Sheet sheet = writeSheetHolder.getSheet();
|
||||
Workbook workbook = writeWorkbookHolder.getWorkbook();
|
||||
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||
|
||||
// 创建下拉框数据列表
|
||||
String[] statusList = {"在职", "离职"};
|
||||
|
||||
// 设置状态下拉框,从第2行开始(第1行是表头),第7列(状态列,索引为6)
|
||||
CellRangeAddressList addressList = new CellRangeAddressList(1, 10000, 6, 6);
|
||||
|
||||
// 创建显式列表约束
|
||||
DataValidation validation = helper.createValidation(
|
||||
helper.createExplicitListConstraint(statusList),
|
||||
addressList
|
||||
);
|
||||
|
||||
// 设置提示信息
|
||||
validation.createPromptBox("状态选择", "请选择员工状态:在职或离职");
|
||||
validation.setShowPromptBox(true);
|
||||
|
||||
// 设置错误提示
|
||||
validation.createErrorBox("状态错误", "请从下拉框中选择有效的状态值!");
|
||||
validation.setShowErrorBox(true);
|
||||
|
||||
sheet.addValidationData(validation);
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.ccdi.mapper.CcdiIntermediaryBlacklistMapper">
|
||||
|
||||
<!-- 批量插入中介黑名单数据 -->
|
||||
<insert id="batchInsert" parameterType="java.util.List">
|
||||
INSERT INTO ccdi_intermediary_blacklist (
|
||||
name,
|
||||
certificate_no,
|
||||
intermediary_type,
|
||||
status,
|
||||
remark,
|
||||
indiv_type,
|
||||
indiv_sub_type,
|
||||
indiv_gender,
|
||||
indiv_cert_type,
|
||||
indiv_phone,
|
||||
indiv_wechat,
|
||||
indiv_address,
|
||||
indiv_company,
|
||||
indiv_position,
|
||||
indiv_related_id,
|
||||
indiv_relation,
|
||||
corp_credit_code,
|
||||
corp_type,
|
||||
corp_nature,
|
||||
corp_industry_category,
|
||||
corp_industry,
|
||||
corp_establish_date,
|
||||
corp_address,
|
||||
corp_legal_rep,
|
||||
corp_legal_cert_type,
|
||||
corp_legal_cert_no,
|
||||
corp_shareholder_1,
|
||||
corp_shareholder_2,
|
||||
corp_shareholder_3,
|
||||
corp_shareholder_4,
|
||||
corp_shareholder_5,
|
||||
data_source,
|
||||
create_by,
|
||||
create_time,
|
||||
update_by,
|
||||
update_time
|
||||
) VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(
|
||||
#{item.name},
|
||||
#{item.certificateNo},
|
||||
#{item.intermediaryType},
|
||||
#{item.status},
|
||||
#{item.remark},
|
||||
#{item.indivType},
|
||||
#{item.indivSubType},
|
||||
#{item.indivGender},
|
||||
#{item.indivCertType},
|
||||
#{item.indivPhone},
|
||||
#{item.indivWechat},
|
||||
#{item.indivAddress},
|
||||
#{item.indivCompany},
|
||||
#{item.indivPosition},
|
||||
#{item.indivRelatedId},
|
||||
#{item.indivRelation},
|
||||
#{item.corpCreditCode},
|
||||
#{item.corpType},
|
||||
#{item.corpNature},
|
||||
#{item.corpIndustryCategory},
|
||||
#{item.corpIndustry},
|
||||
#{item.corpEstablishDate},
|
||||
#{item.corpAddress},
|
||||
#{item.corpLegalRep},
|
||||
#{item.corpLegalCertType},
|
||||
#{item.corpLegalCertNo},
|
||||
#{item.corpShareholder1},
|
||||
#{item.corpShareholder2},
|
||||
#{item.corpShareholder3},
|
||||
#{item.corpShareholder4},
|
||||
#{item.corpShareholder5},
|
||||
#{item.dataSource},
|
||||
#{item.createBy},
|
||||
#{item.createTime},
|
||||
#{item.updateBy},
|
||||
#{item.updateTime}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 批量更新中介黑名单数据 -->
|
||||
<update id="batchUpdate" parameterType="java.util.List">
|
||||
<foreach collection="list" item="item" separator=";">
|
||||
UPDATE ccdi_intermediary_blacklist
|
||||
<set>
|
||||
<if test="item.name != null">name = #{item.name},</if>
|
||||
<if test="item.certificateNo != null">certificate_no = #{item.certificateNo},</if>
|
||||
<if test="item.intermediaryType != null">intermediary_type = #{item.intermediaryType},</if>
|
||||
<if test="item.status != null">status = #{item.status},</if>
|
||||
<if test="item.remark != null">remark = #{item.remark},</if>
|
||||
<if test="item.indivType != null">indiv_type = #{item.indivType},</if>
|
||||
<if test="item.indivSubType != null">indiv_sub_type = #{item.indivSubType},</if>
|
||||
<if test="item.indivGender != null">indiv_gender = #{item.indivGender},</if>
|
||||
<if test="item.indivCertType != null">indiv_cert_type = #{item.indivCertType},</if>
|
||||
<if test="item.indivPhone != null">indiv_phone = #{item.indivPhone},</if>
|
||||
<if test="item.indivWechat != null">indiv_wechat = #{item.indivWechat},</if>
|
||||
<if test="item.indivAddress != null">indiv_address = #{item.indivAddress},</if>
|
||||
<if test="item.indivCompany != null">indiv_company = #{item.indivCompany},</if>
|
||||
<if test="item.indivPosition != null">indiv_position = #{item.indivPosition},</if>
|
||||
<if test="item.indivRelatedId != null">indiv_related_id = #{item.indivRelatedId},</if>
|
||||
<if test="item.indivRelation != null">indiv_relation = #{item.indivRelation},</if>
|
||||
<if test="item.corpCreditCode != null">corp_credit_code = #{item.corpCreditCode},</if>
|
||||
<if test="item.corpType != null">corp_type = #{item.corpType},</if>
|
||||
<if test="item.corpNature != null">corp_nature = #{item.corpNature},</if>
|
||||
<if test="item.corpIndustryCategory != null">corp_industry_category = #{item.corpIndustryCategory},</if>
|
||||
<if test="item.corpIndustry != null">corp_industry = #{item.corpIndustry},</if>
|
||||
<if test="item.corpEstablishDate != null">corp_establish_date = #{item.corpEstablishDate},</if>
|
||||
<if test="item.corpAddress != null">corp_address = #{item.corpAddress},</if>
|
||||
<if test="item.corpLegalRep != null">corp_legal_rep = #{item.corpLegalRep},</if>
|
||||
<if test="item.corpLegalCertType != null">corp_legal_cert_type = #{item.corpLegalCertType},</if>
|
||||
<if test="item.corpLegalCertNo != null">corp_legal_cert_no = #{item.corpLegalCertNo},</if>
|
||||
<if test="item.corpShareholder1 != null">corp_shareholder_1 = #{item.corpShareholder1},</if>
|
||||
<if test="item.corpShareholder2 != null">corp_shareholder_2 = #{item.corpShareholder2},</if>
|
||||
<if test="item.corpShareholder3 != null">corp_shareholder_3 = #{item.corpShareholder3},</if>
|
||||
<if test="item.corpShareholder4 != null">corp_shareholder_4 = #{item.corpShareholder4},</if>
|
||||
<if test="item.corpShareholder5 != null">corp_shareholder_5 = #{item.corpShareholder5},</if>
|
||||
<if test="item.dataSource != null">data_source = #{item.dataSource},</if>
|
||||
<if test="item.certificateNo != null">certificate_no = #{item.certificateNo},</if>
|
||||
update_by = #{item.updateBy},
|
||||
update_time = #{item.updateTime}
|
||||
</set>
|
||||
WHERE intermediary_id = #{item.intermediaryId}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 若依管理系统
|
||||
VUE_APP_TITLE = 纪检初核系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 若依管理系统
|
||||
VUE_APP_TITLE = 纪检初核系统
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 若依管理系统
|
||||
VUE_APP_TITLE = 纪检初核系统
|
||||
|
||||
BABEL_ENV = production
|
||||
|
||||
|
||||
@@ -96,6 +96,12 @@
|
||||
icon="el-icon-s-data"
|
||||
@click="handleEnter(scope.row)"
|
||||
>进入项目</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
@click="handleReAnalyze(scope.row)"
|
||||
>重新分析</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 已完成项目 -->
|
||||
|
||||
@@ -7,7 +7,7 @@ function resolve(dir) {
|
||||
|
||||
const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
|
||||
const name = process.env.VUE_APP_TITLE || '纪检初核系统' // 网页标题
|
||||
|
||||
const baseUrl = 'http://localhost:8080' // 后端接口
|
||||
|
||||
|
||||
276
verify_fix.py
276
verify_fix.py
@@ -1,276 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
接口参数修复验证脚本
|
||||
用于验证 GetToken 接口参数修复是否成功
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
from typing import Dict, Any
|
||||
|
||||
# 配置
|
||||
BASE_URL = "http://localhost:8000"
|
||||
TIMEOUT = 10
|
||||
|
||||
|
||||
def print_separator(title: str = ""):
|
||||
"""打印分隔线"""
|
||||
if title:
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {title}")
|
||||
print(f"{'='*60}")
|
||||
else:
|
||||
print(f"{'='*60}")
|
||||
|
||||
|
||||
def print_result(response: requests.Response, test_name: str):
|
||||
"""打印测试结果"""
|
||||
print(f"\n测试: {test_name}")
|
||||
print(f"状态码: {response.status_code}")
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
print(f"响应数据:")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
# 检查是否成功
|
||||
if data.get("code") == "200":
|
||||
print(f"\n✅ 测试通过")
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ 测试失败: {data.get('message', '未知错误')}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"\n❌ 解析响应失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def test_token_with_all_params():
|
||||
"""测试包含所有必填参数的 Token 请求"""
|
||||
print_separator("测试1: 完整参数的 GetToken 请求")
|
||||
|
||||
request_data = {
|
||||
"projectNo": "test_full_params_001",
|
||||
"entityName": "测试企业-完整参数",
|
||||
"userId": "902001",
|
||||
"userName": "张三",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
"entityId": "91110000MA00ABCD12",
|
||||
"xdRelatedPersons": json.dumps([
|
||||
{"relatedPerson": "关联企业1", "relation": "股东"},
|
||||
{"relatedPerson": "关联人1", "relation": "董事"}
|
||||
], ensure_ascii=False),
|
||||
"jzDataDateId": "0",
|
||||
"innerBSStartDateId": "0",
|
||||
"innerBSEndDateId": "0",
|
||||
"analysisType": "-1"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/account/common/getToken",
|
||||
json=request_data,
|
||||
timeout=TIMEOUT
|
||||
)
|
||||
return print_result(response, "完整参数请求")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\n❌ 请求失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def test_token_with_required_params_only():
|
||||
"""测试仅包含必填参数的 Token 请求"""
|
||||
print_separator("测试2: 仅必填参数的 GetToken 请求")
|
||||
|
||||
request_data = {
|
||||
"projectNo": "test_required_params_002",
|
||||
"entityName": "测试企业-仅必填参数",
|
||||
"userId": "902001",
|
||||
"userName": "李四",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_67890",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
"analysisType": "-1"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/account/common/getToken",
|
||||
json=request_data,
|
||||
timeout=TIMEOUT
|
||||
)
|
||||
return print_result(response, "必填参数请求")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\n❌ 请求失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def test_token_error_scenario():
|
||||
"""测试错误场景触发"""
|
||||
print_separator("测试3: 错误场景触发 (40101)")
|
||||
|
||||
request_data = {
|
||||
"projectNo": "test_error_40101", # 包含错误标记
|
||||
"entityName": "测试错误场景",
|
||||
"userId": "902001",
|
||||
"userName": "王五",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000",
|
||||
"analysisType": "-1"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/account/common/getToken",
|
||||
json=request_data,
|
||||
timeout=TIMEOUT
|
||||
)
|
||||
|
||||
print(f"\n测试: 错误场景触发")
|
||||
print(f"状态码: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
print(f"响应数据:")
|
||||
print(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
# 检查是否返回了预期的错误码
|
||||
if data.get("code") == "40101":
|
||||
print(f"\n✅ 测试通过 - 成功触发错误码 40101")
|
||||
return True
|
||||
else:
|
||||
print(f"\n⚠️ 警告: 未触发预期错误码")
|
||||
return False
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\n❌ 请求失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def test_token_missing_required_param():
|
||||
"""测试缺少必填参数的情况"""
|
||||
print_separator("测试4: 缺少必填参数验证")
|
||||
|
||||
# 故意缺少 departmentCode
|
||||
request_data = {
|
||||
"projectNo": "test_missing_param_003",
|
||||
"entityName": "测试缺少参数",
|
||||
"userId": "902001",
|
||||
"userName": "赵六",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"analysisType": "-1"
|
||||
# 缺少 departmentCode
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/account/common/getToken",
|
||||
json=request_data,
|
||||
timeout=TIMEOUT
|
||||
)
|
||||
|
||||
print(f"\n测试: 缺少必填参数")
|
||||
print(f"状态码: {response.status_code}")
|
||||
|
||||
# 应该返回 422 Unprocessable Entity
|
||||
if response.status_code == 422:
|
||||
print(f"✅ 测试通过 - 正确拒绝了缺少必填参数的请求")
|
||||
print(f"验证错误信息:")
|
||||
print(json.dumps(response.json(), indent=2, ensure_ascii=False))
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ 警告: 服务器接受了不完整的请求")
|
||||
print(f"响应数据:")
|
||||
print(json.dumps(response.json(), indent=2, ensure_ascii=False))
|
||||
return False
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"\n❌ 请求失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def check_server_status():
|
||||
"""检查服务器状态"""
|
||||
print_separator("检查服务器状态")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/health", timeout=TIMEOUT)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ 服务器运行中")
|
||||
print(f"健康状态: {response.json()}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 服务器状态异常: {response.status_code}")
|
||||
return False
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ 无法连接到服务器: {str(e)}")
|
||||
print(f"\n请确保服务器已启动:")
|
||||
print(f" python main.py")
|
||||
print(f" 或")
|
||||
print(f" uvicorn main:app --reload --host 0.0.0.0 --port 8000")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print_separator("接口参数修复验证")
|
||||
print("\n此脚本用于验证 GetToken 接口参数修复是否成功")
|
||||
print(f"服务器地址: {BASE_URL}\n")
|
||||
|
||||
# 检查服务器状态
|
||||
if not check_server_status():
|
||||
print_separator("验证失败")
|
||||
print("请先启动服务器,然后重新运行此脚本")
|
||||
sys.exit(1)
|
||||
|
||||
# 运行测试
|
||||
results = []
|
||||
results.append(("完整参数测试", test_token_with_all_params()))
|
||||
results.append(("必填参数测试", test_token_with_required_params_only()))
|
||||
results.append(("错误场景测试", test_token_error_scenario()))
|
||||
results.append(("参数校验测试", test_token_missing_required_param()))
|
||||
|
||||
# 打印总结
|
||||
print_separator("测试总结")
|
||||
|
||||
passed = sum(1 for _, result in results if result)
|
||||
total = len(results)
|
||||
|
||||
print(f"\n总测试数: {total}")
|
||||
print(f"通过: {passed}")
|
||||
print(f"失败: {total - passed}")
|
||||
|
||||
print("\n详细结果:")
|
||||
for name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f" - {name}: {status}")
|
||||
|
||||
# 最终结论
|
||||
print_separator()
|
||||
if passed == total:
|
||||
print("\n🎉 所有测试通过!接口参数修复成功!\n")
|
||||
print("修复内容:")
|
||||
print(" ✅ 添加 appId 参数")
|
||||
print(" ✅ 添加 appSecretCode 参数")
|
||||
print(" ✅ 添加 role 参数")
|
||||
print(" ✅ 修复 analysisType 类型")
|
||||
print(" ✅ 修复 departmentCode 必填性")
|
||||
print()
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n⚠️ 部分测试失败,请检查修复是否完整\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
276
修复完成报告.md
276
修复完成报告.md
@@ -1,276 +0,0 @@
|
||||
# 🔧 接口参数修复完成报告
|
||||
|
||||
## ✅ 修复状态:已完成
|
||||
|
||||
**修复时间**: 2026-03-03
|
||||
**修复人员**: Claude Code
|
||||
**测试状态**: ✅ 全部通过 (7/7)
|
||||
|
||||
---
|
||||
|
||||
## 📝 修复摘要
|
||||
|
||||
### 问题发现
|
||||
|
||||
通过详细的文档对比分析,发现 GetToken 接口缺少 **3个关键认证参数**,导致接口无法正常调用。
|
||||
|
||||
### 修复内容
|
||||
|
||||
#### 1. 新增必填参数(3个)
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 说明 | 影响 |
|
||||
|--------|------|--------|------|------|
|
||||
| **appId** | `str` | `"remote_app"` | 应用ID | 🔴 认证失败 (40101) |
|
||||
| **appSecretCode** | `str` | 必填 | 安全码 | 🔴 认证失败 (40102) |
|
||||
| **role** | `str` | `"VIEWER"` | 角色权限 | 🟡 权限控制 |
|
||||
|
||||
#### 2. 修复类型错误(2个)
|
||||
|
||||
| 参数名 | 修复前 | 修复后 | 说明 |
|
||||
|--------|--------|--------|------|
|
||||
| **analysisType** | `Optional[int]` | `str` | 类型错误,应为字符串 |
|
||||
| **departmentCode** | `Optional[str]` | `str` | 必填性错误,应为必填 |
|
||||
|
||||
---
|
||||
|
||||
## 📂 修改的文件
|
||||
|
||||
### 核心代码
|
||||
|
||||
1. **models/request.py** - 更新 GetTokenRequest 模型
|
||||
- ✅ 添加 3 个必填参数
|
||||
- ✅ 修复 2 个类型/必填性错误
|
||||
|
||||
### 测试代码
|
||||
|
||||
2. **tests/conftest.py** - 更新测试 fixture
|
||||
3. **tests/test_api.py** - 更新单元测试
|
||||
4. **tests/integration/test_full_workflow.py** - 更新集成测试
|
||||
|
||||
### 文档
|
||||
|
||||
5. **README.md** - 更新使用示例
|
||||
|
||||
---
|
||||
|
||||
## ✅ 测试验证
|
||||
|
||||
### Pytest 测试结果
|
||||
|
||||
```bash
|
||||
============================= test session starts =============================
|
||||
platform win32 -- Python 3.13.12, pytest-9.0.2, pluggy-1.6.0
|
||||
collected 7 items
|
||||
|
||||
tests/integration/test_full_workflow.py::test_complete_workflow PASSED [ 14%]
|
||||
tests/integration/test_full_workflow.py::test_all_error_codes PASSED [ 28%]
|
||||
tests/integration/test_full_workflow.py::test_pagination PASSED [ 42%]
|
||||
tests/test_api.py::test_root_endpoint PASSED [ 57%]
|
||||
tests/test_api.py::test_health_check PASSED [ 71%]
|
||||
tests/test_api.py::test_get_token_success PASSED [ 85%]
|
||||
tests/test_api.py::test_get_token_error_40101 PASSED [100%]
|
||||
|
||||
======================== 7 passed, 1 warning in 0.08s =========================
|
||||
```
|
||||
|
||||
**结论**: ✅ 所有测试通过
|
||||
|
||||
---
|
||||
|
||||
## 🎯 修复对比
|
||||
|
||||
### 修复前
|
||||
|
||||
```python
|
||||
class GetTokenRequest(BaseModel):
|
||||
projectNo: str
|
||||
entityName: str
|
||||
userId: str
|
||||
userName: str
|
||||
orgCode: str
|
||||
# ❌ 缺少 3 个必填参数
|
||||
# ❌ analysisType 类型错误
|
||||
# ❌ departmentCode 可选性错误
|
||||
```
|
||||
|
||||
### 修复后
|
||||
|
||||
```python
|
||||
class GetTokenRequest(BaseModel):
|
||||
projectNo: str
|
||||
entityName: str
|
||||
userId: str
|
||||
userName: str
|
||||
appId: str = "remote_app" # ✅ 新增
|
||||
appSecretCode: str # ✅ 新增
|
||||
role: str = "VIEWER" # ✅ 新增
|
||||
orgCode: str
|
||||
analysisType: str = "-1" # ✅ 类型修复
|
||||
departmentCode: str # ✅ 必填性修复
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 使用示例
|
||||
|
||||
### 正确的请求示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
json={
|
||||
"projectNo": "902000_20260303140000",
|
||||
"entityName": "测试企业有限公司",
|
||||
"userId": "902001",
|
||||
"userName": "张三",
|
||||
"appId": "remote_app", # ✅ 必填
|
||||
"appSecretCode": "your_secret_code", # ✅ 必填
|
||||
"role": "VIEWER", # ✅ 必填
|
||||
"orgCode": "902000",
|
||||
"analysisType": "-1", # ✅ 字符串类型
|
||||
"departmentCode": "902000" # ✅ 必填
|
||||
}
|
||||
)
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "200",
|
||||
"data": {
|
||||
"token": "eyJ0eXAi...",
|
||||
"projectId": 10001,
|
||||
"projectNo": "902000_20260303140000",
|
||||
"entityName": "测试企业有限公司",
|
||||
"analysisType": 0
|
||||
},
|
||||
"message": "create.token.success",
|
||||
"status": "200",
|
||||
"successResponse": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 如何验证修复
|
||||
|
||||
### 方法1: 运行自动化测试
|
||||
|
||||
```bash
|
||||
cd lsfx-mock-server
|
||||
python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
### 方法2: 运行验证脚本
|
||||
|
||||
```bash
|
||||
# 先启动服务器
|
||||
python main.py
|
||||
|
||||
# 在另一个终端运行验证脚本
|
||||
python verify_fix.py
|
||||
```
|
||||
|
||||
### 方法3: 手动测试
|
||||
|
||||
使用 Swagger UI 进行交互式测试:
|
||||
|
||||
1. 启动服务器: `python main.py`
|
||||
2. 访问: http://localhost:8000/docs
|
||||
3. 找到 `/account/common/getToken` 接口
|
||||
4. 点击 "Try it out"
|
||||
5. 填写所有必填参数(包括新增的3个)
|
||||
6. 点击 "Execute" 查看结果
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 重要提示
|
||||
|
||||
### 1. 向后兼容性
|
||||
|
||||
❌ **此修复不向后兼容**
|
||||
|
||||
由于新增了必填参数,所有调用 GetToken 接口的客户端代码需要更新。
|
||||
|
||||
### 2. appSecretCode 生成
|
||||
|
||||
根据文档,`appSecretCode` 应按以下规则生成:
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
|
||||
def generate_app_secret_code(project_no: str, entity_name: str) -> str:
|
||||
"""
|
||||
生成安全码
|
||||
算法: md5(projectNo + "_" + entityName + "_" + "dXj6eHRmPv")
|
||||
"""
|
||||
secret_key = "dXj6eHRmPv"
|
||||
raw_string = f"{project_no}_{entity_name}_{secret_key}"
|
||||
return hashlib.md5(raw_string.encode()).hexdigest()
|
||||
|
||||
# 使用示例
|
||||
code = generate_app_secret_code("902000_20260303", "测试企业")
|
||||
```
|
||||
|
||||
### 3. 固定值参数
|
||||
|
||||
以下参数虽然提供默认值,但仍需在请求中传递:
|
||||
|
||||
- `appId = "remote_app"`
|
||||
- `role = "VIEWER"`
|
||||
- `analysisType = "-1"`
|
||||
|
||||
---
|
||||
|
||||
## 📊 接口完整性检查
|
||||
|
||||
| 接口名称 | 参数匹配度 | 状态 | 备注 |
|
||||
|---------|-----------|------|------|
|
||||
| 获取Token | 100% (15/15) | ✅ | 已修复,完全一致 |
|
||||
| 上传文件 | 100% (2/2) | ✅ | 无问题 |
|
||||
| 拉取行内流水 | 100% (7/7) | ✅ | 无问题 |
|
||||
| 检查解析状态 | 100% (2/2) | ✅ | 无问题 |
|
||||
| 删除文件 | 100% (3/3) | ✅ | 额外实现 |
|
||||
| 获取银行流水 | 100% (4/4) | ✅ | 无问题 |
|
||||
|
||||
---
|
||||
|
||||
## 📄 相关文档
|
||||
|
||||
1. **接口参数检查报告.md** - 详细的参数对比分析
|
||||
2. **修复总结.md** - 详细的修复记录
|
||||
3. **兰溪-流水分析对接-新版.md** - 官方接口文档
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修复验证清单
|
||||
|
||||
- [x] 分析接口文档,识别缺失参数
|
||||
- [x] 更新 GetTokenRequest 模型(5处修改)
|
||||
- [x] 更新测试数据(conftest.py)
|
||||
- [x] 更新单元测试(test_api.py)
|
||||
- [x] 更新集成测试(test_full_workflow.py)
|
||||
- [x] 更新文档示例(README.md)
|
||||
- [x] 运行所有测试通过(7/7 passed)
|
||||
- [x] 创建验证脚本(verify_fix.py)
|
||||
- [x] 编写修复文档
|
||||
|
||||
---
|
||||
|
||||
## 🎉 修复结论
|
||||
|
||||
**状态**: ✅ **修复完成**
|
||||
|
||||
所有接口参数已与文档完全一致,测试全部通过。Mock 服务器现在可以完全模拟真实接口的行为。
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: Claude Code
|
||||
**修复日期**: 2026-03-03
|
||||
**版本**: v1.1.0
|
||||
**下一步**: 可以开始使用修复后的 Mock 服务器进行开发和测试
|
||||
217
修复总结.md
217
修复总结.md
@@ -1,217 +0,0 @@
|
||||
# 接口参数修复总结
|
||||
|
||||
**修复日期**: 2026-03-03
|
||||
**修复范围**: GetToken 接口缺少必填参数
|
||||
|
||||
---
|
||||
|
||||
## 📋 修复内容
|
||||
|
||||
### ✅ 1. 修复 GetTokenRequest 模型
|
||||
|
||||
**文件**: `models/request.py`
|
||||
|
||||
#### 添加的必填参数(3个)
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| **appId** | `str` | `"remote_app"` | 应用ID,固定值 |
|
||||
| **appSecretCode** | `str` | 必填 | 安全码,需计算 MD5 |
|
||||
| **role** | `str` | `"VIEWER"` | 角色权限,固定值 |
|
||||
|
||||
#### 修复的类型错误(2个)
|
||||
|
||||
| 参数名 | 修复前 | 修复后 | 说明 |
|
||||
|--------|--------|--------|------|
|
||||
| **analysisType** | `Optional[int]` | `str` | 类型改为字符串 |
|
||||
| **departmentCode** | `Optional[str]` | `str` | 改为必填 |
|
||||
|
||||
#### 修复后的完整模型
|
||||
|
||||
```python
|
||||
class GetTokenRequest(BaseModel):
|
||||
"""获取Token请求模型"""
|
||||
projectNo: str = Field(..., description="项目编号,格式:902000_当前时间戳")
|
||||
entityName: str = Field(..., description="项目名称")
|
||||
userId: str = Field(..., description="操作人员编号,固定值")
|
||||
userName: str = Field(..., description="操作人员姓名,固定值")
|
||||
appId: str = Field("remote_app", description="应用ID,固定值")
|
||||
appSecretCode: str = Field(..., description="安全码,md5(projectNo + '_' + entityName + '_' + dXj6eHRmPv)")
|
||||
role: str = Field("VIEWER", description="角色,固定值")
|
||||
orgCode: str = Field(..., description="行社机构号,固定值")
|
||||
entityId: Optional[str] = Field(None, description="企业统信码或个人身份证号")
|
||||
xdRelatedPersons: Optional[str] = Field(None, description="信贷关联人信息")
|
||||
jzDataDateId: Optional[str] = Field("0", description="拉取指定日期推送过来的金综链流水")
|
||||
innerBSStartDateId: Optional[str] = Field("0", description="拉取行内流水开始日期")
|
||||
innerBSEndDateId: Optional[str] = Field("0", description="拉取行内流水结束日期")
|
||||
analysisType: str = Field("-1", description="分析类型,固定值")
|
||||
departmentCode: str = Field(..., description="客户经理所属营业部/分理处的机构编码")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ 2. 更新测试数据
|
||||
|
||||
#### 修改的文件
|
||||
|
||||
1. **tests/conftest.py** - 更新 `sample_token_request` fixture
|
||||
2. **tests/test_api.py** - 更新测试用例
|
||||
3. **tests/integration/test_full_workflow.py** - 更新集成测试
|
||||
|
||||
#### 更新后的测试数据示例
|
||||
|
||||
```python
|
||||
{
|
||||
"projectNo": "test_project_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_secret_code_12345",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ 3. 更新文档
|
||||
|
||||
#### 修改的文件
|
||||
|
||||
**README.md** - 更新使用示例
|
||||
|
||||
#### 更新内容
|
||||
|
||||
1. 正常流程示例添加了新参数
|
||||
2. 错误场景测试示例添加了新参数
|
||||
|
||||
---
|
||||
|
||||
## ✅ 测试验证
|
||||
|
||||
### 运行结果
|
||||
|
||||
```
|
||||
============================= test session starts =============================
|
||||
platform win32 -- Python 3.13.12, pytest-9.0.2, pluggy-1.6.0
|
||||
rootdir: D:\ccdi\ccdi\.claude\worktrees\lsfx-mock-server\lsfx-mock-server
|
||||
plugins: anyio-4.12.1, cov-7.0.0
|
||||
collected 7 items
|
||||
|
||||
tests/integration/test_full_workflow.py::test_complete_workflow PASSED [ 14%]
|
||||
tests/integration/test_full_workflow.py::test_all_error_codes PASSED [ 28%]
|
||||
tests/integration/test_full_workflow.py::test_pagination PASSED [ 42%]
|
||||
tests/test_api.py::test_root_endpoint PASSED [ 57%]
|
||||
tests/test_api.py::test_health_check PASSED [ 71%]
|
||||
tests/test_api.py::test_get_token_success PASSED [ 85%]
|
||||
tests/test_api.py::test_get_token_error_40101 PASSED [100%]
|
||||
|
||||
======================== 7 passed, 1 warning in 0.08s =========================
|
||||
```
|
||||
|
||||
**结论**: ✅ 所有 7 个测试用例通过
|
||||
|
||||
---
|
||||
|
||||
## 📊 修复前后对比
|
||||
|
||||
### 修复前的问题
|
||||
|
||||
| 问题类型 | 数量 | 严重性 |
|
||||
|---------|------|--------|
|
||||
| 缺少必填参数 | 3个 | 🔴 高 - 导致认证失败 |
|
||||
| 类型错误 | 1个 | 🟡 中 - 可能导致数据错误 |
|
||||
| 必填性错误 | 1个 | 🟡 中 - 参数校验不一致 |
|
||||
|
||||
### 修复后的状态
|
||||
|
||||
| 接口 | 参数数量 | 匹配度 | 状态 |
|
||||
|------|---------|--------|------|
|
||||
| 获取Token | 15个 | 100% | ✅ 完全一致 |
|
||||
| 上传文件 | 2个 | 100% | ✅ 完全一致 |
|
||||
| 拉取行内流水 | 7个 | 100% | ✅ 完全一致 |
|
||||
| 检查解析状态 | 2个 | 100% | ✅ 完全一致 |
|
||||
| 删除文件 | 3个 | 100% | ✅ 完全一致 |
|
||||
| 获取银行流水 | 4个 | 100% | ✅ 完全一致 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 关键改进
|
||||
|
||||
### 1. 认证参数完整性
|
||||
|
||||
- ✅ 添加 `appId` - 应用标识
|
||||
- ✅ 添加 `appSecretCode` - 安全码验证
|
||||
- ✅ 添加 `role` - 角色权限控制
|
||||
|
||||
### 2. 数据类型准确性
|
||||
|
||||
- ✅ `analysisType` 从 `int` 改为 `str`,符合文档要求
|
||||
- ✅ `departmentCode` 改为必填,确保数据完整性
|
||||
|
||||
### 3. 文档一致性
|
||||
|
||||
- ✅ 所有接口参数与文档完全一致
|
||||
- ✅ 所有示例代码已更新
|
||||
- ✅ 所有测试用例通过
|
||||
|
||||
---
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
### 1. appSecretCode 生成规则
|
||||
|
||||
根据文档说明,`appSecretCode` 应该按以下规则生成:
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
|
||||
def generate_app_secret_code(project_no: str, entity_name: str) -> str:
|
||||
"""
|
||||
生成安全码
|
||||
格式: md5(projectNo + "_" + entityName + "_" + "dXj6eHRmPv")
|
||||
"""
|
||||
secret_key = "dXj6eHRmPv"
|
||||
raw_string = f"{project_no}_{entity_name}_{secret_key}"
|
||||
return hashlib.md5(raw_string.encode()).hexdigest()
|
||||
```
|
||||
|
||||
### 2. 固定值参数
|
||||
|
||||
以下参数虽然有默认值,但仍需在请求中传递:
|
||||
|
||||
- `appId = "remote_app"`
|
||||
- `role = "VIEWER"`
|
||||
- `analysisType = "-1"`
|
||||
|
||||
### 3. 向后兼容性
|
||||
|
||||
由于新增了必填参数,此修复**不向后兼容**。所有调用 GetToken 接口的客户端需要更新请求参数。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修复验证清单
|
||||
|
||||
- [x] 更新 GetTokenRequest 模型(添加 3 个必填参数)
|
||||
- [x] 修复 analysisType 类型(int → str)
|
||||
- [x] 修复 departmentCode 必填性(可选 → 必填)
|
||||
- [x] 更新测试数据(conftest.py)
|
||||
- [x] 更新单元测试(test_api.py)
|
||||
- [x] 更新集成测试(test_full_workflow.py)
|
||||
- [x] 更新文档示例(README.md)
|
||||
- [x] 运行所有测试通过(7/7 passed)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
- [接口参数检查报告.md](./接口参数检查报告.md) - 详细的参数对比分析
|
||||
- [兰溪-流水分析对接-新版.md](../../../doc/对接流水分析/兰溪-流水分析对接-新版.md) - 官方接口文档
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: Claude Code
|
||||
**审核状态**: ✅ 已通过测试验证
|
||||
**版本**: v1.1.0
|
||||
210
接口参数检查报告.md
210
接口参数检查报告.md
@@ -1,210 +0,0 @@
|
||||
# 接口参数对比检查报告
|
||||
|
||||
**检查时间**: 2026-03-03
|
||||
**检查范围**: lsfx-mock-server 所有接口参数与文档对比
|
||||
|
||||
---
|
||||
|
||||
## 📋 总览
|
||||
|
||||
| 接口序号 | 接口名称 | 状态 | 问题数量 |
|
||||
|---------|---------|------|---------|
|
||||
| 1 | 新建项目并获取token | ❌ **严重** | 5个问题 |
|
||||
| 2 | 上传文件接口 | ✅ 一致 | 0个问题 |
|
||||
| 3 | 拉取行内流水接口 | ✅ 一致 | 0个问题 |
|
||||
| 4 | 判断文件是否解析结束 | ✅ 一致 | 0个问题 |
|
||||
| 5 | 删除文件接口 | ⚠️ 额外实现 | 文档中未提及 |
|
||||
| 6 | 获取流水列表 | ✅ 一致 | 0个问题 |
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ 新建项目并获取token - ❌ **严重问题**
|
||||
|
||||
### 缺少的必填参数(3个)
|
||||
|
||||
| 参数名 | 文档要求 | 代码实现 | 严重性 |
|
||||
|--------|---------|---------|--------|
|
||||
| **appId** | `String` 必填,固定值 `"remote_app"` | ❌ **缺失** | 🔴 高 - 认证参数 |
|
||||
| **appSecretCode** | `String` 必填,安全码 | ❌ **缺失** | 🔴 高 - 认证参数 |
|
||||
| **role** | `String` 必填,固定值 `"VIEWER"` | ❌ **缺失** | 🟡 中 - 权限参数 |
|
||||
|
||||
### 类型错误(1个)
|
||||
|
||||
| 参数名 | 文档要求 | 代码实现 | 说明 |
|
||||
|--------|---------|---------|------|
|
||||
| **analysisType** | `String` 必填 | `Optional[int]` 可选 | 应改为 `str` 类型 |
|
||||
|
||||
### 必填性错误(1个)
|
||||
|
||||
| 参数名 | 文档要求 | 代码实现 | 说明 |
|
||||
|--------|---------|---------|------|
|
||||
| **departmentCode** | 必填 | `Optional[str]` 可选 | 应改为必填 |
|
||||
|
||||
### 完整参数对比表(15个参数)
|
||||
|
||||
| 序号 | 参数名 | 文档类型 | 文档必填 | 代码类型 | 代码必填 | 状态 |
|
||||
|-----|--------|---------|---------|---------|---------|------|
|
||||
| 1 | projectNo | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| 2 | entityName | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| 3 | userId | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| 4 | userName | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| 5 | **appId** | String | ✅ 是 | - | - | ❌ **缺失** |
|
||||
| 6 | **appSecretCode** | String | ✅ 是 | - | - | ❌ **缺失** |
|
||||
| 7 | **role** | String | ✅ 是 | - | - | ❌ **缺失** |
|
||||
| 8 | orgCode | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| 9 | entityId | String | 否 | Optional[str] | 否 | ✅ |
|
||||
| 10 | xdRelatedPersons | String | 否 | Optional[str] | 否 | ✅ |
|
||||
| 11 | jzDataDateId | String | 否 | Optional[str] | 否 | ✅ |
|
||||
| 12 | innerBSStartDateId | String | 否 | Optional[str] | 否 | ✅ |
|
||||
| 13 | innerBSEndDateId | String | 否 | Optional[str] | 否 | ✅ |
|
||||
| 14 | **analysisType** | String | ✅ 是 | Optional[int] | 否 | ⚠️ **类型错误** |
|
||||
| 15 | **departmentCode** | String | ✅ 是 | Optional[str] | 否 | ⚠️ **必填性错误** |
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ 上传文件接口 - ✅ **完全一致**
|
||||
|
||||
### 请求参数对比
|
||||
|
||||
| 参数名 | 文档类型 | 文档必填 | 代码类型 | 代码必填 | 状态 |
|
||||
|--------|---------|---------|---------|---------|------|
|
||||
| groupId | Int | ✅ 是 | int (Form) | ✅ 是 | ✅ |
|
||||
| file | File | ✅ 是 | UploadFile | ✅ 是 | ✅ |
|
||||
|
||||
**结论**: 参数完全一致,无缺失。
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ 拉取行内流水接口 - ✅ **完全一致**
|
||||
|
||||
### 请求参数对比
|
||||
|
||||
| 参数名 | 文档类型 | 文档必填 | 代码类型 | 代码必填 | 状态 |
|
||||
|--------|---------|---------|---------|---------|------|
|
||||
| groupId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| customerNo | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| dataChannelCode | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
| requestDateId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| dataStartDateId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| dataEndDateId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| uploadUserId | int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
|
||||
**结论**: 参数完全一致,无缺失。
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ 判断文件是否解析结束 - ✅ **完全一致**
|
||||
|
||||
### 请求参数对比
|
||||
|
||||
| 参数名 | 文档类型 | 文档必填 | 代码类型 | 代码必填 | 状态 |
|
||||
|--------|---------|---------|---------|---------|------|
|
||||
| groupId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| inprogressList | String | ✅ 是 | str | ✅ 是 | ✅ |
|
||||
|
||||
**结论**: 参数完全一致,无缺失。
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ 删除文件接口 - ⚠️ **文档中未提及**
|
||||
|
||||
### 代码实现的参数
|
||||
|
||||
| 参数名 | 代码类型 | 代码必填 |
|
||||
|--------|---------|---------|
|
||||
| groupId | int | ✅ 是 |
|
||||
| logIds | List[int] | ✅ 是 |
|
||||
| userId | int | ✅ 是 |
|
||||
|
||||
**结论**: 接口路径 `/watson/api/project/batchDeleteUploadFile` 在文档的调用流程中提到,但没有详细的参数说明文档。
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ 获取流水列表 - ✅ **完全一致**
|
||||
|
||||
### 请求参数对比
|
||||
|
||||
| 参数名 | 文档类型 | 文档必填 | 代码类型 | 代码必填 | 状态 |
|
||||
|--------|---------|---------|---------|---------|------|
|
||||
| groupId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| logId | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| pageNow | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
| pageSize | Int | ✅ 是 | int | ✅ 是 | ✅ |
|
||||
|
||||
**结论**: 参数完全一致,无缺失。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
### ❌ **严重问题**
|
||||
|
||||
**接口1 - 获取Token接口缺少3个关键认证参数:**
|
||||
- `appId` - 固定值 `"remote_app"`
|
||||
- `appSecretCode` - 安全码,格式为 `md5(projectNo + "_" + entityName + "_" + dXj6eHRmPv)`
|
||||
- `role` - 固定值 `"VIEWER"`
|
||||
|
||||
这3个参数缺失会导致接口调用失败(错误码 40101, 40102)。
|
||||
|
||||
### ⚠️ **次要问题**
|
||||
|
||||
1. `analysisType` 类型应为 `str` 而非 `int`
|
||||
2. `departmentCode` 应为必填而非可选
|
||||
|
||||
### ✅ **正常接口**
|
||||
|
||||
其他5个接口参数完全一致,无缺失问题。
|
||||
|
||||
---
|
||||
|
||||
## 📝 修复建议
|
||||
|
||||
### 1. 修复 GetTokenRequest 模型
|
||||
|
||||
**当前代码:**
|
||||
```python
|
||||
class GetTokenRequest(BaseModel):
|
||||
projectNo: str
|
||||
entityName: str
|
||||
userId: str
|
||||
userName: str
|
||||
orgCode: str
|
||||
entityId: Optional[str] = None
|
||||
xdRelatedPersons: Optional[str] = None
|
||||
jzDataDateId: Optional[str] = "0"
|
||||
innerBSStartDateId: Optional[str] = "0"
|
||||
innerBSEndDateId: Optional[str] = "0"
|
||||
analysisType: Optional[int] = -1
|
||||
departmentCode: Optional[str] = None
|
||||
```
|
||||
|
||||
**应修改为:**
|
||||
```python
|
||||
class GetTokenRequest(BaseModel):
|
||||
projectNo: str = Field(..., description="项目编号,格式:902000_当前时间戳")
|
||||
entityName: str = Field(..., description="项目名称")
|
||||
userId: str = Field(..., description="操作人员编号,固定值")
|
||||
userName: str = Field(..., description="操作人员姓名,固定值")
|
||||
appId: str = Field("remote_app", description="应用ID,固定值")
|
||||
appSecretCode: str = Field(..., description="安全码,md5(projectNo + '_' + entityName + '_' + dXj6eHRmPv)")
|
||||
role: str = Field("VIEWER", description="角色,固定值")
|
||||
orgCode: str = Field(..., description="行社机构号,固定值")
|
||||
entityId: Optional[str] = Field(None, description="企业统信码或个人身份证号")
|
||||
xdRelatedPersons: Optional[str] = Field(None, description="信贷关联人信息")
|
||||
jzDataDateId: Optional[str] = Field("0", description="拉取指定日期推送过来的金综链流水")
|
||||
innerBSStartDateId: Optional[str] = Field("0", description="拉取行内流水开始日期")
|
||||
innerBSEndDateId: Optional[str] = Field("0", description="拉取行内流水结束日期")
|
||||
analysisType: str = Field("-1", description="分析类型,固定值")
|
||||
departmentCode: str = Field(..., description="客户经理所属营业部/分理处的机构编码")
|
||||
```
|
||||
|
||||
### 2. 注意事项
|
||||
|
||||
- `appSecretCode` 需要在服务端计算 MD5 值
|
||||
- `appId` 和 `role` 虽然是固定值,但仍需在请求体中传递
|
||||
- `analysisType` 应为字符串类型 `"-1"`,而不是整数 `-1`
|
||||
|
||||
---
|
||||
|
||||
**检查完成时间**: 2026-03-03
|
||||
**检查人员**: Claude Code
|
||||
415
接口调用示例.md
415
接口调用示例.md
@@ -1,415 +0,0 @@
|
||||
# 📖 接口调用示例
|
||||
|
||||
## 测试日期
|
||||
2026-03-03
|
||||
|
||||
## 传输格式
|
||||
**所有接口使用 Form-Data 格式** (`application/x-www-form-urlencoded`)
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ 获取 Token
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
data={ # ✅ 使用 data 参数
|
||||
"projectNo": "test_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "your_secret_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/account/common/getToken \
|
||||
-d "projectNo=test_001" \
|
||||
-d "entityName=测试企业" \
|
||||
-d "userId=902001" \
|
||||
-d "userName=902001" \
|
||||
-d "appId=remote_app" \
|
||||
-d "appSecretCode=your_secret_code" \
|
||||
-d "role=VIEWER" \
|
||||
-d "orgCode=902000" \
|
||||
-d "departmentCode=902000"
|
||||
```
|
||||
|
||||
### JavaScript fetch
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
formData.append('projectNo', 'test_001');
|
||||
formData.append('entityName', '测试企业');
|
||||
formData.append('userId', '902001');
|
||||
formData.append('userName', '902001');
|
||||
formData.append('appId', 'remote_app');
|
||||
formData.append('appSecretCode', 'your_secret_code');
|
||||
formData.append('role', 'VIEWER');
|
||||
formData.append('orgCode', '902000');
|
||||
formData.append('departmentCode', '902000');
|
||||
|
||||
fetch('http://localhost:8000/account/common/getToken', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ 上传文件
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
# 获取 token 后得到 project_id
|
||||
project_id = 10001
|
||||
|
||||
# 上传文件
|
||||
files = {'file': ('statement.csv', open('statement.csv', 'rb'), 'text/csv')}
|
||||
data = {'groupId': project_id}
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/remoteUploadSplitFile",
|
||||
files=files,
|
||||
data=data
|
||||
)
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/watson/api/project/remoteUploadSplitFile \
|
||||
-F "file=@statement.csv" \
|
||||
-F "groupId=10001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ 拉取行内流水
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/getJZFileOrZjrcuFile",
|
||||
data={
|
||||
"groupId": 10001,
|
||||
"customerNo": "330102199001011234",
|
||||
"dataChannelCode": "ZJRCU",
|
||||
"requestDateId": 20260303,
|
||||
"dataStartDateId": 20260101,
|
||||
"dataEndDateId": 20260303,
|
||||
"uploadUserId": 902001
|
||||
}
|
||||
)
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/watson/api/project/getJZFileOrZjrcuFile \
|
||||
-d "groupId=10001" \
|
||||
-d "customerNo=330102199001011234" \
|
||||
-d "dataChannelCode=ZJRCU" \
|
||||
-d "requestDateId=20260303" \
|
||||
-d "dataStartDateId=20260101" \
|
||||
-d "dataEndDateId=20260303" \
|
||||
-d "uploadUserId=902001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ 检查文件解析状态
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
import time
|
||||
|
||||
log_id = 10001
|
||||
|
||||
# 轮询检查解析状态
|
||||
for i in range(10):
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/upload/getpendings",
|
||||
data={
|
||||
"groupId": 10001,
|
||||
"inprogressList": str(log_id)
|
||||
}
|
||||
)
|
||||
|
||||
result = response.json()
|
||||
print(f"第{i+1}次检查: parsing={result['data']['parsing']}")
|
||||
|
||||
if not result['data']['parsing']:
|
||||
print("✅ 解析完成")
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/watson/api/project/upload/getpendings \
|
||||
-d "groupId=10001" \
|
||||
-d "inprogressList=10001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ 删除文件
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/batchDeleteUploadFile",
|
||||
data={
|
||||
"groupId": 10001,
|
||||
"logIds": "10001,10002,10003", # 逗号分隔的文件ID
|
||||
"userId": 902001
|
||||
}
|
||||
)
|
||||
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/watson/api/project/batchDeleteUploadFile \
|
||||
-d "groupId=10001" \
|
||||
-d "logIds=10001,10002,10003" \
|
||||
-d "userId=902001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ 获取银行流水
|
||||
|
||||
### Python requests
|
||||
```python
|
||||
import requests
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/watson/api/project/getBSByLogId",
|
||||
data={
|
||||
"groupId": 10001,
|
||||
"logId": 10001,
|
||||
"pageNow": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
)
|
||||
|
||||
result = response.json()
|
||||
print(f"总记录数: {result['data']['totalCount']}")
|
||||
print(f"当前页数据: {len(result['data']['bankStatementList'])} 条")
|
||||
|
||||
for statement in result['data']['bankStatementList']:
|
||||
print(f"交易日期: {statement['trxDate']}, 金额: {statement['transAmount']}")
|
||||
```
|
||||
|
||||
### curl
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/watson/api/project/getBSByLogId \
|
||||
-d "groupId=10001" \
|
||||
-d "logId=10001" \
|
||||
-d "pageNow=1" \
|
||||
-d "pageSize=10"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 完整工作流程示例
|
||||
|
||||
### Python 完整示例
|
||||
```python
|
||||
import requests
|
||||
import time
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
# 1. 获取 Token
|
||||
print("1️⃣ 获取 Token...")
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/account/common/getToken",
|
||||
data={
|
||||
"projectNo": "test_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "your_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
|
||||
token_data = response.json()
|
||||
project_id = token_data['data']['projectId']
|
||||
print(f"✅ Token 获取成功,项目ID: {project_id}")
|
||||
|
||||
# 2. 上传文件
|
||||
print("\n2️⃣ 上传文件...")
|
||||
files = {'file': ('test.csv', b'test data', 'text/csv')}
|
||||
data = {'groupId': project_id}
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/watson/api/project/remoteUploadSplitFile",
|
||||
files=files,
|
||||
data=data
|
||||
)
|
||||
|
||||
upload_data = response.json()
|
||||
log_id = upload_data['data']['uploadLogList'][0]['logId']
|
||||
print(f"✅ 文件上传成功,logId: {log_id}")
|
||||
|
||||
# 3. 轮询检查解析状态
|
||||
print("\n3️⃣ 检查解析状态...")
|
||||
for i in range(10):
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/watson/api/project/upload/getpendings",
|
||||
data={
|
||||
"groupId": project_id,
|
||||
"inprogressList": str(log_id)
|
||||
}
|
||||
)
|
||||
|
||||
result = response.json()
|
||||
if not result['data']['parsing']:
|
||||
print(f"✅ 解析完成(第{i+1}次检查)")
|
||||
break
|
||||
print(f"⏳ 解析中...(第{i+1}次检查)")
|
||||
time.sleep(1)
|
||||
|
||||
# 4. 获取银行流水
|
||||
print("\n4️⃣ 获取银行流水...")
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/watson/api/project/getBSByLogId",
|
||||
data={
|
||||
"groupId": project_id,
|
||||
"logId": log_id,
|
||||
"pageNow": 1,
|
||||
"pageSize": 5
|
||||
}
|
||||
)
|
||||
|
||||
statements = response.json()
|
||||
print(f"✅ 获取到 {statements['data']['totalCount']} 条流水记录")
|
||||
print(f" 当前页显示 {len(statements['data']['bankStatementList'])} 条")
|
||||
|
||||
# 5. 删除文件
|
||||
print("\n5️⃣ 删除文件...")
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/watson/api/project/batchDeleteUploadFile",
|
||||
data={
|
||||
"groupId": project_id,
|
||||
"logIds": str(log_id),
|
||||
"userId": 902001
|
||||
}
|
||||
)
|
||||
|
||||
print(f"✅ 文件删除成功")
|
||||
|
||||
print("\n🎉 完整流程测试完成!")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 常见错误
|
||||
|
||||
### ❌ 错误:使用 JSON 格式
|
||||
```python
|
||||
# ❌ 错误
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
json={ # 错误:使用了 json 参数
|
||||
"projectNo": "test_001",
|
||||
...
|
||||
}
|
||||
)
|
||||
# 返回: 422 Unprocessable Entity
|
||||
```
|
||||
|
||||
### ✅ 正确:使用 Form-Data
|
||||
```python
|
||||
# ✅ 正确
|
||||
response = requests.post(
|
||||
"http://localhost:8000/account/common/getToken",
|
||||
data={ # 正确:使用 data 参数
|
||||
"projectNo": "test_001",
|
||||
...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Content-Type 对比
|
||||
|
||||
| 参数方式 | Content-Type | Swagger UI 显示 | requests 参数 |
|
||||
|---------|-------------|----------------|--------------|
|
||||
| JSON | `application/json` | JSON 编辑器 | `json={}` |
|
||||
| Form-Data | `application/x-www-form-urlencoded` | 表单字段 | `data={}` |
|
||||
| Multipart | `multipart/form-data` | 文件上传 | `files={}, data={}` |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 快速测试脚本
|
||||
|
||||
保存为 `test_api.py`:
|
||||
```python
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
# 测试获取 Token
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/account/common/getToken",
|
||||
data={
|
||||
"projectNo": "test_001",
|
||||
"entityName": "测试企业",
|
||||
"userId": "902001",
|
||||
"userName": "902001",
|
||||
"appId": "remote_app",
|
||||
"appSecretCode": "test_code",
|
||||
"role": "VIEWER",
|
||||
"orgCode": "902000",
|
||||
"departmentCode": "902000"
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("✅ 接口测试成功")
|
||||
print(response.json())
|
||||
else:
|
||||
print(f"❌ 接口测试失败: {response.status_code}")
|
||||
print(response.text)
|
||||
```
|
||||
|
||||
运行测试:
|
||||
```bash
|
||||
python test_api.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**文档创建日期**: 2026-03-03
|
||||
**适用版本**: v1.4.0
|
||||
Reference in New Issue
Block a user