Merge branch 'feature/model-param-config' into dev
This commit is contained in:
@@ -30,6 +30,30 @@
|
|||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- spring-doc -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -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<ModelListVO> list = modelParamService.selectModelList(projectId);
|
||||||
|
return success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询模型参数列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "查询模型参数列表")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public AjaxResult list(@Validated ModelParamQueryDTO queryDTO) {
|
||||||
|
List<ModelParamVO> 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("保存成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CcdiModelParam> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定项目和模型的参数列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param modelCode 模型编码
|
||||||
|
* @return 参数列表
|
||||||
|
*/
|
||||||
|
List<CcdiModelParam> selectByProjectAndModel(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("modelCode") String modelCode
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有模型列表(去重)
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 模型列表
|
||||||
|
*/
|
||||||
|
List<CcdiModelParam> selectDistinctModels(@Param("projectId") Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新参数值(只更新param_value字段)
|
||||||
|
*
|
||||||
|
* @param list 参数列表
|
||||||
|
* @return 更新数量
|
||||||
|
*/
|
||||||
|
int batchUpdateParamValues(@Param("list") List<CcdiModelParam> list);
|
||||||
|
}
|
||||||
@@ -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<ModelListVO> selectModelList(Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询模型参数列表
|
||||||
|
*
|
||||||
|
* @param queryDTO 查询条件
|
||||||
|
* @return 参数列表
|
||||||
|
*/
|
||||||
|
List<ModelParamVO> selectParamList(ModelParamQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存模型参数(只更新阈值)
|
||||||
|
*
|
||||||
|
* @param saveDTO 保存参数
|
||||||
|
*/
|
||||||
|
void saveParams(ModelParamSaveDTO saveDTO);
|
||||||
|
}
|
||||||
@@ -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<ModelListVO> selectModelList(Long projectId) {
|
||||||
|
if (projectId == null) {
|
||||||
|
projectId = 0L; // 默认查询系统级参数
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ModelListVO> result = new ArrayList<>();
|
||||||
|
List<CcdiModelParam> 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<ModelParamVO> selectParamList(ModelParamQueryDTO queryDTO) {
|
||||||
|
Long projectId = queryDTO.getProjectId();
|
||||||
|
if (projectId == null) {
|
||||||
|
projectId = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(
|
||||||
|
projectId,
|
||||||
|
queryDTO.getModelCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ModelParamVO> 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<CcdiModelParam> existingParams = modelParamMapper.selectByProjectAndModel(
|
||||||
|
projectId,
|
||||||
|
saveDTO.getModelCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingParams.isEmpty()) {
|
||||||
|
throw new ServiceException("未找到模型参数配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建Map提升性能
|
||||||
|
Map<String, CcdiModelParam> existingMap = existingParams.stream()
|
||||||
|
.collect(Collectors.toMap(CcdiModelParam::getParamCode, p -> p));
|
||||||
|
|
||||||
|
// 准备更新列表 - 只更新 param_value 字段
|
||||||
|
List<CcdiModelParam> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?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.project.mapper.CcdiModelParamMapper">
|
||||||
|
|
||||||
|
<resultMap id="ModelParamResult" type="com.ruoyi.ccdi.project.domain.CcdiModelParam">
|
||||||
|
<id property="id" column="id"/>
|
||||||
|
<result property="projectId" column="project_id"/>
|
||||||
|
<result property="modelCode" column="model_code"/>
|
||||||
|
<result property="modelName" column="model_name"/>
|
||||||
|
<result property="paramCode" column="param_code"/>
|
||||||
|
<result property="paramName" column="param_name"/>
|
||||||
|
<result property="paramDesc" column="param_desc"/>
|
||||||
|
<result property="paramValue" column="param_value"/>
|
||||||
|
<result property="paramUnit" column="param_unit"/>
|
||||||
|
<result property="sortOrder" column="sort_order"/>
|
||||||
|
<result property="createBy" column="create_by"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="updateBy" column="update_by"/>
|
||||||
|
<result property="updateTime" column="update_time"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectModelParamVo">
|
||||||
|
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
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectByProjectAndModel" resultMap="ModelParamResult">
|
||||||
|
<include refid="selectModelParamVo"/>
|
||||||
|
where project_id = #{projectId} and model_code = #{modelCode}
|
||||||
|
order by sort_order asc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectDistinctModels" resultMap="ModelParamResult">
|
||||||
|
select distinct model_code, model_name
|
||||||
|
from ccdi_model_param
|
||||||
|
where project_id = #{projectId}
|
||||||
|
order by model_code
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 关键:只更新 param_value 字段,使用 CASE WHEN 批量更新 -->
|
||||||
|
<update id="batchUpdateParamValues">
|
||||||
|
update ccdi_model_param
|
||||||
|
<set>
|
||||||
|
<trim prefix="param_value = case" suffix="end,">
|
||||||
|
<foreach collection="list" item="item">
|
||||||
|
when id = #{item.id} then #{item.paramValue}
|
||||||
|
</foreach>
|
||||||
|
</trim>
|
||||||
|
<trim prefix="update_by = case" suffix="end,">
|
||||||
|
<foreach collection="list" item="item">
|
||||||
|
when id = #{item.id} then #{item.updateBy}
|
||||||
|
</foreach>
|
||||||
|
</trim>
|
||||||
|
update_time = sysdate()
|
||||||
|
</set>
|
||||||
|
where id in
|
||||||
|
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||||
|
#{item.id}
|
||||||
|
</foreach>
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
325
doc/test-scripts/后端功能测试报告.md
Normal file
325
doc/test-scripts/后端功能测试报告.md
Normal file
@@ -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)
|
||||||
40
ruoyi-ui/src/api/ccdi/modelParam.js
Normal file
40
ruoyi-ui/src/api/ccdi/modelParam.js
Normal file
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
219
ruoyi-ui/src/views/ccdi/modelParam/index.vue
Normal file
219
ruoyi-ui/src/views/ccdi/modelParam/index.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 顶部标题 -->
|
||||||
|
<div class="header">
|
||||||
|
<span class="title">模型参数管理</span>
|
||||||
|
<router-link :to="{ path: '/project/manage' }" class="link">
|
||||||
|
<i class="el-icon-arrow-left"></i> 返回项目管理
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查询筛选区 -->
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-form :inline="true" :model="queryParams" ref="queryForm">
|
||||||
|
<el-form-item label="模型名称" prop="modelCode">
|
||||||
|
<el-select v-model="queryParams.modelCode" placeholder="请选择模型">
|
||||||
|
<el-option
|
||||||
|
v-for="model in modelList"
|
||||||
|
:key="model.modelCode"
|
||||||
|
:label="model.modelName"
|
||||||
|
:value="model.modelCode"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" @click="handleQuery">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 参数配置表格 -->
|
||||||
|
<div class="table-container">
|
||||||
|
<h3 class="table-title">阈值参数配置</h3>
|
||||||
|
<el-table :data="paramList" border style="width: 100%">
|
||||||
|
<el-table-column label="监测项" prop="paramName" width="200" />
|
||||||
|
<el-table-column label="描述" prop="paramDesc" />
|
||||||
|
<el-table-column label="阈值设置" width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input
|
||||||
|
v-model="row.paramValue"
|
||||||
|
placeholder="请输入阈值"
|
||||||
|
@input="markAsModified(row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="单位" prop="paramUnit" width="120" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="button-container">
|
||||||
|
<el-button type="primary" @click="handleSave" :loading="saving">
|
||||||
|
保存配置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listModels, listParams, saveParams } from "@/api/ccdi/modelParam";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ModelParam",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 模型列表
|
||||||
|
modelList: [],
|
||||||
|
// 查询参数
|
||||||
|
queryParams: {
|
||||||
|
modelCode: undefined,
|
||||||
|
projectId: 0, // 默认查询系统级参数
|
||||||
|
},
|
||||||
|
// 参数列表
|
||||||
|
paramList: [],
|
||||||
|
// 保存中状态
|
||||||
|
saving: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getModelList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 查询模型列表 */
|
||||||
|
getModelList() {
|
||||||
|
listModels({ projectId: this.queryParams.projectId }).then((response) => {
|
||||||
|
this.modelList = response.data;
|
||||||
|
if (this.modelList.length > 0) {
|
||||||
|
this.queryParams.modelCode = this.modelList[0].modelCode;
|
||||||
|
this.handleQuery();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询参数列表 */
|
||||||
|
handleQuery() {
|
||||||
|
if (!this.queryParams.modelCode) {
|
||||||
|
this.$message.warning("请选择模型");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listParams(this.queryParams).then((response) => {
|
||||||
|
this.paramList = response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 标记参数为已修改 */
|
||||||
|
markAsModified(row) {
|
||||||
|
row.modified = true;
|
||||||
|
},
|
||||||
|
/** 保存配置 */
|
||||||
|
handleSave() {
|
||||||
|
if (!this.queryParams.modelCode) {
|
||||||
|
this.$message.warning("请选择模型");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只保存修改过的参数值
|
||||||
|
const modifiedParams = this.paramList.filter((item) => item.modified);
|
||||||
|
if (modifiedParams.length === 0) {
|
||||||
|
this.$message.info("没有需要保存的修改");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveDTO = {
|
||||||
|
projectId: this.queryParams.projectId,
|
||||||
|
modelCode: this.queryParams.modelCode,
|
||||||
|
modelName: this.modelList.find(
|
||||||
|
(m) => m.modelCode === this.queryParams.modelCode
|
||||||
|
)?.modelName,
|
||||||
|
params: modifiedParams.map((item) => ({
|
||||||
|
paramCode: item.paramCode,
|
||||||
|
paramName: item.paramName,
|
||||||
|
paramDesc: item.paramDesc,
|
||||||
|
paramValue: item.paramValue,
|
||||||
|
paramUnit: item.paramUnit,
|
||||||
|
sortOrder: item.sortOrder,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
saveParams(saveDTO)
|
||||||
|
.then((response) => {
|
||||||
|
this.$modal.msgSuccess("保存成功");
|
||||||
|
// 清除修改标记
|
||||||
|
this.paramList.forEach((item) => {
|
||||||
|
item.modified = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.saving = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #1890ff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
padding: 15px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.table-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
padding: 15px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user