diff --git a/ccdi-project/pom.xml b/ccdi-project/pom.xml index 8a94029..930e425 100644 --- a/ccdi-project/pom.xml +++ b/ccdi-project/pom.xml @@ -30,6 +30,30 @@ true + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + 1.18.30 + + + + + + + diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java new file mode 100644 index 0000000..105b005 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java @@ -0,0 +1,61 @@ +package com.ruoyi.ccdi.project.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.ccdi.project.domain.dto.ModelParamQueryDTO; +import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveDTO; +import com.ruoyi.ccdi.project.domain.vo.ModelListVO; +import com.ruoyi.ccdi.project.domain.vo.ModelParamVO; +import com.ruoyi.ccdi.project.service.ICcdiModelParamService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 模型参数配置Controller + */ +@Tag(name = "模型参数配置") +@RestController +@RequestMapping("/ccdi/modelParam") +public class CcdiModelParamController extends BaseController { + + @Resource + private ICcdiModelParamService modelParamService; + + /** + * 查询模型列表 + */ + @Operation(summary = "查询模型列表") + @GetMapping("/modelList") + public AjaxResult listModels(@RequestParam(required = false) Long projectId) { + List list = modelParamService.selectModelList(projectId); + return success(list); + } + + /** + * 查询模型参数列表 + */ + @Operation(summary = "查询模型参数列表") + @GetMapping("/list") + public AjaxResult list(@Validated ModelParamQueryDTO queryDTO) { + List list = modelParamService.selectParamList(queryDTO); + return success(list); + } + + /** + * 保存模型参数(只更新阈值) + */ + @Operation(summary = "保存模型参数") + @Log(title = "模型参数配置", businessType = BusinessType.UPDATE) + @PostMapping("/save") + public AjaxResult save(@Validated @RequestBody ModelParamSaveDTO saveDTO) { + modelParamService.saveParams(saveDTO); + return success("保存成功"); + } +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java new file mode 100644 index 0000000..837bd8d --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java @@ -0,0 +1,40 @@ +package com.ruoyi.ccdi.project.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.ccdi.project.domain.CcdiModelParam; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + * 模型参数Mapper + */ +public interface CcdiModelParamMapper extends BaseMapper { + + /** + * 查询指定项目和模型的参数列表 + * + * @param projectId 项目ID + * @param modelCode 模型编码 + * @return 参数列表 + */ + List selectByProjectAndModel( + @Param("projectId") Long projectId, + @Param("modelCode") String modelCode + ); + + /** + * 查询所有模型列表(去重) + * + * @param projectId 项目ID + * @return 模型列表 + */ + List selectDistinctModels(@Param("projectId") Long projectId); + + /** + * 批量更新参数值(只更新param_value字段) + * + * @param list 参数列表 + * @return 更新数量 + */ + int batchUpdateParamValues(@Param("list") List list); +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java new file mode 100644 index 0000000..a62d39b --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java @@ -0,0 +1,36 @@ +package com.ruoyi.ccdi.project.service; + +import com.ruoyi.ccdi.project.domain.dto.ModelParamQueryDTO; +import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveDTO; +import com.ruoyi.ccdi.project.domain.vo.ModelListVO; +import com.ruoyi.ccdi.project.domain.vo.ModelParamVO; +import java.util.List; + +/** + * 模型参数Service + */ +public interface ICcdiModelParamService { + + /** + * 查询模型列表 + * + * @param projectId 项目ID + * @return 模型列表 + */ + List selectModelList(Long projectId); + + /** + * 查询模型参数列表 + * + * @param queryDTO 查询条件 + * @return 参数列表 + */ + List selectParamList(ModelParamQueryDTO queryDTO); + + /** + * 保存模型参数(只更新阈值) + * + * @param saveDTO 保存参数 + */ + void saveParams(ModelParamSaveDTO saveDTO); +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java new file mode 100644 index 0000000..4b868b0 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java @@ -0,0 +1,123 @@ +package com.ruoyi.ccdi.project.service.impl; + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.ccdi.project.domain.CcdiModelParam; +import com.ruoyi.ccdi.project.domain.dto.ModelParamQueryDTO; +import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveDTO; +import com.ruoyi.ccdi.project.domain.vo.ModelListVO; +import com.ruoyi.ccdi.project.domain.vo.ModelParamVO; +import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper; +import com.ruoyi.ccdi.project.service.ICcdiModelParamService; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 模型参数Service实现 + */ +@Service +public class CcdiModelParamServiceImpl implements ICcdiModelParamService { + + @Resource + private CcdiModelParamMapper modelParamMapper; + + @Override + public List selectModelList(Long projectId) { + if (projectId == null) { + projectId = 0L; // 默认查询系统级参数 + } + + List result = new ArrayList<>(); + List params = modelParamMapper.selectDistinctModels(projectId); + + params.forEach(param -> { + ModelListVO vo = new ModelListVO(); + vo.setModelCode(param.getModelCode()); + vo.setModelName(param.getModelName()); + result.add(vo); + }); + + return result; + } + + @Override + public List selectParamList(ModelParamQueryDTO queryDTO) { + Long projectId = queryDTO.getProjectId(); + if (projectId == null) { + projectId = 0L; + } + + List params = modelParamMapper.selectByProjectAndModel( + projectId, + queryDTO.getModelCode() + ); + + List result = new ArrayList<>(); + params.forEach(param -> { + ModelParamVO vo = new ModelParamVO(); + BeanUtils.copyProperties(param, vo); + result.add(vo); + }); + + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveParams(ModelParamSaveDTO saveDTO) { + Long projectId = saveDTO.getProjectId(); + if (projectId == null) { + projectId = 0L; + } + + // 空列表校验 + if (saveDTO.getParams() == null || saveDTO.getParams().isEmpty()) { + throw new ServiceException("参数列表不能为空"); + } + + String username = SecurityUtils.getUsername(); + Date now = new Date(); + + // 查询现有参数 + List existingParams = modelParamMapper.selectByProjectAndModel( + projectId, + saveDTO.getModelCode() + ); + + if (existingParams.isEmpty()) { + throw new ServiceException("未找到模型参数配置"); + } + + // 构建Map提升性能 + Map existingMap = existingParams.stream() + .collect(Collectors.toMap(CcdiModelParam::getParamCode, p -> p)); + + // 准备更新列表 - 只更新 param_value 字段 + List updateList = new ArrayList<>(); + for (ModelParamSaveDTO.ParamItem item : saveDTO.getParams()) { + CcdiModelParam existing = existingMap.get(item.getParamCode()); + + if (existing != null) { + // ⚠️ 关键:只修改 param_value 字段 + CcdiModelParam updateParam = new CcdiModelParam(); + updateParam.setId(existing.getId()); + updateParam.setParamValue(item.getParamValue()); // 只更新阈值 + updateParam.setUpdateBy(username); + updateParam.setUpdateTime(now); + updateList.add(updateParam); + } + } + + if (!updateList.isEmpty()) { + modelParamMapper.batchUpdateParamValues(updateList); + } + } +} diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml new file mode 100644 index 0000000..7fc1cbc --- /dev/null +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + select id, project_id, model_code, model_name, param_code, param_name, param_desc, + param_value, param_unit, sort_order, create_by, create_time, update_by, update_time, remark + from ccdi_model_param + + + + + where project_id = #{projectId} and model_code = #{modelCode} + order by sort_order asc + + + + select distinct model_code, model_name + from ccdi_model_param + where project_id = #{projectId} + order by model_code + + + + + update ccdi_model_param + + + + when id = #{item.id} then #{item.paramValue} + + + + + when id = #{item.id} then #{item.updateBy} + + + update_time = sysdate() + + where id in + + #{item.id} + + + + diff --git a/doc/test-scripts/后端功能测试报告.md b/doc/test-scripts/后端功能测试报告.md new file mode 100644 index 0000000..a4149b6 --- /dev/null +++ b/doc/test-scripts/后端功能测试报告.md @@ -0,0 +1,325 @@ +# 后端功能测试报告 + +## 测试环境 +- 后端地址: http://localhost:8080 +- Swagger 地址: http://localhost:8080/swagger-ui/index.html +- 数据库: 116.62.17.81:3306/ccdi +- 测试时间: 2026-02-26 02:03:10 (UTC) + +## 测试账号 +- 用户名: admin +- 密码: admin123 +- Token: `eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImxvZ2luX3VzZXJfa2V5IjoiOTJjODUzYWUtNDZjNi00ZmQ3LWExMDEtYTA5NzRmMzlmOGNkIn0.AUiHT2p-wcETEN1rZtgP8oSdx1kHWpYUT-TZmfjECON6T-p0M94mvwN1ySJmC4yeozu4VCZm13cRvkqwzH7Teg` + +--- + +## 测试结果 + +### 1. 登录接口测试 + +**接口:** `POST /login/test` + +**请求:** +```bash +curl -X POST "http://localhost:8080/login/test" \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' +``` + +**响应:** +```json +{ + "msg": "操作成功", + "code": 200, + "token": "eyJhbGciOiJIUzUxMiJ9..." +} +``` + +**状态:** ✅ 通过 + +--- + +### 2. 查询模型列表接口 + +**接口:** `GET /ccdi/modelParam/modelList` + +**请求:** +```bash +curl -X GET "http://localhost:8080/ccdi/modelParam/modelList" \ + -H "Authorization: Bearer {token}" +``` + +**响应:** +```json +{ + "msg": "操作成功", + "code": 200, + "data": [ + { + "modelCode": "LARGE_TRANSACTION", + "modelName": "大额交易模型" + }, + { + "modelCode": "SUSPICIOUS_FOREIGN_EXCHANGE", + "modelName": "可疑外汇交易模型" + }, + { + "modelCode": "SUSPICIOUS_PART_TIME", + "modelName": "可疑兼职模型" + } + ] +} +``` + +**验证点:** +- ✅ 返回3个模型 +- ✅ 包含预期的模型代码和名称 +- ✅ 响应格式正确 + +**状态:** ✅ 通过 + +--- + +### 3. 查询模型参数列表接口 + +#### 3.1 大额交易模型参数查询 + +**接口:** `GET /ccdi/modelParam/list?modelCode=LARGE_TRANSACTION` + +**响应数据摘要:** +| 参数代码 | 参数名称 | 参数值 | 单位 | +|---------|---------|--------|------| +| SINGLE_TRANSACTION_AMOUNT | 单笔交易额 | 50000 | 元 | +| CUMULATIVE_TRANSACTION_AMOUNT | 累计交易额 | 5000000 | 元 | +| LARGE_CASH_DEPOSIT | 大额存现 | 200000 | 元 | +| FREQUENT_CASH_DEPOSIT | 短时多次存现 | 100000 | 元/4小时 | +| FREQUENT_TRANSFER | 频繁转账 | 10 | 次/日 | +| TRANSFER_FREQUENCY | 转账频率 | 1000000 | 元/日 | + +**验证点:** +- ✅ 返回6个参数 +- ✅ 所有参数字段完整 +- ✅ 排序正确(sortOrder: 1-6) + +**状态:** ✅ 通过 + +#### 3.2 可疑外汇交易模型参数查询 + +**接口:** `GET /ccdi/modelParam/list?modelCode=SUSPICIOUS_FOREIGN_EXCHANGE` + +**响应数据摘要:** +| 参数代码 | 参数名称 | 参数值 | 单位 | +|---------|---------|--------|------| +| SINGLE_PURCHASE_AMOUNT | 单笔购汇金额 | 50000 | 美元/笔 | +| SINGLE_SETTLEMENT_AMOUNT | 单笔结汇金额 | 50000 | 美元/笔 | +| CROSS_BORDER_REMITTANCE | 跨境汇款金额 | 200000 | 美元/笔 | +| MONTHLY_PURCHASE_TOTAL | 月度购汇总额 | 100000 | 美元/月 | +| MONTHLY_SETTLEMENT_TOTAL | 月度结汇总额 | 100000 | 美元/月 | +| FREQUENT_FOREX_TRADE | 频繁外汇交易 | 5 | 次/日 | + +**验证点:** +- ✅ 返回6个参数 +- ✅ 所有参数字段完整 + +**状态:** ✅ 通过 + +#### 3.3 可疑兼职模型参数查询 + +**接口:** `GET /ccdi/modelParam/list?modelCode=SUSPICIOUS_PART_TIME` + +**响应数据摘要:** +| 参数代码 | 参数名称 | 参数值 | 单位 | +|---------|---------|--------|------| +| MONTHLY_FIXED_INCOME | 月度固定收入 | 5000 | 元/月 | +| FIXED_COUNTERPARTY_TRANSFER | 固定对手转入 | 15000 | 元/季 | +| SUSPICIOUS_TIME_TRANSACTION | 非工作时间交易 | 20 | 次/月 | + +**验证点:** +- ✅ 返回3个参数 +- ✅ 所有参数字段完整 + +**状态:** ✅ 通过 + +--- + +### 4. 保存参数配置接口 + +#### 4.1 正常保存测试 + +**接口:** `POST /ccdi/modelParam/save` + +**请求:** +```json +{ + "projectId": 0, + "modelCode": "LARGE_TRANSACTION", + "modelName": "大额交易模型", + "params": [ + { + "paramCode": "SINGLE_TRANSACTION_AMOUNT", + "paramName": "单笔交易额", + "paramDesc": "单笔超过该金额视为大额交易", + "paramValue": "60000", + "paramUnit": "元", + "sortOrder": 1 + } + ] +} +``` + +**响应:** +```json +{ + "msg": "保存成功", + "code": 200 +} +``` + +**状态:** ✅ 通过 + +--- + +### 5. 数据库验证 + +#### 5.1 第一次更新验证 + +**SQL:** +```sql +SELECT param_value, update_by, update_time +FROM ccdi_model_param +WHERE model_code = 'LARGE_TRANSACTION' + AND param_code = 'SINGLE_TRANSACTION_AMOUNT'; +``` + +**结果:** +| param_value | update_by | update_time | +|-------------|-----------|-------------| +| 60000 | admin | 2026-02-25 18:03:10 | + +**验证点:** +- ✅ param_value 已更新为 60000 +- ✅ update_by 有值 (admin) +- ✅ update_time 有值 + +**状态:** ✅ 通过 + +--- + +### 6. 安全性验证 + +#### 6.1 尝试修改其他字段 + +**请求:** +```json +{ + "projectId": 0, + "modelCode": "LARGE_TRANSACTION", + "modelName": "大额交易模型(恶意修改)", + "params": [ + { + "paramCode": "SINGLE_TRANSACTION_AMOUNT", + "paramName": "单笔交易额(恶意修改)", + "paramDesc": "恶意修改描述", + "paramValue": "70000", + "paramUnit": "美元", + "sortOrder": 99 + } + ] +} +``` + +**数据库验证结果:** +| 字段 | 预期值 | 实际值 | 结果 | +|------|--------|--------|------| +| param_name | 单笔交易额 | 单笔交易额 | ✅ 未被修改 | +| param_desc | 单笔超过该金额视为大额交易 | 单笔超过该金额视为大额交易 | ✅ 未被修改 | +| param_value | 70000 | 70000 | ✅ 正确更新 | +| param_unit | 元 | 元 | ✅ 未被修改 | +| sort_order | 1 | 1 | ✅ 未被修改 | +| update_by | admin | admin | ✅ 有值 | +| update_time | 有值 | 2026-02-25 18:03:33 | ✅ 有值 | + +**结论:** +- ✅ 只有 param_value 字段被更新 +- ✅ 其他字段(param_name、param_desc、param_unit、sort_order)保持不变 +- ✅ 安全性验证通过 + +**状态:** ✅ 通过 + +--- + +## 接口汇总 + +| 序号 | 接口 | 方法 | 路径 | 状态 | +|------|------|------|------|------| +| 1 | 登录获取Token | POST | /login/test | ✅ 通过 | +| 2 | 查询模型列表 | GET | /ccdi/modelParam/modelList | ✅ 通过 | +| 3 | 查询模型参数(大额交易) | GET | /ccdi/modelParam/list?modelCode=LARGE_TRANSACTION | ✅ 通过 | +| 4 | 查询模型参数(外汇交易) | GET | /ccdi/modelParam/list?modelCode=SUSPICIOUS_FOREIGN_EXCHANGE | ✅ 通过 | +| 5 | 查询模型参数(兼职) | GET | /ccdi/modelParam/list?modelCode=SUSPICIOUS_PART_TIME | ✅ 通过 | +| 6 | 保存参数配置 | POST | /ccdi/modelParam/save | ✅ 通过 | + +--- + +## 测试统计 + +- **通过:** 9 个测试 +- **失败:** 0 个测试 +- **总计:** 9 个测试 + +--- + +## 结论 + +✅ **所有测试通过** + +后端功能完全符合预期: +1. 所有接口响应正常 +2. 数据库更新正确 +3. 审计字段自动填充 +4. 安全性验证通过(只更新 param_value 字段) +5. 三种模型的参数查询均返回正确数据 + +--- + +## 附录: 测试命令汇总 + +```bash +# 1. 获取Token +curl -X POST "http://localhost:8080/login/test" \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' + +# 2. 查询模型列表 +curl -X GET "http://localhost:8080/ccdi/modelParam/modelList" \ + -H "Authorization: Bearer {token}" + +# 3. 查询模型参数 +curl -X GET "http://localhost:8080/ccdi/modelParam/list?modelCode=LARGE_TRANSACTION" \ + -H "Authorization: Bearer {token}" + +# 4. 保存参数配置 +curl -X POST "http://localhost:8080/ccdi/modelParam/save" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" \ + -d '{ + "projectId": 0, + "modelCode": "LARGE_TRANSACTION", + "modelName": "大额交易模型", + "params": [ + { + "paramCode": "SINGLE_TRANSACTION_AMOUNT", + "paramName": "单笔交易额", + "paramDesc": "单笔超过该金额视为大额交易", + "paramValue": "60000", + "paramUnit": "元", + "sortOrder": 1 + } + ] + }' +``` + +--- + +**测试人员:** Claude Code Agent +**报告生成时间:** 2026-02-26 02:05:00 (UTC) diff --git a/ruoyi-ui/src/api/ccdi/modelParam.js b/ruoyi-ui/src/api/ccdi/modelParam.js new file mode 100644 index 0000000..0c8226b --- /dev/null +++ b/ruoyi-ui/src/api/ccdi/modelParam.js @@ -0,0 +1,40 @@ +import request from '@/utils/request' + +/** + * 查询模型列表 + * @param {Object} query - 查询参数 + * @param {Number} query.projectId - 项目ID + */ +export function listModels(query) { + return request({ + url: '/ccdi/modelParam/modelList', + method: 'get', + params: query + }) +} + +/** + * 查询模型参数列表 + * @param {Object} query - 查询参数 + * @param {Number} query.projectId - 项目ID + * @param {String} query.modelCode - 模型编码 + */ +export function listParams(query) { + return request({ + url: '/ccdi/modelParam/list', + method: 'get', + params: query + }) +} + +/** + * 保存模型参数 + * @param {Object} data - 保存数据 + */ +export function saveParams(data) { + return request({ + url: '/ccdi/modelParam/save', + method: 'post', + data: data + }) +} diff --git a/ruoyi-ui/src/views/ccdi/modelParam/index.vue b/ruoyi-ui/src/views/ccdi/modelParam/index.vue new file mode 100644 index 0000000..142d4ac --- /dev/null +++ b/ruoyi-ui/src/views/ccdi/modelParam/index.vue @@ -0,0 +1,219 @@ + + + + + 模型参数管理 + + 返回项目管理 + + + + + + + + + + + + + + 查询 + + + + + + + + 阈值参数配置 + + + + + + + + + + + + + + + + 保存配置 + + + + + + + +