24 KiB
24 KiB
模型参数配置页面优化设计文档
文档版本: v1.0 创建日期: 2026-03-06 设计人员: Claude Code
一、概述
1.1 背景
当前模型参数配置页面采用模型下拉框切换的方式,用户需要逐个切换模型才能查看和配置不同模型的参数,操作不够便捷。本次优化旨在取消模型切换,改为在同一页面中以垂直堆叠方式展示所有模型的参数表格,提升用户体验。
1.2 目标
- ✅ 取消模型名称查询切换
- ✅ 在同一页面中分多个表格展示所有模型的参数
- ✅ 全局模型参数配置页面和项目内模型参数配置页面同步修改
- ✅ 统一保存机制,一次性保存所有修改
1.3 影响范围
前端页面:
ruoyi-ui/src/views/ccdi/modelParam/index.vue- 全局模型参数配置页面ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue- 项目内参数配置页面
后端接口:
CcdiModelParamController.java- 新增批量查询和批量保存接口ICcdiModelParamService.java- 新增Service方法CcdiModelParamServiceImpl.java- 实现批量操作逻辑CcdiModelParamMapper.java- 新增Mapper方法CcdiModelParamMapper.xml- 新增SQL查询
二、详细设计
2.1 后端接口设计
2.1.1 批量查询所有模型参数
接口路径: GET /ccdi/modelParam/listAll
请求参数:
public class ModelParamAllQueryDTO {
/** 项目ID(0表示全局配置,>0表示项目配置) */
private Long projectId;
}
响应结构:
public class ModelParamAllVO {
/** 模型列表(包含每个模型及其参数) */
private List<ModelGroupVO> models;
}
public class ModelGroupVO {
/** 模型编码 */
private String modelCode;
/** 模型名称 */
private String modelName;
/** 参数列表 */
private List<ModelParamVO> params;
}
返回数据示例:
{
"code": 200,
"msg": "操作成功",
"data": {
"models": [
{
"modelCode": "LARGE_TRANSACTION",
"modelName": "大额交易模型",
"params": [
{
"paramCode": "THRESHOLD_AMOUNT",
"paramName": "单笔交易金额阈值",
"paramDesc": "单笔交易金额超过此值触发预警",
"paramValue": "50000",
"paramUnit": "元",
"sortOrder": 1
}
]
},
{
"modelCode": "SUSPICIOUS_FOREIGN_EXCHANGE",
"modelName": "可疑外汇交易模型",
"params": [
{
"paramCode": "FREQUENCY_THRESHOLD",
"paramName": "交易频次阈值",
"paramDesc": "交易频次超过此值触发预警",
"paramValue": "10",
"paramUnit": "次/天",
"sortOrder": 1
}
]
},
{
"modelCode": "SUSPICIOUS_PART_TIME",
"modelName": "可疑兼职模型",
"params": [...]
}
]
}
}
2.1.2 批量保存所有模型参数
接口路径: POST /ccdi/modelParam/saveAll
请求结构:
public class ModelParamSaveAllDTO {
/** 项目ID */
private Long projectId;
/** 所有模型的参数修改(只包含修改过的参数) */
private List<ModelParamGroupDTO> models;
}
public class ModelParamGroupDTO {
/** 模型编码 */
private String modelCode;
/** 该模型下修改过的参数 */
private List<ParamValueItem> params;
}
public class ParamValueItem {
private String paramCode;
private String paramValue;
}
请求示例:
{
"projectId": 1,
"models": [
{
"modelCode": "LARGE_TRANSACTION",
"params": [
{
"paramCode": "THRESHOLD_AMOUNT",
"paramValue": "60000"
}
]
},
{
"modelCode": "SUSPICIOUS_FOREIGN_EXCHANGE",
"params": [
{
"paramCode": "FREQUENCY_THRESHOLD",
"paramValue": "5"
}
]
}
]
}
响应示例:
{
"code": 200,
"msg": "保存成功"
}
错误码说明:
| 错误码 | 说明 |
|---|---|
| 400 | 参数验证失败(项目ID为空、参数列表为空等) |
| 500 | 服务器内部错误(数据库操作失败等) |
2.2 后端Service层设计
2.2.1 Service接口新增方法
public interface ICcdiModelParamService {
/**
* 查询所有模型及其参数(按模型分组)
*
* @param projectId 项目ID(0表示全局配置)
* @return 所有模型的参数配置
*/
ModelParamAllVO selectAllParams(Long projectId);
/**
* 批量保存所有模型的参数修改
*
* @param saveAllDTO 所有模型的参数修改数据
*/
void saveAllParams(ModelParamSaveAllDTO saveAllDTO);
// ... 保留原有的其他方法
}
2.2.2 Service实现类核心逻辑
查询所有模型参数:
@Override
public ModelParamAllVO selectAllParams(Long projectId) {
// 1. 参数验证
if (projectId == null) {
projectId = 0L;
}
// 2. 如果是项目查询,根据 configType 决定查询哪组参数
Long effectiveProjectId = projectId;
if (projectId > 0) {
CcdiProject project = projectMapper.selectById(projectId);
if (project == null) {
throw new ServiceException("项目不存在");
}
if ("default".equals(project.getConfigType())) {
effectiveProjectId = 0L;
}
}
// 3. 查询所有模型的参数
List<CcdiModelParam> allParams = modelParamMapper.selectByProjectId(effectiveProjectId);
// 4. 按模型分组
Map<String, List<CcdiModelParam>> groupedParams = allParams.stream()
.collect(Collectors.groupingBy(CcdiModelParam::getModelCode));
// 5. 转换为VO
ModelParamAllVO result = new ModelParamAllVO();
List<ModelGroupVO> models = new ArrayList<>();
groupedParams.forEach((modelCode, params) -> {
ModelGroupVO groupVO = new ModelGroupVO();
groupVO.setModelCode(modelCode);
groupVO.setModelName(params.get(0).getModelName());
List<ModelParamVO> paramVOs = params.stream()
.map(param -> {
ModelParamVO vo = new ModelParamVO();
BeanUtils.copyProperties(param, vo);
return vo;
})
.collect(Collectors.toList());
groupVO.setParams(paramVOs);
models.add(groupVO);
});
// 6. 按模型编码排序(保证固定顺序)
models.sort(Comparator.comparing(ModelGroupVO::getModelCode));
result.setModels(models);
return result;
}
批量保存参数:
@Override
@Transactional(rollbackFor = Exception.class)
public void saveAllParams(ModelParamSaveAllDTO saveAllDTO) {
try {
// 1. 参数验证
if (saveAllDTO.getProjectId() == null) {
throw new ServiceException("项目ID不能为空");
}
if (saveAllDTO.getModels() == null || saveAllDTO.getModels().isEmpty()) {
throw new ServiceException("参数列表不能为空");
}
Long projectId = saveAllDTO.getProjectId();
// 2. 如果是项目保存,检查是否需要复制默认参数
if (projectId > 0) {
CcdiProject project = projectMapper.selectById(projectId);
if (project == null) {
throw new ServiceException("项目不存在");
}
// 如果是首次保存(configType=default),需要复制所有模型的系统默认参数
if ("default".equals(project.getConfigType())) {
for (ModelParamGroupDTO modelGroup : saveAllDTO.getModels()) {
copyDefaultParamsToProject(projectId, modelGroup.getModelCode());
}
// 更新项目配置类型为 custom
project.setConfigType("custom");
projectMapper.updateById(project);
}
}
// 3. 批量更新所有模型的参数值
String username = SecurityUtils.getUsername();
for (ModelParamGroupDTO modelGroup : saveAllDTO.getModels()) {
for (ParamValueItem item : modelGroup.getParams()) {
int updated = modelParamMapper.updateParamValue(
projectId,
modelGroup.getModelCode(),
item.getParamCode(),
item.getParamValue()
);
if (updated == 0) {
log.warn("参数不存在或未更新,modelCode={}, paramCode={}",
modelGroup.getModelCode(), item.getParamCode());
}
}
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.error("批量保存模型参数失败", e);
throw new ServiceException("批量保存模型参数失败:" + e.getMessage());
}
}
2.3 后端Mapper层设计
2.3.1 Mapper接口新增方法
public interface CcdiModelParamMapper extends BaseMapper<CcdiModelParam> {
/**
* 根据项目ID查询所有模型参数
*/
List<CcdiModelParam> selectByProjectId(@Param("projectId") Long projectId);
// ... 保留原有的其他方法
}
2.3.2 Mapper XML
<select id="selectByProjectId" resultType="CcdiModelParam">
SELECT * FROM ccdi_model_param
WHERE project_id = #{projectId}
ORDER BY model_code, sort_order
</select>
2.4 前端组件设计
2.4.1 页面结构(两个页面相同布局)
<template>
<div class="param-config-container">
<!-- 页面标题 -->
<div class="page-header">
<h2>{{ pageTitle }}</h2>
</div>
<!-- 模型参数卡片组(垂直堆叠) -->
<div class="model-cards-container">
<div
v-for="model in modelGroups"
:key="model.modelCode"
class="model-card"
>
<!-- 模型标题 -->
<div class="model-header">
<h3>{{ model.modelName }}</h3>
</div>
<!-- 参数表格 -->
<el-table :data="model.params" border>
<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(model.modelCode, row)"
/>
</template>
</el-table-column>
<el-table-column label="单位" prop="paramUnit" width="120" />
</el-table>
</div>
</div>
<!-- 统一保存按钮 -->
<div class="button-section">
<el-button type="primary" @click="handleSaveAll" :loading="saving">
保存所有修改
</el-button>
<span v-if="modifiedCount > 0" class="modified-tip">
已修改 {{ modifiedCount }} 个参数
</span>
</div>
</div>
</template>
2.4.2 核心数据结构
data() {
return {
// 页面标题(全局配置 vs 项目配置)
pageTitle: this.projectId ? '项目参数配置' : '全局模型参数管理',
// 模型参数数据(按模型分组)
modelGroups: [], // ModelGroupVO[]
// 修改记录(记录哪些参数被修改过)
modifiedParams: new Map(), // Map<modelCode, Set<paramCode>>
// 保存状态
saving: false
}
}
2.4.3 核心方法
methods: {
/** 加载所有模型参数 */
async loadAllParams() {
try {
const res = await listAllParams({ projectId: this.projectId })
this.modelGroups = res.data.models
// 清空修改记录
this.modifiedParams.clear()
} catch (error) {
this.$message.error('加载参数失败:' + error.message)
}
},
/** 标记参数为已修改 */
markAsModified(modelCode, row) {
if (!this.modifiedParams.has(modelCode)) {
this.modifiedParams.set(modelCode, new Set())
}
this.modifiedParams.get(modelCode).add(row.paramCode)
},
/** 保存所有修改 */
async handleSaveAll() {
// 验证是否有修改
if (this.modifiedCount === 0) {
this.$message.info('没有需要保存的修改')
return
}
// 构造保存数据(只包含修改过的参数)
const saveDTO = {
projectId: this.projectId,
models: []
}
this.modifiedParams.forEach((paramCodes, modelCode) => {
const modelGroup = this.modelGroups.find(m => m.modelCode === modelCode)
const modifiedParamList = modelGroup.params
.filter(p => paramCodes.has(p.paramCode))
.map(p => ({
paramCode: p.paramCode,
paramValue: p.paramValue
}))
if (modifiedParamList.length > 0) {
saveDTO.models.push({
modelCode: modelCode,
params: modifiedParamList
})
}
})
// 保存
this.saving = true
try {
await saveAllParams(saveDTO)
this.$message.success('保存成功')
// 清空修改记录并重新加载
this.modifiedParams.clear()
await this.loadAllParams()
} catch (error) {
this.$message.error('保存失败:' + error.message)
} finally {
this.saving = false
}
}
},
computed: {
/** 计算已修改参数数量 */
modifiedCount() {
let count = 0
this.modifiedParams.forEach(params => {
count += params.size
})
return count
}
}
2.4.4 样式设计
.param-config-container {
padding: 20px;
background-color: #fff;
min-height: 400px;
}
.page-header {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 4px;
h2 {
font-size: 18px;
font-weight: bold;
color: #333;
margin: 0;
}
}
.model-cards-container {
margin-bottom: 20px;
}
.model-card {
background: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #e4e7ed;
.model-header {
margin-bottom: 15px;
h3 {
font-size: 16px;
font-weight: bold;
color: #333;
margin: 0;
}
}
}
.button-section {
padding: 15px;
background: #fff;
border-radius: 4px;
text-align: left;
.modified-tip {
margin-left: 15px;
color: #909399;
font-size: 14px;
}
}
2.5 前端API层设计
在 ruoyi-ui/src/api/ccdi/modelParam.js 中添加:
import request from '@/utils/request'
/**
* 查询所有模型及其参数(按模型分组)
*/
export function listAllParams(query) {
return request({
url: '/ccdi/modelParam/listAll',
method: 'get',
params: query
})
}
/**
* 批量保存所有模型的参数修改
*/
export function saveAllParams(data) {
return request({
url: '/ccdi/modelParam/saveAll',
method: 'post',
data: data
})
}
// 保留原有的其他API方法...
三、数据库设计
无需修改数据库表结构,现有的 ccdi_model_param 表结构已满足需求。
现有表结构说明:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键ID |
| project_id | BIGINT | 项目ID(0表示默认参数) |
| model_code | VARCHAR | 模型编码 |
| model_name | VARCHAR | 模型名称 |
| param_code | VARCHAR | 参数编码 |
| param_name | VARCHAR | 监测项名称 |
| param_desc | VARCHAR | 参数描述 |
| param_value | VARCHAR | 参数值 |
| param_unit | VARCHAR | 参数单位 |
| sort_order | INT | 排序号 |
| create_by | VARCHAR | 创建者 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR | 更新者 |
| update_time | DATETIME | 更新时间 |
| remark | VARCHAR | 备注 |
索引说明:
- 主键:
id - 常用查询索引:
idx_project_model(project_id,model_code)
四、实现步骤
4.1 后端开发任务
第一阶段:DTO/VO类创建
- 创建
ModelParamAllQueryDTO.java- 批量查询请求DTO - 创建
ModelParamAllVO.java- 批量查询响应VO - 创建
ModelGroupVO.java- 模型分组VO - 创建
ModelParamSaveAllDTO.java- 批量保存请求DTO - 创建
ModelParamGroupDTO.java- 模型参数分组DTO
第二阶段:Mapper层修改
- 在
CcdiModelParamMapper.java中添加selectByProjectId方法 - 在
CcdiModelParamMapper.xml中添加对应的SQL查询
第三阶段:Service层修改
- 在
ICcdiModelParamService.java接口中添加selectAllParams方法 - 在
ICcdiModelParamService.java接口中添加saveAllParams方法 - 在
CcdiModelParamServiceImpl.java中实现selectAllParams方法 - 在
CcdiModelParamServiceImpl.java中实现saveAllParams方法
第四阶段:Controller层修改
- 在
CcdiModelParamController.java中添加listAll接口(GET) - 在
CcdiModelParamController.java中添加saveAll接口(POST)
第五阶段:后端测试
- 使用 Swagger 测试
listAll接口- 测试全局配置查询(projectId=0)
- 测试项目配置查询(projectId>0)
- 测试使用默认配置的项目(configType=default)
- 使用 Swagger 测试
saveAll接口- 测试全局配置保存
- 测试项目首次保存(验证参数复制逻辑)
- 测试项目二次保存
- 测试多模型同时保存
- 验证错误处理
- 参数验证失败
- 项目不存在
- 数据库异常
4.2 前端开发任务
第一阶段:API层修改
- 在
ruoyi-ui/src/api/ccdi/modelParam.js中添加listAllParams方法 - 在
ruoyi-ui/src/api/ccdi/modelParam.js中添加saveAllParams方法
第二阶段:全局配置页面重构
- 重构
ruoyi-ui/src/views/ccdi/modelParam/index.vue- 去掉模型下拉框
- 添加页面标题
- 实现垂直堆叠布局展示所有模型
- 实现参数修改跟踪
- 实现统一保存按钮
- 添加修改提示(显示已修改参数数量)
- 优化样式
第三阶段:项目配置页面重构
- 重构
ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue- 采用与全局配置页面相同的布局和逻辑
- 适配 projectId 传递
- 适配项目信息显示
第四阶段:前端测试
- 测试全局配置页面
- 页面加载是否正确显示所有模型
- 参数修改和标记是否正常
- 统一保存功能是否正常
- 修改提示是否准确
- 测试项目配置页面
- 页面加载是否正确显示所有模型
- 参数修改和保存功能是否正常
- 使用默认配置的项目是否正确显示系统参数
- 首次保存是否成功
- 测试用户体验
- 页面加载速度
- 操作流畅性
- 错误提示友好性
五、兼容性与迁移说明
5.1 向后兼容
保留原有接口:
- 原有的
GET /list接口保留,不影响其他可能的调用方 - 原有的
POST /save接口保留,继续可用
数据库无变更:
- 数据库表结构无修改
- 现有数据无需迁移
5.2 废弃说明
功能废弃:
- 前端的模型下拉框切换方式不再使用
- 但后端接口仍保留,以确保向后兼容
建议:
- 逐步迁移所有调用方到新接口
- 未来版本可以废弃旧接口
六、性能考虑
6.1 查询性能
优化措施:
- 使用
selectByProjectId一次性查询所有参数,减少数据库往返 - 在内存中按模型分组,避免多次查询
- 利用现有的
idx_project_model索引
预期性能:
- 当前模型数量:3个
- 预计参数总数:约30个
- 单次查询时间:<50ms
- 完全满足性能要求
6.2 保存性能
优化措施:
- 只保存修改过的参数,减少数据库更新操作
- 使用事务保证数据一致性
- 批量更新,避免多次提交
预期性能:
- 典型修改场景:1-5个参数
- 保存时间:<100ms
- 完全满足性能要求
6.3 前端性能
优化措施:
- 使用
v-for高效渲染列表 - 使用计算属性缓存已修改参数数量
- 避免不必要的重渲染
预期性能:
- 页面渲染时间:<200ms
- 操作响应时间:<50ms
- 完全满足用户体验要求
七、安全考虑
7.1 权限控制
现有权限机制:
- 使用 Spring Security + JWT 进行认证
- 基于角色的访问控制(RBAC)
- 新接口继承现有权限控制机制
权限标识:
- 查询:
ccdi:modelParam:list - 保存:
ccdi:modelParam:edit
7.2 数据验证
后端验证:
- 使用
@Validated注解进行参数验证 - 验证项目ID、模型编码、参数编码的合法性
- 验证参数值的格式和范围
前端验证:
- 参数值非空验证
- 参数值格式验证
7.3 数据一致性
事务管理:
- 使用
@Transactional保证批量保存的原子性 - 保存失败时自动回滚
并发控制:
- 使用乐观锁或悲观锁(根据实际并发情况决定)
- 当前场景并发量低,无需特殊处理
八、测试策略
8.1 单元测试
Service层测试:
- 测试
selectAllParams方法- 测试全局配置查询
- 测试项目配置查询
- 测试使用默认配置的项目
- 测试空数据情况
- 测试
saveAllParams方法- 测试参数验证
- 测试首次保存(参数复制)
- 测试二次保存
- 测试事务回滚
8.2 集成测试
API接口测试:
- 使用 Swagger UI 进行接口测试
- 测试各种参数组合
- 测试错误场景
8.3 前端测试
功能测试:
- 测试页面加载和渲染
- 测试参数修改和标记
- 测试保存功能
- 测试错误处理
用户体验测试:
- 测试页面响应速度
- 测试操作流畅性
- 测试错误提示友好性
九、风险评估
9.1 技术风险
| 风险 | 概率 | 影响 | 应对措施 |
|---|---|---|---|
| 后端接口设计不合理 | 低 | 中 | 充分设计评审,参考现有接口 |
| 前端组件复杂度高 | 低 | 低 | 采用简单清晰的组件结构 |
| 数据库查询性能差 | 极低 | 中 | 已有索引支持,数据量小 |
| 批量保存失败 | 低 | 高 | 使用事务保证原子性 |
9.2 业务风险
| 风险 | 概率 | 影响 | 应对措施 |
|---|---|---|---|
| 用户不习惯新界面 | 中 | 低 | 提供用户培训,界面简洁直观 |
| 误操作导致参数错误 | 低 | 高 | 添加确认提示,记录操作日志 |
| 保存时数据丢失 | 极低 | 高 | 使用事务,添加错误处理 |
9.3 兼容性风险
| 风险 | 概率 | 影响 | 应对措施 |
|---|---|---|---|
| 旧接口调用方受影响 | 低 | 低 | 保留旧接口,逐步迁移 |
| 数据库不兼容 | 极低 | 高 | 无数据库结构变更 |
十、上线计划
10.1 上线前准备
- 完成所有开发任务
- 完成所有测试任务
- 准备上线文档
- 准备回滚方案
10.2 上线步骤
-
后端部署
- 停止应用服务
- 部署新版本代码
- 启动应用服务
- 验证接口可用性
-
前端部署
- 构建前端代码
- 部署到服务器
- 清理浏览器缓存
- 验证页面可用性
-
功能验证
- 测试全局配置页面
- 测试项目配置页面
- 验证保存功能
- 验证数据一致性
10.3 上线后监控
- 监控接口响应时间
- 监控错误日志
- 收集用户反馈
- 准备问题修复
10.4 回滚方案
如果出现严重问题:
- 前端回滚到旧版本
- 后端回滚到旧版本(接口保留不影响)
- 数据无需回滚(无数据库变更)
十一、总结
本次设计采用了优化接口的方案,通过新增批量查询和批量保存接口,实现了在同一页面中展示和编辑所有模型参数的需求。设计充分考虑了性能、安全性、兼容性和可维护性,是一个可行且高效的解决方案。
设计亮点:
- ✅ 接口设计合理,易于理解和扩展
- ✅ 前后端分离,逻辑清晰
- ✅ 保留向后兼容,降低风险
- ✅ 性能优化,用户体验好
- ✅ 代码复用性高,可维护性好
预期收益:
- 🎯 提升用户体验,减少操作步骤
- 🎯 提高工作效率,一次查看所有模型
- 🎯 降低误操作风险,统一保存机制
- 🎯 代码结构更清晰,便于后续维护
附录
A. 相关文档
B. 变更记录
| 版本 | 日期 | 修改人 | 修改内容 |
|---|---|---|---|
| v1.0 | 2026-03-06 | Claude Code | 初始版本 |
文档结束