# 模型参数配置页面优化设计文档 **文档版本:** 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` **请求参数:** ```java public class ModelParamAllQueryDTO { /** 项目ID(0表示全局配置,>0表示项目配置) */ private Long projectId; } ``` **响应结构:** ```java public class ModelParamAllVO { /** 模型列表(包含每个模型及其参数) */ private List models; } public class ModelGroupVO { /** 模型编码 */ private String modelCode; /** 模型名称 */ private String modelName; /** 参数列表 */ private List params; } ``` **返回数据示例:** ```json { "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` **请求结构:** ```java public class ModelParamSaveAllDTO { /** 项目ID */ private Long projectId; /** 所有模型的参数修改(只包含修改过的参数) */ private List models; } public class ModelParamGroupDTO { /** 模型编码 */ private String modelCode; /** 该模型下修改过的参数 */ private List params; } public class ParamValueItem { private String paramCode; private String paramValue; } ``` **请求示例:** ```json { "projectId": 1, "models": [ { "modelCode": "LARGE_TRANSACTION", "params": [ { "paramCode": "THRESHOLD_AMOUNT", "paramValue": "60000" } ] }, { "modelCode": "SUSPICIOUS_FOREIGN_EXCHANGE", "params": [ { "paramCode": "FREQUENCY_THRESHOLD", "paramValue": "5" } ] } ] } ``` **响应示例:** ```json { "code": 200, "msg": "保存成功" } ``` **错误码说明:** | 错误码 | 说明 | |--------|------| | 400 | 参数验证失败(项目ID为空、参数列表为空等) | | 500 | 服务器内部错误(数据库操作失败等) | --- ### 2.2 后端Service层设计 #### 2.2.1 Service接口新增方法 ```java public interface ICcdiModelParamService { /** * 查询所有模型及其参数(按模型分组) * * @param projectId 项目ID(0表示全局配置) * @return 所有模型的参数配置 */ ModelParamAllVO selectAllParams(Long projectId); /** * 批量保存所有模型的参数修改 * * @param saveAllDTO 所有模型的参数修改数据 */ void saveAllParams(ModelParamSaveAllDTO saveAllDTO); // ... 保留原有的其他方法 } ``` #### 2.2.2 Service实现类核心逻辑 **查询所有模型参数:** ```java @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 allParams = modelParamMapper.selectByProjectId(effectiveProjectId); // 4. 按模型分组 Map> groupedParams = allParams.stream() .collect(Collectors.groupingBy(CcdiModelParam::getModelCode)); // 5. 转换为VO ModelParamAllVO result = new ModelParamAllVO(); List models = new ArrayList<>(); groupedParams.forEach((modelCode, params) -> { ModelGroupVO groupVO = new ModelGroupVO(); groupVO.setModelCode(modelCode); groupVO.setModelName(params.get(0).getModelName()); List 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; } ``` **批量保存参数:** ```java @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接口新增方法 ```java public interface CcdiModelParamMapper extends BaseMapper { /** * 根据项目ID查询所有模型参数 */ List selectByProjectId(@Param("projectId") Long projectId); // ... 保留原有的其他方法 } ``` #### 2.3.2 Mapper XML ```xml ``` --- ### 2.4 前端组件设计 #### 2.4.1 页面结构(两个页面相同布局) ```vue ``` #### 2.4.2 核心数据结构 ```javascript data() { return { // 页面标题(全局配置 vs 项目配置) pageTitle: this.projectId ? '项目参数配置' : '全局模型参数管理', // 模型参数数据(按模型分组) modelGroups: [], // ModelGroupVO[] // 修改记录(记录哪些参数被修改过) modifiedParams: new Map(), // Map> // 保存状态 saving: false } } ``` #### 2.4.3 核心方法 ```javascript 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 样式设计 ```scss .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` 中添加: ```javascript 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 上线步骤 1. **后端部署** - 停止应用服务 - 部署新版本代码 - 启动应用服务 - 验证接口可用性 2. **前端部署** - 构建前端代码 - 部署到服务器 - 清理浏览器缓存 - 验证页面可用性 3. **功能验证** - 测试全局配置页面 - 测试项目配置页面 - 验证保存功能 - 验证数据一致性 ### 10.3 上线后监控 - [ ] 监控接口响应时间 - [ ] 监控错误日志 - [ ] 收集用户反馈 - [ ] 准备问题修复 ### 10.4 回滚方案 **如果出现严重问题:** 1. 前端回滚到旧版本 2. 后端回滚到旧版本(接口保留不影响) 3. 数据无需回滚(无数据库变更) --- ## 十一、总结 本次设计采用了优化接口的方案,通过新增批量查询和批量保存接口,实现了在同一页面中展示和编辑所有模型参数的需求。设计充分考虑了性能、安全性、兼容性和可维护性,是一个可行且高效的解决方案。 **设计亮点:** - ✅ 接口设计合理,易于理解和扩展 - ✅ 前后端分离,逻辑清晰 - ✅ 保留向后兼容,降低风险 - ✅ 性能优化,用户体验好 - ✅ 代码复用性高,可维护性好 **预期收益:** - 🎯 提升用户体验,减少操作步骤 - 🎯 提高工作效率,一次查看所有模型 - 🎯 降低误操作风险,统一保存机制 - 🎯 代码结构更清晰,便于后续维护 --- ## 附录 ### A. 相关文档 - [若依框架官方文档](http://doc.ruoyi.vip/) - [Element UI 组件库](https://element.eleme.cn/) - [MyBatis Plus 官方文档](https://baomidou.com/) ### B. 变更记录 | 版本 | 日期 | 修改人 | 修改内容 | |------|------|--------|----------| | v1.0 | 2026-03-06 | Claude Code | 初始版本 | --- **文档结束**