提交 a8a15201 authored 作者: 陈泽健's avatar 陈泽健

feat(test): 添加ERP对接PRD文档并更新测试用例配置

- 新增自动化功能测试报告ERP上传需求文档
- 添加完整的ERP接口对接PRD文档包含接口说明和调用示例
- 更新AI完善测试用例工具的配置文件路径指向兰州中石化项目
- 移除多个旧的权限管理相关PRD文档
- 调整测试用例文件路径配置以匹配新项目名称
上级 66784c3b
## 📋 背景
- 系统中,会议安排模块,原有已支持进行党委会的合并
- 系统的决策会议,分为以下三种
- 总经理办公会
- com.ubains.meeting.message.entity.Message.convergentMediaTypes
- 类型值:12:总经理办公会
- 党委会
- com.ubains.meeting.message.entity.Message.convergentMediaTypes
- 类型值:13:党委会
- 管理委员会
- com.ubains.meeting.message.entity.Message.convergentMediaTypes
- 类型值:14:管理委员会
- 本次新增的会议类型
### 文件路径
- **项目根目录**:D:\workspace\program\huazhao_workspace\ubains-meeting-parent\ubains-hwhx
- **会议模块数据层**:ubains-meeting-persistence\src\main\resources\mybatis\mapper\message\MessageMapper.xml
- **会议实体**:ubains-meeting-persistence\src\main\java\com\ubains\meeting\message\entity\Message.java
- **议题实体**:ubains-meeting-persistence\src\main\java\com\ubains\meeting\message\entity\Topic.java
## 🎯 任务
### 任务-1:密码登录错误限制调整为5分钟
#### 目标:
- 会议安排模块合并会议时
- 默认党委会排在前面
- 管理委员会排在后面
#### 具体任务
- 会议安排模块,查询合并会议时
- 需要另外进行排序
- 当接口接受selectType为3的时
- sql查询语句需要根据convergentMediaTypes进行正序排序
- 其中selectType表示的是兰州查询页面,1:会议管理页面(配合queryType为6) 2:决策会议资料 3:合并会议
- 调整代码位置为:
- '会议模块数据层',第623-818行
- 需要注意selectType需要和queryType组合使用
- 比如说:
- selectType=3,queryType=5
- selectType=3,queryType=2
- 或者其他queryType值
- '会议实体'convergentMediaTypes补充注释'14管理委员会'
- 第321行,当前已经手动添加
- '议题实体'convergentMediaTypes补充注释'14管理委员会'
- 第164行,当前已经手动添加
## plan模式问题点
- selectType=3(合并会议查询)在实际使用时,是否可能与所有 queryType 值(0-10)组合使用,还是只与特定的几个
queryType 组合? → 所有 queryType 都可能组合
### 代码规范
- 严格按照Docs/PRD/_PRD_规范文档_代码规范.md
### 文档规范
- 严格按照Docs/PRD/_PRD_规范文档_文档规范.md
### 测试验证
- 严格按照Docs/PRD/_PRD_规范文档_测试规范.md
### 问题记录与规避
- 执行时,按照Docs/PRD/_PRD_方法总结_记录文档.md,累计的记录所积累的经验,规避已经出现过的问题
- 执行时,按照Docs/PRD/_PRD_方法总结_记录文档.md,累计的记录所积累的经验,规避已经出现过的问题
\ No newline at end of file
# 执行计划:合并会议室决策会议按照类型排序
## 📋 背景
**需求来源**: 兰州项目需求 - 会议安排模块合并会议时需要按照决策会议类型排序
**问题描述**: 当前系统在查询合并会议(selectType=3)时,没有按照决策会议类型(convergentMediaTypes)进行排序,导致党委会和管理委员会的显示顺序不符合预期。
**目标**: 当 selectType=3(合并会议查询)时,按照 convergentMediaTypes 字段正序排序,使:
- 党委会(类型值13)排在前面
- 管理委员会(类型值14)排在后面
---
## 🎯 任务范围
### 需要修改的文件
| 文件 | 修改内容 |
|------|----------|
| `ubains-meeting-persistence/src/main/resources/mybatis/mapper/message/MessageMapper.xml` | 修改排序逻辑,在 selectType=3 时添加 convergentMediaTypes 正序排序 |
### 已完成的任务(PRD说明)
- ✅ Message.java 第321行 convergentMediaTypes 已补充注释 '14管理委员会'
- ✅ Topic.java 第164行 convergentMediaTypes 已补充注释 '14管理委员会'
---
## 🔧 技术方案
### 问题分析
1. **当前排序逻辑**: 在 `getMessagePageListLz` 方法(第623-818行)的 `<choose>` 块中,排序根据 queryType 决定
2. **挑战**: selectType=3 可能与多种 queryType 组合使用(如 queryType=0-10)
3. **需求**: 在所有 queryType 组合下,当 selectType=3 时都需要优先按 convergentMediaTypes 排序
### 解决方案
`<choose>` 块的每个 `ORDER BY` 子句中,添加条件判断:
-`selectType=3` 时,在原有排序条件前添加 `m.convergent_mediaTypes ASC`
- 其他情况保持原有排序不变
### 修改示例
**修改前**(queryType=2 分支):
```xml
<when test="queryMap.queryType == 2">
ORDER BY m.config_value_id ASC ,m.start_time ASC
</when>
```
**修改后**:
```xml
<when test="queryMap.queryType == 2">
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time ASC
</when>
```
---
## 📝 执行步骤
### Step 1: 修改 MessageMapper.xml 排序逻辑
**文件**: `ubains-meeting-persistence/src/main/resources/mybatis/mapper/message/MessageMapper.xml`
**行号**: 777-816(`<choose>` 块)
需要修改的 queryType 分支:
| 分支 | 行号 | 原有排序 | 修改后排序(selectType=3时) |
|------|------|----------|------------------------------|
| queryType == 0 | 778-781 | config_value_id ASC, start_time ASC | convergent_mediaTypes ASC, config_value_id ASC, start_time ASC |
| queryType == 1 | 782-785 | config_value_id ASC, start_time DESC | convergent_mediaTypes ASC, config_value_id ASC, start_time DESC |
| queryType == 2 | 786-788 | config_value_id ASC, start_time ASC | convergent_mediaTypes ASC, config_value_id ASC, start_time ASC |
| queryType == 3 | 789-793 | config_value_id ASC, start_time ASC | convergent_mediaTypes ASC, config_value_id ASC, start_time ASC |
| queryType == 4 | 794-798 | config_value_id ASC, start_time ASC | convergent_mediaTypes ASC, config_value_id ASC, start_time ASC |
| queryType == 5 | 799-802 | start_time DESC | convergent_mediaTypes ASC, start_time DESC |
| queryType == 7 | 803-806 | config_value_id ASC, start_time DESC | convergent_mediaTypes ASC, config_value_id ASC, start_time DESC |
| queryType == 8 | 807-809 | start_time ASC | convergent_mediaTypes ASC, start_time ASC |
| queryType == 6 | 810-812 | start_time DESC | convergent_mediaTypes ASC, start_time DESC |
| queryType == 10 | 813-815 | start_time DESC | convergent_mediaTypes ASC, start_time DESC |
### Step 2: 生成实现文档
**路径**: `Docs/Doc/兰州/260317需求开发/`
**文件名**: `Doc_会议安排模块_合并会议室决策会议按照类型排序_功能实现文档.md`
---
## 📝 具体代码修改
### MessageMapper.xml 修改详情
**修改前(第777-816行)**:
```xml
<choose>
<when test="queryMap.queryType == 0">
AND (m.config_value_id = 1 OR m.config_value_id = 2)
ORDER BY m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 1">
AND m.config_value_id = 4
ORDER BY m.config_value_id ASC ,m.start_time DESC
</when>
<when test="queryMap.queryType == 2">
ORDER BY m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 3">
and m.uid=#{queryMap.uid}
AND m.end_time > SYSDATE()
ORDER BY m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 4">
and m.message_id in (select mid from rms_meeting_signs where uid=#{queryMap.uid})
AND m.end_time > SYSDATE()
ORDER BY m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 5">
AND (m.config_value_id = 1 OR m.config_value_id = 2 OR m.config_value_id = 4)
ORDER BY m.start_time DESC
</when>
<when test="queryMap.queryType == 7">
AND m.config_value_id = 2
ORDER BY m.config_value_id ASC ,m.start_time DESC
</when>
<when test="queryMap.queryType == 8">
ORDER BY m.start_time ASC
</when>
<when test="queryMap.queryType == 6">
ORDER BY m.start_time DESC
</when>
<when test="queryMap.queryType == 10">
ORDER BY m.start_time DESC
</when>
</choose>
```
**修改后**:
```xml
<choose>
<when test="queryMap.queryType == 0">
AND (m.config_value_id = 1 OR m.config_value_id = 2)
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 1">
AND m.config_value_id = 4
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time DESC
</when>
<when test="queryMap.queryType == 2">
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 3">
and m.uid=#{queryMap.uid}
AND m.end_time &gt; SYSDATE()
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 4">
and m.message_id in (select mid from rms_meeting_signs where uid=#{queryMap.uid})
AND m.end_time &gt; SYSDATE()
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time ASC
</when>
<when test="queryMap.queryType == 5">
AND (m.config_value_id = 1 OR m.config_value_id = 2 OR m.config_value_id = 4)
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.start_time DESC
</when>
<when test="queryMap.queryType == 7">
AND m.config_value_id = 2
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.config_value_id ASC ,m.start_time DESC
</when>
<when test="queryMap.queryType == 8">
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.start_time ASC
</when>
<when test="queryMap.queryType == 6">
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.start_time DESC
</when>
<when test="queryMap.queryType == 10">
ORDER BY
<if test="queryMap.selectType != null and queryMap.selectType == 3">
m.convergent_mediaTypes ASC,
</if>
m.start_time DESC
</when>
</choose>
```
---
## ✅ 验证方案
### 测试场景
| 场景 | 参数 | 预期结果 |
|------|------|----------|
| 1 | selectType=3, queryType=2 | 合并会议按 convergentMediaTypes 正序排序 |
| 2 | selectType=3, queryType=5 | 合并会议按 convergentMediaTypes 正序排序 |
| 3 | selectType=3, queryType=6 | 合并会议按 convergentMediaTypes 正序排序 |
| 4 | selectType!=3, queryType=2 | 原有排序逻辑不变 |
| 5 | selectType!=3, queryType=5 | 原有排序逻辑不变 |
### 预期结果
- 党委会(convergentMediaTypes=13)排在管理委员会(convergentMediaTypes=14)前面
- 非合并会议的排序逻辑保持不变
---
## 📁 关键文件路径
```
ubains-meeting-persistence/
└── src/main/resources/mybatis/mapper/message/
└── MessageMapper.xml (修改排序逻辑)
```
---
## ⚠️ 注意事项
1. **数据库兼容性**: convergent_mediaTypes 是字符串类型字段,直接 ASC 排序可正常工作(因为值是两位数 "12", "13", "14")
2. **不影响原有逻辑**: 仅在 selectType=3 时添加额外排序,其他情况保持不变
3. **代码规范**: 遵循项目现有的 MyBatis XML 编写风格
---
## 📅 执行状态
- [x] Step 1: 修改 MessageMapper.xml 排序逻辑 ✅ 2026-03-17
- [x] Step 2: 生成实现文档 ✅ 2026-03-17
- [ ] Step 3: 功能测试验证
---
## ✅ 实际执行结果
### 代码修改确认
**文件**: `ubains-meeting-persistence/src/main/resources/mybatis/mapper/message/MessageMapper.xml`
**实际修改行号**: 第777-856行
**修改内容**: 在 `<choose>` 块的每个 queryType 分支中,`ORDER BY` 子句前添加条件判断:
-`selectType=3` 时,优先按 `m.convergent_mediaTypes ASC` 排序
- 其他情况保持原有排序逻辑不变
**各分支修改状态**:
| queryType | 行号 | 修改状态 |
|-----------|------|----------|
| 0 | 778-785 | ✅ 已完成 |
| 1 | 786-793 | ✅ 已完成 |
| 2 | 794-800 | ✅ 已完成 |
| 3 | 801-809 | ✅ 已完成 |
| 4 | 810-818 | ✅ 已完成 |
| 5 | 819-826 | ✅ 已完成 |
| 7 | 827-834 | ✅ 已完成 |
| 8 | 835-841 | ✅ 已完成 |
| 6 | 842-848 | ✅ 已完成 |
| 10 | 849-856 | ✅ 已完成 |
### 复核结论
1. **代码实现与执行文档描述一致**
2. **XML语法正确**
3. **所有queryType分支均已覆盖**
### 待办事项
- [ ] 启动项目验证无启动错误
- [ ] 调用合并会议查询接口(selectType=3)验证排序结果
- [ ] 回归测试其他查询场景
# 兰州中石化_新增决策会议类型
## 概述
1. **兰州中石化项目总线页**`src/views/HomeLZZSH/index.vue`
2. **议题申报页**`src/views/HomeLZZSH/CreateTopic/index.vue`
3. **议题详情页**`src/views/HomeLZZSH/TopicDetail/index.vue`
4. **议题列表页**`src/views/HomeLZZSH/TopicList/index.vue`
5. **待办列表页**`src/views/HomeLZZSH/TodoList/index.vue`
6. **创建会议页**`src/views/HomeLZZSH/CreateMeeting/index.vue`
7. **会议管理页**`src/views/HomeLZZSH/MeetingManage/index.vue`
8. **决策会议页**`src/views/HomeLZZSH/DecisionMeeting/index.vue`
9. **添加到会议组件**`src/components/ProjectLZZSH/AddInMeeting/index.vue`
10. **会议安排页**`src\views\HomeLZZSH\MeetingArrange\index.vue`
## 任务
1. 议题申报决策会议类型增加”管理委员会”
- convergentMediaTypes新增选项14代表”管理委员会”
- 接口传参也传递14
- 议题详情页注意回显处理
- 议题列表页、待办列表页的getMediaTypes()方法添加14的映射
- 创建会议页添加”管理委员会”选项,更新标题显示,添加summaryType=3支持
- 会议管理页和会议安排页决策会议类型值从”12,13”更新为”12,13,14”
- 决策会议页convergentMediaTypesList数组添加”管理委员会”选项
- 添加到会议组件添加summaryType=3的支持
2. 兰州中石化项目不需要国际化
## 任务执行要求
- 在执行任务前,判断当前文件根目录下有没有"当前文件+'_计划执行.md'"文件,例如"PRD_实时转录_页面整合.md"与"PRD_实时转录_页面整合_计划执行.md",没有则新建一个
- 在执行任务中,将将要执行的计划补充在"当前文件+'_计划执行.md'"文件中,执行的任务以"当前文件+'_计划执行.md'"为主
- 在执行任务后,将任务执行的结果返回补充到"当前文件+'_计划执行.md'"文件中,需要写明实现了什么,以及有什么优化项或者有什么风险等
## 问题排查要求
- 在问题排查前,判断当前文件根目录下有没有"当前文件+'_分析执行.md'"文件,例如"PRD_实时转录_页面整合.md"与"PRD_实时转录_页面整合_分析执行.md",没有则新建一个
- 在问题排查中,将将要执行的操作补充在"当前文件+'_分析执行.md'"文件中,执行的操作以"当前文件+'_分析执行.md'"为主
- 在问题排查后,将问题修复的结果返回补充到"当前文件+'_分析执行.md'"文件中,需要写明实现了什么,以及有什么优化项或者有什么风险等
## 任务/问题执行要求
- 在写任何代码之前,在 Planning 模式下无尽地审问我的想法。不要假设任何问题。问问题直到没有假设剩下。
## 代码规范
- 严格按照`Docs\PRD\_PRD_规范文档_代码规范.md`
## 文档规范
- 严格按照`Docs\PRD\_PRD_规范文档_文档规范.md`
## 测试验证
- 严格按照`Docs\PRD\_PRD_规范文档_测试规范.md`
\ No newline at end of file
# 兰州中石化_新增决策会议类型 - 计划执行
## 任务概述
在兰州中石化项目的议题申报功能中,新增"管理委员会"作为决策会议类型选项。
## 任务详情
1. 议题申报决策会议类型增加"管理委员会"
- convergentMediaTypes新增选项14代表"管理委员会"
- 接口传参也传递14
- 议题详情页注意回显处理
2. 兰州中石化项目不需要国际化
## 涉及文件
- `src/views/HomeLZZSH/CreateTopic/index.vue` - 议题申报页
- `src/views/HomeLZZSH/TopicDetail/index.vue` - 议题详情页
## 计划阶段
- 等待需求确认后制定详细计划
---
## 实施记录 (2026-03-17)
### 实施概述
已完成兰州中石化项目"管理委员会"决策会议类型的前端支持,`convergentMediaTypes=14` 代表"管理委员会"。
### 修改文件列表 (共8个文件)
| 文件 | 修改内容 |
|------|----------|
| `src/views/HomeLZZSH/CreateTopic/index.vue` | 添加"管理委员会"单选选项 (label="14") |
| `src/views/HomeLZZSH/TopicDetail/index.vue` | 更新决策会议类型显示逻辑,支持14显示为"管理委员会" |
| `src/views/HomeLZZSH/TopicList/index.vue` | getMediaTypes()方法中添加14的映射 |
| `src/views/HomeLZZSH/TodoList/index.vue` | getMediaTypes()方法中添加14的映射 |
| `src/views/HomeLZZSH/CreateMeeting/index.vue` | 添加"管理委员会"选项;更新标题显示;添加summaryType=3支持 |
| `src/views/HomeLZZSH/MeetingManage/index.vue` | 决策会议类型值从"12,13"更新为"12,13,14" |
| `src/views/HomeLZZSH/DecisionMeeting/index.vue` | convergentMediaTypesList数组添加"管理委员会"选项 |
| `src/components/ProjectLZZSH/AddInMeeting/index.vue` | 添加summaryType=3的支持;更新注释 |
### 具体修改详情
#### 1. CreateTopic/index.vue (议题申报页)
- **位置**: 第44-48行
- **修改**: 在决策会议类型单选框组中添加"管理委员会"选项
```vue
<el-radio label="14">管理委员会</el-radio>
```
#### 2. TopicDetail/index.vue (议题详情页)
- **位置**: 第36行
- **修改**: 更新决策会议类型显示逻辑
```vue
{{ topicData.convergentMediaTypes === '12' ? '总经理办公会' : topicData.convergentMediaTypes === '13' ? '党委会' : '管理委员会' }}
```
#### 3. TopicList/index.vue (议题列表页)
- **位置**: 第285-298行
- **修改**: getMediaTypes()方法中添加14的映射
```javascript
} else if (convergentMediaTypes == '14') {
return '管理委员会';
```
#### 4. TodoList/index.vue (待办列表页)
- **位置**: 第196-209行
- **修改**: getMediaTypes()方法中添加14的映射
```javascript
} else if (convergentMediaTypes == '14') {
return '管理委员会';
```
#### 5. CreateMeeting/index.vue (创建会议页)
- **位置**: 第24-29行,第15行,第662-666行
- **修改**:
- 添加"管理委员会"单选选项
- 更新议题汇总标题显示逻辑
- 添加summaryType=3的初始化逻辑
#### 6. MeetingManage/index.vue (会议管理页)
- **位置**: 第130-152行
- **修改**: convergentMediaTypesList数组中的决策会议类型值更新为"12,13,14"
#### 7. DecisionMeeting/index.vue (决策会议页)
- **位置**: 第105-126行
- **修改**: convergentMediaTypesList数组添加"管理委员会"选项
```javascript
{
value: '14',
label: '管理委员会',
}
```
#### 8. AddInMeeting/index.vue (添加到会议组件)
- **位置**: 第111-115行,第58-61行
- **修改**:
- 添加summaryType=3的支持,对应convergentMediaTypes=14
- 更新注释说明
### 验证建议
1. **议题申报验证**
- 打开议题申报页面,验证"管理委员会"选项是否存在
- 选择"管理委员会"并提交,验证接口参数是否传递14
2. **议题详情验证**
- 打开已创建的"管理委员会"类型议题,验证是否正确显示
3. **列表显示验证**
- 在议题列表和待办列表中,验证"管理委员会"类型是否正确显示
4. **会议创建验证**
- 验证会议创建页面是否可以选择"管理委员会"类型
5. **会议管理验证**
- 验证会议管理页面查询条件中是否包含"管理委员会"
### 后续优化建议
如需完整支持"管理委员会"类型,可考虑:
1. **TopicList页面** - 添加第三个卡片"管理委员会议题列表"
2. **DecisionMeetingData页面** - 添加第三个卡片"管理委员会会议列表"
3. **CreateMeeting页面** - 在议题汇总功能中完善对summaryType=3的支持
这些改动需要较大的架构调整,建议根据实际业务需求决定是否实施。
### 风险提示
- 确保后端API已正确处理convergentMediaTypes=14的情况
- 测试各页面之间的数据传递和回显是否正确
- 测试审批流程中"管理委员会"类型议题的处理
# 兰州中石化_议题列表新增决策会议兼容显示
## 概述
1. **兰州中石化项目总线页**`src/views/HomeLZZSH/index.vue`
2. **议题列表页**`src\views\HomeLZZSH\TopicList\index.vue`
3. **决策会议资料页**`src\views\HomeLZZSH\DecisionMeetingData\index.vue`
## 任务
1. 当前根据`Docs\PRD\兰州中石化项目\PRD_兰州中石化_新增决策会议类型_计划执行.md`新增了新的决策会议类型"管理委员会"
2.**议题列表页****决策会议资料页**实现新的决策会议类型兼容
- 当前页面显示了两种决策会议类型,需要补充"管理委员会"决策会议类型显示
- 默认展开第一个卡片(党委会)
- 点击折叠的卡片时展开它,同时折叠其他卡片
- 点击已展开的卡片时保持展开状态
- 两个页面的显示顺序为"党委会-管理委员会-总经理办公会",从上到下
3. 兰州中石化项目不需要国际化
## 任务执行要求
- 在执行任务前,判断当前文件根目录下有没有"当前文件+'_计划执行.md'"文件,例如"PRD_实时转录_页面整合.md"与"PRD_实时转录_页面整合_计划执行.md",没有则新建一个
- 在执行任务中,将将要执行的计划补充在"当前文件+'_计划执行.md'"文件中,执行的任务以"当前文件+'_计划执行.md'"为主
- 在执行任务后,将任务执行的结果返回补充到"当前文件+'_计划执行.md'"文件中,需要写明实现了什么,以及有什么优化项或者有什么风险等
## 问题排查要求
- 在问题排查前,判断当前文件根目录下有没有"当前文件+'_分析执行.md'"文件,例如"PRD_实时转录_页面整合.md"与"PRD_实时转录_页面整合_分析执行.md",没有则新建一个
- 在问题排查中,将将要执行的操作补充在"当前文件+'_分析执行.md'"文件中,执行的操作以"当前文件+'_分析执行.md'"为主
- 在问题排查后,将问题修复的结果返回补充到"当前文件+'_分析执行.md'"文件中,需要写明实现了什么,以及有什么优化项或者有什么风险等
## 任务/问题执行要求
- 在写任何代码之前,在 Planning 模式下无尽地审问我的想法。不要假设任何问题。问问题直到没有假设剩下。
## 代码规范
- 严格按照`Docs\PRD\_PRD_规范文档_代码规范.md`
## 文档规范
- 严格按照`Docs\PRD\_PRD_规范文档_文档规范.md`
## 测试验证
- 严格按照`Docs\PRD\_PRD_规范文档_测试规范.md`
\ No newline at end of file
# 兰州中石化_议题列表新增决策会议兼容显示 - 计划执行
## 任务概述
**议题列表页****决策会议资料页**实现新的决策会议类型"管理委员会"兼容显示。
## 任务详情
1. 当前页面显示了两种决策会议类型,需要补充"管理委员会"决策会议类型显示
2. 默认展开第一个卡片(党委会)
3. 点击折叠的卡片时展开它,同时折叠其他卡片
4. 点击已展开的卡片时保持展开状态
5. 两个页面的显示顺序为"党委会-管理委员会-总经理办公会",从上到下
## 涉及文件
- `src/views/HomeLZZSH/TopicList/index.vue` - 议题列表页
- `src/views/HomeLZZSH/DecisionMeetingData/index.vue` - 决策会议资料页
## 计划阶段
待制定详细计划
# 权限数据扩展补充
## 概述
1. **配置项数据地址**:`Docs\Temp\licence.js`
2. **权限控制数据地址**:`Docs\Temp\permission.json`
## 任务
1. ✅ 根据配置项数据补充权限控制数据
2. ✅ 补充权限控制数据的国际化
3. ✅ 补充相关说明文档
### 补充权限控制数据要求
1. 配置项数据和权限控制数据中都包含有`xx_yyyy_list`数据,帮我根据配置项数据并参考权限控制数据原数据补充权限控制数据,每个都包含基础的`view、create、edit、delete`
2. 配置项数据中只要是`xx_yyyy_list`里的数据,都要补充到权限控制数据中
3. 当补充完数据后,需要确认一下是不是每一个都包含在权限控制数据里面了
### 补充权限控制数据的国际化要求
1. 配置项数据中有着一些注释比如`set_room_enable`的注释为"区域管理",这个将代表着`set_room_list`的为"区域管理",子集`office_manage`也有着`办公室管理`的注释
2. 帮我根据注释以及上面的规律,找到国际化文件中的`permissionClass``xx_yyyy_enable`的注释作为`xx_yyyy_list`的国际化添加进行,将子集的注释补充到`permission`中,并实现国际化
3. 当补充完国际化后,需要确认一下国际化中的`permissionClass`是否包含了每一个`xx_yyyy_enable`的注释作为`xx_yyyy_list`的value,`permission`中包含每一个子集的注释
### 相关说明文档补充要求
1. **文档位置**`Docs\Temp\权限配置README.md`
2. 由于权限控制数据是一个json文件,为了说明相关的配置有何功能,所以在文档进行说明,我要你将配置项和相关的文件说明整合到文档中
## 国际化
当涉及到国际化时,遵循`Docs\PRD\_PRD_规范文档_国际化.md`规范
\ No newline at end of file
# 权限管理页面新增开发
## 📋 概述
1. 权限管理页面地址: `src\views\Backend\Admin\PermissionManage\index.vue`
2. 相关API文档地址:`Docs\Api\API_权限组管理接口文档.md`
3. 新增/修改权限组页面地址:`src\views\Backend\Admin\PermissionManage\components\AddEditPage\index.vue`
4. 权限绑定弹窗地址:`src\views\Backend\Admin\PermissionManage\components\BindDialog\index.vue`
5. 自定义权限控制指令v-permission地址:`src\utils\permission.js`
6. 权限组弹窗权限勾选组件:`src\views\Backend\Admin\PermissionManage\components\PermissionConfig\index.vue`
## 🎯 任务
1. ✅ 在权限管理页面遵循`Docs\PRD\_PRD_规范文档_新建页面.md`并标准化初始化页面,在`src\router.js`注册路由
2. ✅ 参考页面`src\views\Backend\Admin\Role\index.vue`的设计在初始化后的权限管理页新增功能
3. ✅ 将权限管理页面的新增/修改权限组弹窗和绑定弹窗分别抽离成组件放置在`src\views\Backend\Admin\PermissionManage\components`中,注意数据的传输,并优化一下样式
4. ✅ 实现权限组添加权限
5. ✅ 在新增/修改权限组弹窗继续实现任务4的要求
6. ✅ 帮我参考API接口文档,实现权限组的增删改查、禁用/启用功能,绑定功能先不实现
7. ✅ 进入权限管理页面时,帮我请求API接口文档的接口`/permissionGroup/getAllPermissions`获取所有配置项,然后将`src\views\Backend\Admin\PermissionManage\components\PermissionConfig\index.vue`的permissionList替换为接口请求到的真实数据
8. ✅ 重构权限绑定弹窗及补充功能实现
9. ✅ 数据结构调整
10. ✅ 用户反馈说新增/修改权限组弹窗和权限绑定弹窗这两个弹窗数据比较多,使用弹窗显示不全,帮我改成页面组件,不需要新建router路由,只需要改为页面级组件,并遵循`Docs\PRD\_PRD_规范文档_新建页面.md`与使用ui-ux-pro-max优化页面样式,要求样式紧凑一点,尽可能显示多的页面数据
11. 帮我参考新增/修改权限组页面样式,实现权限查看功能与页面
### 权限页面基础功能
1. 实现功能时提供了API接口,但先不进行对接,先在前端用虚拟数据实现并预留操作方法等,等我要接口对接时再进行接口对接
2. 支持模糊搜索,支持基础的增删改查
3. el-table的主要显示字段获取参考相关API文档的"分页查询权限组"接口,显示字段groupName、isEnable、createTime,支持多选,支持分页,操作有修改、删除、绑定
### 权限组添加权限要求
1. 实现功能时提供了API接口,但先不进行对接,先在前端用虚拟数据实现并预留操作方法等,等我要接口对接时再进行接口对接
2. 我在`src\constant\permissionList.js`模拟了权限数据,帮我使用el-checkbox进行显示
3. 文本显示逻辑:在国际化文件中新建permission子集,通过key值匹配显示文本:例如view=i18n文件中的`permission.view`
4. 页面显示:要同时显示名称和勾选框,比如
```json
functionType_82: {
view: 0,
create: 0,
update: 0,
delete: 0,
},
```
要显示为"办公室管理:查看 新增 编辑 删除"
### 重构权限绑定弹窗及补充功能实现要求
1. 弹窗页面重构,当前的设计不符合产品的功能要求,具体功能要求:权限组支持同时绑定多个用户、角色、部门,取消权限配置功能
2. 页面样式自由发挥,符合系统主题即可,优先使用UI/UX Pro Max
3. 参考`@src/views/Backend/Account/User/index.vue:930-984 `实现用户数据请求获取
4. 参考`@src/views/Backend/Account/User/index.vue:986-1010 `实现角色数据的请求获取
5. 参考`@src/views/Backend/Account/User/index.vue:263-273 ``@src/views/Backend/Account/User/index.vue:1055-1068 `实现部门数据的请求获取
6. 根据权限api文档的`/permissionGroup/bindRelation`实现权限绑定
## 任务执行要求
- 在执行任务前,判断当前文件根目录下有没有"当前文件+'_计划执行.md'"文件,例如"PRD_实时转录_页面整合.md"与"PRD_实时转录_页面整合_计划执行.md",没有则新建一个
- 在执行任务中,将将要执行的计划补充在"当前文件+'_计划执行.md'"文件中,执行的任务以"当前文件+'_计划执行.md'"为主
- 在执行任务后,将任务执行的结果返回补充到"当前文件+'_计划执行.md'"文件中,需要写明实现了什么,以及有什么优化项或者有什么风险等
## 问题排查要求
- 在问题排查前,判断当前文件根目录下有没有"当前文件+'_分析执行.md'"文件,例如"PRD_实时转录_页面整合.md"与"PRD_实时转录_页面整合_分析执行.md",没有则新建一个
- 在问题排查中,将将要执行的操作补充在"当前文件+'_分析执行.md'"文件中,执行的操作以"当前文件+'_分析执行.md'"为主
- 在问题排查后,将问题修复的结果返回补充到"当前文件+'_分析执行.md'"文件中,需要写明实现了什么,以及有什么优化项或者有什么风险等
## 过程总结返填
- 在执行任务时,先了解相关的页面与方法,先看看有什么不清楚的或者需要我确认的,先找我确认,再进行代码开发
- 在我确认完后,需要把我确认的东西反填到当前文档中,确保信息对齐
## 代码规范
- 严格按照`Docs\PRD\_PRD_规范文档_代码规范.md`
## 文档规范
- 严格按照`Docs\PRD\_PRD_规范文档_文档规范.md`
## 测试验证
- 严格按照`Docs\PRD\_PRD_规范文档_测试规范.md`
\ No newline at end of file
# 需求文档
## 概述
**文件**
- **初始化权限组方法**:com.ubains.meeting.system.service.impl.PermissionGroupServiceImpl.initBasePermissionGroupsForAllCompanies
- **数据库初始化文件**:com/ubains/meeting/common/AdjustmentDb.java
- **原基础角色基本权限组开发相关文件**:
- **原始需求文档**:Docs/PRD/系统管理/权限管理/PRD_权限组管理_添加权限组_给基础角色添加基本权限组.md
- **计划执行文档**:Docs/PRD/系统管理/权限管理/PRD_权限组管理_添加权限组_给基础角色添加基本权限组_计划执行.md
### 背景
原来开发的基础角色和基本权限组有点问题,需要优化调整一下:
1. 基础角色中有个id为2的Admin基础角色,该角色与后续添加的系统管理员冲突了,他们本质应该是同一个
2. id为2的Admin基础角色没有基本权限组
## 任务
- 参考`原基础角色基本权限组开发相关文件`,了解之前的开发逻辑
- `数据库初始化文件`从第615行到第668行,调整基础角色
- id为6的系统管理员角色不要了
- 安全管理员和审计管理员的id往前移1位,即7->6(安全管理员),8->7(审计管理员)
- `初始化权限组方法`调整权限组和角色的绑定
- 系统管理员的权限组绑定id为2的原Admin基础角色
- 绑定安全管理员和审计管理员时设置id往前移1位,即7->6(安全管理员),8->7(审计管理员)
- 先给出计划执行方案文档,将文档创建在`Docs/PRD/+同路径+/同名文件+_计划执行.md`,等审批完文档再由我考虑是否按计划执行文档来执行
- 生成的计划执行方案文档需添加任务:生成ai输出的总结内容到`Docs/Doc/+同路径+/Doc_+同名文件(不包含前缀PRD_)+的总结.md`
## 规范
严格执行以下文档
#### 代码规范
- Docs/PRD/_PRD_规范文档_代码规范.md
#### 文档规范
- Docs/PRD/_PRD_规范文档_文档规范.md
#### 测试规范
- Docs/PRD/_PRD_规范文档_测试规范.md
\ No newline at end of file
## Plan: 权限组管理功能设计
本方案设计一个基于权限组的RBAC权限管理系统。通过`sys_permission_group`权限组表和三个独立关联表(部门、角色、用户),实现权限组的增删改查及绑定关系。删除采用物理删除并级联清理关联数据,权限合并采用并集策略。
### 数据库设计
**1. 权限组表 `sys_permission_group`**
| 字段 | 类型 | 说明 |
|------------------|--------------|------------------------------------------------------|
| `group_id` | bigint | 主键,自增 |
| `group_name` | varchar(100) | 权限组名称 |
| `permissions` | text | 权限配置JSON,如 `{"permission1":{"create":1,"delete":0}}` |
| `company_number` | varchar(64) | 公司编号(多租户) |
| `is_enable` | int | 是否启用(0:禁用,1:启用) |
| `is_base` | int | 是否基础权限组(0:否,1:是) |
| `create_time` | datetime | 创建时间 |
| `update_time` | datetime | 更新时间 |
**2. 权限组-部门关联表 `sys_permission_group_department`**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint | 主键,自增 |
| `group_id` | bigint | 权限组ID |
| `department_id` | bigint | 部门ID |
| `create_time` | datetime | 创建时间 |
**索引**`idx_group_id(group_id)``idx_department_id(department_id)`
**3. 权限组-角色关联表 `sys_permission_group_role`**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint | 主键,自增 |
| `group_id` | bigint | 权限组ID |
| `role_id` | bigint | 角色ID |
| `create_time` | datetime | 创建时间 |
**索引**`idx_group_id(group_id)``idx_role_id(role_id)`
**4. 权限组-用户关联表 `sys_permission_group_user`**
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | bigint | 主键,自增 |
| `group_id` | bigint | 权限组ID |
| `user_id` | bigint | 用户ID |
| `create_time` | datetime | 创建时间 |
**索引**`idx_group_id(group_id)``idx_user_id(user_id)`
**3. permission.json 配置文件格式**
```json
{
"permission1": {"create": 0, "delete": 0, "edit": 0},
"permission2": {"create": 0, "delete": 0, "edit": 0, "view": 0}
}
```
### 接口设计
| 接口 | URL | 方法 | 说明 |
|------|-----|------|------|
| 分页查询权限组 | `/permissionGroup/getPage` | POST | 返回权限组分页列表 |
| 获取所有权限配置 | `/permissionGroup/getAllPermissions` | GET | 读取permission.json返回所有可选权限 |
| 新增权限组 | `/permissionGroup/add` | POST | 创建权限组及权限配置 |
| 修改权限组 | `/permissionGroup/update` | PUT | 更新权限组名称和权限配置 |
| 删除权限组 | `/permissionGroup/delete` | POST | 物理删除权限组,级联删除关联记录 |
| 绑定关系 | `/permissionGroup/bindRelation` | POST | 绑定部门/角色/用户(type区分) |
| 解绑关系 | `/permissionGroup/unbindRelation` | POST | 解绑部门/角色/用户 |
| 查询绑定关系 | `/permissionGroup/getRelations` | POST | 查询权限组已绑定的对象列表 |
| 查询用户最终权限 | `/permissionGroup/getUserPermissions` | GET | 返回用户通过所有途径获得的并集权限 |
### 权限合并逻辑
查询用户最终权限时,合并以下三个来源的权限组:
1. **直接绑定**:查询 `sys_permission_group_user` 表中 `user_id=用户ID` 的权限组
2. **部门绑定**:查询 `sys_permission_group_department` 表中 `department_id=用户所属部门ID` 的权限组
3. **角色绑定**:查询 `sys_permission_group_role` 表中 `role_id=用户所属角色ID` 的权限组
合并策略:对所有权限组的`permissions`字段进行JSON并集,同一操作项有任意一个为1则最终为1。
### Steps
1.[ubains-meeting-persistence](ubains-meeting-persistence/src/main) 创建`PermissionGroup``PermissionGroupDepartment``PermissionGroupRole``PermissionGroupUser`四个实体类,Mapper接口继承`BaseMapper`
2.[ubains-meeting-provider](ubains-meeting-provider/src/main) 创建`IPermissionGroupService`接口及实现类,实现CRUD、绑定解绑、权限并集合并逻辑
3.[ubains-meeting-inner-api/resources](ubains-meeting-inner-api/src/main/resources) 创建`permission.json`配置文件
4.[ubains-meeting-inner-api](ubains-meeting-inner-api/src/main) 创建`PermissionGroupController`,实现9个API接口,删除接口使用`@Transactional`保证级联删除的事务一致性
5. 创建`PermissionUtil`工具类封装权限JSON并集合并方法,供Service层和权限校验拦截器调用
6. 在src/main/java/com/ubains/meeting/common/AdjustmentDb.java的adjustmentDb方法创建对应数据库表
7. 输出PermissionGroupController的接口文档到Docs/Doc/权限管理目录下“API_权限组管理接口文档.md”
### SQL参考
```sql
-- 权限组表
CREATE TABLE `sys_permission_group` (
`group_id` bigint NOT NULL AUTO_INCREMENT,
`group_name` varchar(100) NOT NULL COMMENT '权限组名称',
`permissions` text COMMENT '权限配置JSON',
`company_number` varchar(64) DEFAULT NULL COMMENT '公司编号',
`is_enable` int DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
`is_base` int DEFAULT 0 COMMENT '是否基础权限组(0:否,1:是)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`group_id`),
KEY `idx_company` (`company_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限组表';
-- 权限组-部门关联表
CREATE TABLE `sys_permission_group_department` (
`id` bigint NOT NULL AUTO_INCREMENT,
`group_id` bigint NOT NULL COMMENT '权限组ID',
`department_id` bigint NOT NULL COMMENT '部门ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_group_id` (`group_id`),
KEY `idx_department_id` (`department_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限组-部门关联表';
-- 权限组-角色关联表
CREATE TABLE `sys_permission_group_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`group_id` bigint NOT NULL COMMENT '权限组ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_group_id` (`group_id`),
KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限组-角色关联表';
-- 权限组-用户关联表
CREATE TABLE `sys_permission_group_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`group_id` bigint NOT NULL COMMENT '权限组ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_group_id` (`group_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限组-用户关联表';
```
### 其他
1.把AI的总结内容输出ubains-meeting-parent\Docs\Doc\权限管理目录下的“PRD_权限组管理_增删改查_权限组功能开发设计的总结.md”
2.日志和注释要详细,尤其是日志,详细到每个步骤和返回,采用LogsUtil的logxxxInverted方法
\ No newline at end of file
# 权限组管理_权限结构_调整权限数据结构
## 概述
原开发的设计文档:Docs\PRD\PRD_系统\权限管理\PRD_权限组管理_增删改查_权限组功能开发设计.md
代码:com.ubains.controller.system.PermissionGroupController
## 目标
1.调整读取permission.json权限配置文件(getAllPermissions)和获取用户最终权限方法(getUserPermissions),
原permission.json的数据结构为`{"permission1":{"create":1,"delete":0}}`
调整后的数据结构为`{"class1":{"permission1":{"create":0,"delete":0,"edit":0}},"class2":{"permission1":{"create":0,"delete":0,"edit":0}}}`,原来的基础上增加了一层权限分类。
2.getAllPermissions方法返回的数据要求permission.json的数据顺序一致。
### 其他
1.把AI的总结内容输出ubains-meeting-parent\Docs\Doc\权限管理目录下的“PRD_权限组管理_权限结构_调整权限数据结构的总结.md”
2.日志和注释要详细,尤其是日志,详细到每个步骤和返回,采用LogsUtil的logxxxInverted方法
\ No newline at end of file
# 需求文档
## 概述
给基础角色添加基本权限组。
基础角色:
- `超级管理员` 角色id = 1
- `系统管理员` 角色id = 6
- `安全管理员` 角色id = 7
- `审计管理员` 角色id = 8
相关文件和代码:
- com.ubains.meeting.common.AdjustmentDb.adjustmentDb 数据库迁移方法,系统初始化时执行
- com.ubains.meeting.system.entity.Role 角色实体类
- com.ubains.meeting.system.entity.PermissionGroup 权限组实体类
-
- 权限组设计相关文件:
- Docs/PRD/PRD_系统/权限管理 该目录下的其他文档是原来权限组设计的文档
- ubains-meeting-inner-api/src/main/resources/config/permission.yml 权限文件
### 任务
- 给基础角色添加基本权限组,权限组名称对应角色名称
- **权限组按公司编号隔离**:每个公司都需要创建4个基础权限组,通过`company_number`字段区分
-`PermissionGroupService`中实现初始化逻辑,在`adjustmentDb`方法中调用
- 超级管理员读取`permission.yml`文件获取所有权限,作为权限组的权限,关联角色id为1
- 系统管理员读取`permission-system.yml`文件(需创建)获取所有权限,作为权限组的权限,关联角色id为6
- 安全管理员读取`permission-security.yml`文件(需创建)获取所有权限,作为权限组的权限,关联角色id为7
- 审计管理员读取`permission-audit.yml`文件(需创建)获取所有权限,作为权限组的权限,关联角色id为8
- 基础权限组的`isBase`字段值为1
- **ID说明**`role_id`固定为1/6/7/8,`group_id`和关联表`id`为自增主键,通过`is_base`+`group_name`+`company_number`判断权限组是否存在
- **优化 `PermissionUtil#loadPermissionConfig()` 方法**:新增带参数的重载方法 `loadPermissionConfig(String fileName)`,支持读取指定的权限配置文件(如 `permission-system.yml`
- 开发前先给出计划执行方案文档到在`Docs/PRD/PRD_系统/权限管理/同名文件+_计划执行.md`
- 生成的计划执行方案文档需添加任务:生成ai输出的总结内容到`Docs/Doc/权限管理/Doc_+同名文件(不包含前缀PRD_)+的总结.md`
### 方法总结
- Docs/PRD/PRD模板、规范/_PRD_方法总结_记录文档.md
### 代码规范
- Docs/PRD/PRD模板、规范/_PRD_规范文档_代码规范.md
### 文档规范
- Docs/PRD/PRD模板、规范/_PRD_规范文档_文档规范.md
### 测试规范
- Docs/PRD/PRD模板、规范/_PRD_规范文档_测试规范.md
### 记录文档
- Docs/PRD/PRD模板、规范/_PRD_问题总结_记录文档.md
# 需求文档
## 概述
权限组目前需要查看详情功能,详情除了基本权限组信息外,还需要有已绑定的角色、部门和用户信息。
**权限组的控制层**:com.ubains.controller.system.PermissionGroupController
## 任务
-`权限组的控制层`上添加新的接口,该接口用于查看权限组详情,该详情包含权限组基本信息、已绑定的角色、部门、用户信息
- 角色、部门、用户信息,不需要多余信息,主要显示名称和id即可
- 注意接口返回要结果加密,使用`@EncryptResult`注解
- 先给出计划执行方案文档,将文档创建在`Docs/PRD/系统管理/权限管理/同名文件+_计划执行.md`,等审批完文档再由我考虑是否按计划执行文档来执行
- 生成的计划执行方案文档需添加任务:生成API文档到`Docs/Doc/系统管理/权限管理/Doc_+同名文件(不包含前缀PRD_)+_API.md`
- 生成的计划执行方案文档需添加任务:生成ai输出的总结内容到`Docs/Doc/系统管理/权限管理/Doc_+同名文件(不包含前缀PRD_)+的总结.md`
## 规范
严格执行以下文档
#### 代码规范
- Docs/PRD/_PRD_规范文档_代码规范.md
#### 文档规范
- Docs/PRD/_PRD_规范文档_文档规范.md
#### 测试规范
- Docs/PRD/_PRD_规范文档_测试规范.md
\ No newline at end of file
## 📋 背景
- 系统默认的机制是,连续登录失败5次,则锁定登录10分钟,现在要改为支持动态配置
### 文件路径
- **项目根目录**:D:\workspace\program\huazhao_workspace\ubains-meeting-parent\ubains-hwhx
- **登录模块控制层**:ubains-meeting-inner-api\src\main\java\com\ubains\controller\system\LoginControlller.java
- **公共配置属性**:ubains-meeting-provider\src\main\java\com\ubains\meeting\common\saas\property\CommonProperty.java
- **配置枚举**:ubains-meeting-common\src\main\java\com\ubains\meeting\common\constant\ConfigEnum.java
## 🎯 任务
### 任务-1:密码登录错误限制调整为5分钟
#### 目标:
- 登录失败5次后,锁定时间调整支持动态配置的,对应的错误提示也需要能传值
#### 具体任务
- '公共配置属性'添加公共配置属性
- 内容为:'登录锁定时长'
- 默认值为10分钟
- 和其他公共配置放在一块
- '配置枚举'需要用对应的枚举
- 和其他公共配置放在一块
- 登录失败5次后,锁定时间调整为5分钟
- '登录模块控制层'的login方法,第429-434行
- 更改为根据配置的时间来锁定登录
- 对应的错误码也调整一下
- '登录模块控制层'的login方法,第429-434行
- ResultCode.A_LOGIN_TIMES_LIMIT保留
- 给标准版使用
- 另外新增一个兰州用的错误码
- 错误码的code为:A0520
- 放在 code:A0519下面
- 内容为:超过限制登录次数,请x分钟以后再登录!
- x为传参,根据配置值来显示
## plan模式问题点
### 代码规范
- 严格按照Docs/PRD/_PRD_规范文档_代码规范.md
### 文档规范
- 严格按照Docs/PRD/_PRD_规范文档_文档规范.md
### 测试验证
- 严格按照Docs/PRD/_PRD_规范文档_测试规范.md
### 问题记录与规避
- 执行时,按照Docs/PRD/_PRD_方法总结_记录文档.md,累计的记录所积累的经验,规避已经出现过的问题
- 执行时,按照Docs/PRD/_PRD_方法总结_记录文档.md,累计的记录所积累的经验,规避已经出现过的问题
\ No newline at end of file
# 执行计划:密码登录错误限制调整为可配置
## 📋 背景
当前系统登录失败5次后,锁定时间是硬编码的10分钟(600秒)。需求要求将锁定时间改为支持动态配置,并新增错误码A0520用于显示动态的锁定时间。
## 📁 涉及文件
| 文件 | 路径 | 修改类型 |
|------|------|----------|
| ConfigEnum.java | ubains-meeting-common/src/main/java/com/ubains/meeting/common/constant/ConfigEnum.java | 新增枚举 |
| CommonProperty.java | ubains-meeting-provider/src/main/java/com/ubains/meeting/common/saas/property/CommonProperty.java | 新增属性 |
| ResultCode.java | ubains-meeting-common/src/main/java/com/ubains/meeting/common/result/ResultCode.java | 新增错误码 |
| LoginControlller.java | ubains-meeting-inner-api/src/main/java/com/ubains/controller/system/LoginControlller.java | 修改逻辑 |
## 🔧 执行步骤
### 步骤1:ConfigEnum.java - 新增配置枚举
**文件路径**`ubains-meeting-common/src/main/java/com/ubains/meeting/common/constant/ConfigEnum.java`
**位置**:在 `LOCAL_REPEAT_LOGIN_CHECK` 枚举项后面添加
```java
LOGIN_LOCK_MINUTES("0", "loginLockMinutes", "ubains.login.lock.minutes","登录锁定时长,单位分钟,默认10分钟"),
```
---
### 步骤2:CommonProperty.java - 新增配置属性
**文件路径**`ubains-meeting-provider/src/main/java/com/ubains/meeting/common/saas/property/CommonProperty.java`
**位置1**:在 `localRepeatLoginCheck` 属性声明后面添加
```java
/**
* 登录锁定时长,单位分钟,默认10分钟
*/
public static Long loginLockMinutes;
```
**位置2**:在 `initProps()` 方法中添加(在 `localRepeatLoginCheck` 初始化之后)
```java
loginLockMinutes = env.getProperty(ConfigEnum.LOGIN_LOCK_MINUTES.propertyKey(), Long.class, 10L);
```
---
### 步骤3:ResultCode.java - 新增错误码A0520
**文件路径**`ubains-meeting-common/src/main/java/com/ubains/meeting/common/result/ResultCode.java`
**位置**:在 `A_USE_PHONE_OR_EMAIL_ERROR("A0519", ...)` 后面添加
```java
/**
* 超过限制登录次数,请x分钟以后再登录!
*/
A_LOGIN_TIMES_LIMIT_CONFIGURABLE("A0520", "超过限制登录次数,请%s分钟以后再登录!", "More than the limit of login times, please log in %s minutes later!", "로그인 시간 제한 이상, %s 분 후 로그인 해주세요!"),
```
---
### 步骤4:LoginControlller.java - 修改登录失败锁定逻辑
**文件路径**`ubains-meeting-inner-api/src/main/java/com/ubains/controller/system/LoginControlller.java`
**修改位置**:第429-434行
**修改前**
```java
if (errorLoginNumber >= 5) {
// 设置10分钟在登录
redisUtil.expire(rRestrictedLogin,600);
LogsUtil.logErrorInverted("系统登录", "已经存在错误登录次数-大于5次,设置10分钟之后登录", errorLoginNumber,"", identification, account, RequestMode.POST);
return Result.failure(ResultCode.A_LOGIN_TIMES_LIMIT);
}
```
**修改后**
```java
if (errorLoginNumber >= 5) {
// 根据配置设置锁定时间(分钟转秒)
Long lockMinutes = CommonProperty.loginLockMinutes;
Long lockSeconds = lockMinutes * 60;
redisUtil.expire(rRestrictedLogin, lockSeconds.intValue());
LogsUtil.logErrorInverted("系统登录", "已经存在错误登录次数-大于5次,设置锁定时间", "锁定分钟:" + lockMinutes + ",错误次数:" + errorLoginNumber, "", identification, account, RequestMode.POST);
return Result.failure(ResultCode.A_LOGIN_TIMES_LIMIT_CONFIGURABLE).inputParam(String.valueOf(lockMinutes));
}
```
---
## ✅ 验证方式
### 功能验证
1. 配置文件中不设置 `ubains.login.lock.minutes`,验证默认值为10分钟
2. 配置文件中设置 `ubains.login.lock.minutes=5`,验证锁定时间为5分钟
3. 连续登录失败5次后,验证:
- 返回错误码 A0520
- 错误信息显示正确的锁定分钟数
- Redis中key的过期时间正确
### 测试命令
```bash
# 启动项目后测试登录接口
# 连续5次错误密码登录后,验证返回的错误信息
```
---
## ⚠️ 注意事项
1. 保持 `ResultCode.A_LOGIN_TIMES_LIMIT` (A0091) 不变,供标准版使用
2. 新增的 `A_LOGIN_TIMES_LIMIT_CONFIGURABLE` (A0520) 用于兰州项目,支持动态参数
3. 锁定时间单位为分钟,Redis过期时间单位为秒,需要转换
4. 配置默认值为10分钟,与现有行为保持一致
---
## 📝 相关文档
- 需求文档:`PRD_登录模块_密码登录错误限制调整为可配置.md`
- 代码规范:`_PRD_规范文档_代码规范.md`
......@@ -27,14 +27,14 @@ REQUIREMENTS_DIR = CONFIG_DIR / "需求文档"
PRD_DIR = CONFIG_DIR / "开发PRD"
# 测试用例文件
TEST_CASE_FILE = CONFIG_DIR / "测试用例" / "新统一平台权限管理测试用例.xlsx"
TEST_CASE_FILE = CONFIG_DIR / "测试用例" / "兰州中石化项目260319第一轮测试.xlsx"
# ==================== 输出路径 ====================
# 输出测试用例文件
OUTPUT_TEST_CASE = PERFECTED_DIR / "新统一平台权限管理测试用例_完善版本.xlsx"
OUTPUT_TEST_CASE = PERFECTED_DIR / "兰州中石化项目260319第一轮测试用例_完善版本.xlsx"
# 输出差异性报告文件
OUTPUT_REPORT = REPORTS_DIR / "新统一平台权限管理测试用例_差异性报告.docx"
OUTPUT_REPORT = REPORTS_DIR / "兰州中石化项目260319第一轮测试用例_差异性报告.docx"
# ==================== 测试用例列定义 ====================
# 测试用例Excel表格的列名
......
# OpenClaw API - 测试报告接口调用说明
> 文档版本:v1.0
>
> 更新时间:2026-03-17
>
> 适用对象:第三方系统对接开发人员
---
## 一、概述
本文档描述如何通过 OpenClaw API 创建测试报告,包含以下功能:
- 获取报告类型列表
- 上传富文本图片
- 创建测试报告(含图片、抄送人)
- 查询测试报告
- 更新测试报告
- 删除测试报告
---
## 二、接口基础信息
### 2.1 请求地址
| 环境 | 地址 |
|------|------|
| 生产环境 | `https://office.ubainsyun.com:5082/api/uerp` |
| 测试环境 | `http://127.0.0.1:8002/uerp` |
### 2.2 认证方式
所有接口需要在请求头中携带 API Key:
```
X-Api-Key: your_api_key_here
```
### 2.3 请求格式
| 请求类型 | Content-Type |
|----------|--------------|
| JSON 请求 | `application/json` |
| 文件上传 | `multipart/form-data` |
### 2.4 响应格式
所有接口返回 JSON 格式:
```json
{
"success": 1, // 1=成功, 0=失败
"data": { ... }, // 成功时返回数据
"error": 40000014, // 失败时的错误码
"msg": "错误信息" // 失败时的错误描述
}
```
---
## 三、接口列表
| 序号 | 接口名称 | 方法 | 路径 | 说明 |
|------|----------|------|------|------|
| 1 | 获取报告类型 | GET | `/openclaw/reporttype` | 获取可用的报告类型 |
| 2 | 上传图片 | POST | `/openclaw/upload/richtext` | 上传富文本图片 |
| 3 | 创建报告 | POST | `/openclaw/report` | 创建测试报告 |
| 4 | 查询报告 | GET | `/openclaw/report` | 查询测试报告列表 |
| 5 | 更新报告 | PUT | `/openclaw/report` | 更新测试报告 |
| 6 | 删除报告 | DELETE | `/openclaw/report` | 删除测试报告 |
| 7 | 获取人员列表 | GET | `/openclaw/stuff` | 获取抄送人列表 |
| 8 | 获取测试单列表 | GET | `/openclaw/testing` | 获取测试单列表 |
---
## 四、接口详情
### 4.1 获取报告类型
获取系统中可用的报告类型列表,用于创建报告时选择类型。
**请求**
```
GET /openclaw/reporttype
```
**请求头**
```
X-Api-Key: your_api_key
```
**响应示例**
```json
{
"success": 1,
"data": [
{"id": 1, "name": "进度报告"},
{"id": 2, "name": "测试报告"},
{"id": 21, "name": "问题报告"},
{"id": 22, "name": "节点催审"},
{"id": 23, "name": "总结报告"}
]
}
```
**字段说明**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 报告类型 ID,创建报告时使用 |
| name | string | 报告类型名称 |
---
### 4.2 上传图片
上传富文本编辑器中的图片,返回完整的图片访问 URL。
**请求**
```
POST /openclaw/upload/richtext
Content-Type: multipart/form-data
```
**请求头**
```
X-Api-Key: your_api_key
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | file | 是 | 图片文件 |
| name | string | 否 | 文件名称,默认使用上传文件名 |
**请求示例 (cURL)**
```bash
curl -X POST "https://office.ubainsyun.com:5015/uerp/openclaw/upload/richtext" \
-H "X-Api-Key: your_api_key" \
-F "file=@/path/to/image.png" \
-F "name=screenshot.png"
```
**响应示例**
```json
{
"success": 1,
"data": {
"id": 12444,
"name": "screenshot.png",
"path": "upload/20260317223125747.png",
"url": "https://office.ubainsyun.com:5015/upload/20260317223125747.png",
"type": ".png",
"size": 209
}
}
```
**字段说明**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 文件记录 ID |
| name | string | 文件名称 |
| path | string | 相对路径 |
| url | string | **完整访问 URL,可直接用于富文本** |
| type | string | 文件扩展名 |
| size | int | 文件大小(字节) |
---
### 4.3 创建测试报告
创建一条测试报告,关联到指定的测试单。
**请求**
```
POST /openclaw/report
Content-Type: application/json
```
**请求头**
```
X-Api-Key: your_api_key
Content-Type: application/json
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| developtesting_id | int | 是 | 关联的测试单 ID |
| type_id | int | 是 | 报告类型 ID |
| content | string | 是 | 报告内容(HTML 格式) |
| descript | string | 否 | 风险问题描述 |
| sort | int | 否 | 排序,默认 0 |
| copyuserList | array | 否 | 抄送人 ID 列表,如 `[1, 24, 31]` |
**请求示例**
```json
{
"developtesting_id": 32,
"type_id": 2,
"content": "<p>本次测试覆盖以下功能:</p><ul><li>用户登录</li><li>数据导出</li></ul><p>测试结果:<img src=\"https://office.ubainsyun.com:5015/upload/20260317223125747.png\"/></p>",
"descript": "发现 2 个中风险问题,已转交开发处理",
"copyuserList": [1, 24, 31]
}
```
**响应示例**
```json
{
"success": 1,
"data": {
"create": 9
}
}
```
**字段说明**
| 字段 | 类型 | 说明 |
|------|------|------|
| create | int | 新创建的报告 ID |
---
### 4.4 查询测试报告
查询指定测试单下的所有报告。
**请求**
```
GET /openclaw/report?developtesting_id={id}
```
**请求头**
```
X-Api-Key: your_api_key
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| developtesting_id | int | 是 | 测试单 ID |
**请求示例**
```bash
curl -X GET "https://office.ubainsyun.com:5015/uerp/openclaw/report?developtesting_id=32" \
-H "X-Api-Key: your_api_key"
```
**响应示例**
```json
{
"success": 1,
"data": [
{
"id": 9,
"type_id": 2,
"type_name": "测试报告",
"content": "<p>本次测试覆盖以下功能...</p>",
"descript": "发现 2 个中风险问题",
"history": "2026-03-17 22:31:38 - 郑晓兵 - 创建\r\n",
"sort": 0,
"createuser_id": 20,
"createuser_name": "郑晓兵",
"createtime": "2026-03-17T22:31:38",
"updatetime": "2026-03-17T22:31:38",
"copyuserList": [
{"id": 1, "name": "超管员"},
{"id": 24, "name": "黄史恭"},
{"id": 31, "name": "欧阳友平"}
]
}
]
}
```
**字段说明**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 报告 ID |
| type_id | int | 报告类型 ID |
| type_name | string | 报告类型名称 |
| content | string | 报告内容(HTML) |
| descript | string | 风险问题描述 |
| history | string | 操作历史记录 |
| createuser_id | int | 创建人 ID |
| createuser_name | string | 创建人姓名 |
| createtime | string | 创建时间 |
| updatetime | string | 更新时间 |
| copyuserList | array | 抄送人列表 |
---
### 4.5 更新测试报告
更新已有的测试报告。
**请求**
```
PUT /openclaw/report
Content-Type: application/json
```
**请求头**
```
X-Api-Key: your_api_key
Content-Type: application/json
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | int | 是 | 报告 ID |
| type_id | int | 否 | 报告类型 ID |
| content | string | 否 | 报告内容 |
| descript | string | 否 | 风险问题描述 |
| action | string | 否 | 操作描述,记录到历史 |
| copyuserList | array | 否 | 抄送人 ID 列表(会覆盖原有列表) |
**请求示例**
```json
{
"id": 9,
"content": "<p>更新后的报告内容...</p>",
"descript": "风险问题已修复",
"action": "更新测试结果",
"copyuserList": [1, 24]
}
```
**响应示例**
```json
{
"success": 1,
"data": {
"update": 9
}
}
```
---
### 4.6 删除测试报告
软删除测试报告(标记为已删除,不从数据库物理删除)。
**请求**
```
DELETE /openclaw/report
Content-Type: application/json
```
**请求头**
```
X-Api-Key: your_api_key
Content-Type: application/json
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | int | 是 | 报告 ID |
**请求示例**
```json
{
"id": 9
}
```
**响应示例**
```json
{
"success": 1,
"data": {
"delete": 9
}
}
```
---
### 4.7 获取人员列表
获取系统中所有人员,用于选择抄送人。
**请求**
```
GET /openclaw/stuff
```
**请求头**
```
X-Api-Key: your_api_key
```
**响应示例**
```json
{
"success": 1,
"data": [
{"id": 1, "name": "超管员", "department_id": null},
{"id": 20, "name": "郑晓兵", "department_id": 94020884},
{"id": 24, "name": "黄史恭", "department_id": 596543063},
{"id": 31, "name": "欧阳友平", "department_id": 363531662}
]
}
```
**字段说明**
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 人员 ID,用于抄送人列表 |
| name | string | 人员姓名 |
| department_id | int | 部门 ID |
---
### 4.8 获取测试单列表
获取测试单列表,用于确定报告关联的测试单 ID。
**请求**
```
GET /openclaw/testing?pageNum=1&pageSize=20
```
**请求头**
```
X-Api-Key: your_api_key
```
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageNum | int | 否 | 页码,默认 1 |
| pageSize | int | 否 | 每页数量,默认 20 |
**响应示例**
```json
{
"success": 1,
"pageSize": 20,
"pageNum": 1,
"totalPages": 5,
"totalRows": 22,
"data": [
{
"id": 32,
"name": "禅道测试",
"requirement": "<p>测试需求描述</p>",
"status_id": 1,
"status_name": "指派中",
"project_id": 4304,
"project_name": "测试项目8",
"createuser_id": 20,
"createuser_name": "郑晓兵"
}
]
}
```
---
## 五、完整调用流程
### 5.1 流程图
```
┌─────────────────────────────────────────────────────────────────┐
│ 创建测试报告完整流程 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1. 获取测试单 ID │
│ GET /openclaw/testing │
│ 目的:找到需要创建报告的测试单 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 2. 获取报告类型 ID │
│ GET /openclaw/reporttype │
│ 目的:确定报告类型(测试报告/进度报告等) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 3. 上传图片(可选) │
│ POST /openclaw/upload/richtext │
│ 目的:获取图片完整 URL,用于富文本内容 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 4. 获取抄送人 ID(可选) │
│ GET /openclaw/stuff │
│ 目的:获取人员 ID,用于抄送列表 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 5. 创建测试报告 │
│ POST /openclaw/report │
│ 提交:测试单ID、报告类型、内容、抄送人 │
└─────────────────────────────────────────────────────────────────┘
```
### 5.2 最小调用流程
如果已知测试单 ID 和报告类型 ID,只需一步即可创建报告:
```bash
curl -X POST "https://office.ubainsyun.com:5015/uerp/openclaw/report" \
-H "X-Api-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"developtesting_id": 32,
"type_id": 2,
"content": "<p>测试报告内容</p>",
"descript": "无风险问题"
}'
```
---
## 六、代码示例
### 6.1 Python 示例
```python
import requests
from typing import List, Optional
class OpenclawReportAPI:
"""OpenClaw 测试报告 API 客户端"""
def __init__(self, base_url: str, api_key: str):
"""
初始化客户端
Args:
base_url: API 基础地址,如 https://office.ubainsyun.com:5015/uerp
api_key: API Key
"""
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.headers = {
'X-Api-Key': api_key,
'Content-Type': 'application/json'
}
def get_report_types(self) -> list:
"""获取报告类型列表"""
resp = requests.get(
f'{self.base_url}/openclaw/reporttype',
headers=self.headers
)
result = resp.json()
if result['success'] == 1:
return result['data']
raise Exception(f"获取报告类型失败: {result.get('msg')}")
def get_users(self) -> list:
"""获取人员列表"""
resp = requests.get(
f'{self.base_url}/openclaw/stuff',
headers=self.headers
)
result = resp.json()
if result['success'] == 1:
return result['data']
raise Exception(f"获取人员列表失败: {result.get('msg')}")
def get_testing_list(self, page_num: int = 1, page_size: int = 20) -> dict:
"""获取测试单列表"""
resp = requests.get(
f'{self.base_url}/openclaw/testing',
headers=self.headers,
params={'pageNum': page_num, 'pageSize': page_size}
)
result = resp.json()
if result['success'] == 1:
return result
raise Exception(f"获取测试单列表失败: {result.get('msg')}")
def upload_image(self, file_path: str, name: Optional[str] = None) -> str:
"""
上传图片
Args:
file_path: 图片文件路径
name: 文件名(可选)
Returns:
图片完整访问 URL
"""
with open(file_path, 'rb') as f:
files = {'file': f}
data = {'name': name} if name else {}
resp = requests.post(
f'{self.base_url}/openclaw/upload/richtext',
headers={'X-Api-Key': self.api_key},
files=files,
data=data
)
result = resp.json()
if result['success'] == 1:
return result['data']['url']
raise Exception(f"上传图片失败: {result.get('msg')}")
def create_report(
self,
developtesting_id: int,
type_id: int,
content: str,
descript: str = '',
copyuser_list: Optional[List[int]] = None
) -> int:
"""
创建测试报告
Args:
developtesting_id: 测试单 ID
type_id: 报告类型 ID
content: 报告内容(HTML 格式)
descript: 风险问题描述
copyuser_list: 抄送人 ID 列表
Returns:
新创建的报告 ID
"""
data = {
'developtesting_id': developtesting_id,
'type_id': type_id,
'content': content,
'descript': descript,
'copyuserList': copyuser_list or []
}
resp = requests.post(
f'{self.base_url}/openclaw/report',
headers=self.headers,
json=data
)
result = resp.json()
if result['success'] == 1:
return result['data']['create']
raise Exception(f"创建报告失败: {result.get('msg')}")
def get_reports(self, developtesting_id: int) -> list:
"""
查询测试报告列表
Args:
developtesting_id: 测试单 ID
Returns:
报告列表
"""
resp = requests.get(
f'{self.base_url}/openclaw/report',
headers=self.headers,
params={'developtesting_id': developtesting_id}
)
result = resp.json()
if result['success'] == 1:
return result['data']
raise Exception(f"查询报告失败: {result.get('msg')}")
def update_report(
self,
report_id: int,
content: Optional[str] = None,
descript: Optional[str] = None,
action: Optional[str] = None,
copyuser_list: Optional[List[int]] = None
) -> int:
"""
更新测试报告
Args:
report_id: 报告 ID
content: 新的报告内容
descript: 新的风险问题描述
action: 操作描述
copyuser_list: 新的抄送人列表
Returns:
更新的报告 ID
"""
data = {'id': report_id}
if content is not None:
data['content'] = content
if descript is not None:
data['descript'] = descript
if action is not None:
data['action'] = action
if copyuser_list is not None:
data['copyuserList'] = copyuser_list
resp = requests.put(
f'{self.base_url}/openclaw/report',
headers=self.headers,
json=data
)
result = resp.json()
if result['success'] == 1:
return result['data']['update']
raise Exception(f"更新报告失败: {result.get('msg')}")
def delete_report(self, report_id: int) -> int:
"""
删除测试报告
Args:
report_id: 报告 ID
Returns:
删除的报告 ID
"""
resp = requests.delete(
f'{self.base_url}/openclaw/report',
headers=self.headers,
json={'id': report_id}
)
result = resp.json()
if result['success'] == 1:
return result['data']['delete']
raise Exception(f"删除报告失败: {result.get('msg')}")
# ==================== 使用示例 ====================
def example_create_report_with_image():
"""示例:创建包含图片的测试报告"""
# 初始化客户端
api = OpenclawReportAPI(
base_url='https://office.ubainsyun.com:5015/uerp',
api_key='your_api_key_here'
)
# 1. 获取测试单列表,找到目标测试单
testing_list = api.get_testing_list(page_size=100)
target_testing = None
for item in testing_list['data']:
if item['name'] == '禅道测试':
target_testing = item
break
if not target_testing:
print("未找到目标测试单")
return
print(f"找到测试单: {target_testing['name']} (ID: {target_testing['id']})")
# 2. 获取报告类型
report_types = api.get_report_types()
type_id = None
for t in report_types:
if t['name'] == '测试报告':
type_id = t['id']
break
print(f"报告类型 ID: {type_id}")
# 3. 上传图片
image_url = api.upload_image('/path/to/screenshot.png')
print(f"图片 URL: {image_url}")
# 4. 获取抄送人
users = api.get_users()
copyuser_ids = [u['id'] for u in users if u['name'] in ['超管员', '黄史恭']]
print(f"抄送人 ID: {copyuser_ids}")
# 5. 创建报告
content = f'''
<h3>测试报告</h3>
<p><strong>测试时间:</strong>2026-03-17</p>
<p><strong>测试人员:</strong>张三</p>
<h4>测试内容</h4>
<ul>
<li>用户登录功能</li>
<li>数据导出功能</li>
<li>权限验证功能</li>
</ul>
<h4>测试结果截图</h4>
<p><img src="{image_url}" alt="测试截图" style="max-width: 600px;"/></p>
<h4>测试结论</h4>
<p>所有功能测试通过,无阻塞性问题。</p>
'''
report_id = api.create_report(
developtesting_id=target_testing['id'],
type_id=type_id,
content=content,
descript='发现 2 个中风险问题,已转交开发处理',
copyuser_list=copyuser_ids
)
print(f"报告创建成功! ID: {report_id}")
# 6. 查询确认
reports = api.get_reports(target_testing['id'])
for r in reports:
if r['id'] == report_id:
print(f"确认报告已创建: {r['type_name']} by {r['createuser_name']}")
break
if __name__ == '__main__':
example_create_report_with_image()
```
### 6.2 cURL 示例
```bash
#!/bin/bash
# 配置
API_KEY="your_api_key_here"
BASE_URL="https://office.ubainsyun.com:5015/uerp"
# 1. 获取报告类型
echo "=== 获取报告类型 ==="
curl -s "${BASE_URL}/openclaw/reporttype" -H "X-Api-Key: ${API_KEY}" | jq .
# 2. 上传图片
echo -e "\n=== 上传图片 ==="
IMAGE_RESULT=$(curl -s -X POST "${BASE_URL}/openclaw/upload/richtext" \
-H "X-Api-Key: ${API_KEY}" \
-F "file=@./screenshot.png")
echo $IMAGE_RESULT | jq .
IMAGE_URL=$(echo $IMAGE_RESULT | jq -r '.data.url')
echo "图片 URL: ${IMAGE_URL}"
# 3. 创建报告
echo -e "\n=== 创建报告 ==="
REPORT_RESULT=$(curl -s -X POST "${BASE_URL}/openclaw/report" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"developtesting_id\": 32,
\"type_id\": 2,
\"content\": \"<p>测试报告</p><p><img src=\\\"${IMAGE_URL}\\\"/></p>\",
\"descript\": \"无风险问题\",
\"copyuserList\": [1, 24, 31]
}")
echo $REPORT_RESULT | jq .
REPORT_ID=$(echo $REPORT_RESULT | jq -r '.data.create')
echo "报告 ID: ${REPORT_ID}"
# 4. 查询报告
echo -e "\n=== 查询报告 ==="
curl -s "${BASE_URL}/openclaw/report?developtesting_id=32" \
-H "X-Api-Key: ${API_KEY}" | jq .
```
---
## 七、错误码说明
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 40000001 | 参数错误或业务异常 | 检查请求参数是否正确 |
| 40000014 | 无效的 API Key | 检查 X-Api-Key 是否正确 |
| 40000014 | API Key 已过期 | 联系管理员延长有效期 |
| 40000014 | 无权限访问该接口 | 联系管理员添加接口权限 |
| 40000001 | 缺少测试单 ID | 创建报告时必须传 developtesting_id |
| 40000001 | 缺少报告 ID | 更新/删除报告时必须传 id |
---
## 八、注意事项
1. **图片 URL 格式**:上传接口返回的 `url` 字段是完整 URL,可直接用于富文本 `<img src="">`
2. **抄送人列表**`copyuserList` 接收人员 ID 数组,如 `[1, 24, 31]`,更新时会完全覆盖原有列表
3. **富文本内容**`content` 字段支持 HTML 格式,建议使用标准 HTML 标签
4. **测试单关联**:报告必须关联到有效的测试单 ID,可通过 `/openclaw/testing` 接口获取
5. **删除是软删除**:删除操作只是标记 `delflag=1`,数据仍保留在数据库中
6. **API Key 权限**:确保 API Key 已添加 `reporttype``report` 权限
---
## 九、联系支持
如有问题,请联系 ERP 系统管理员获取技术支持。
# 生成报告后自动调用ERP上传
## 代码路径
- 代码路径:[AuxiliaryTool/FunctionalTestReportGeneration]
- ERP接口PRD:[Docs/PRD/自动化生成功能测试报告/ERP对接PRD/PRD_测试列表_测试报告_例子说明.md]
## 功能需求
### 功能目标
**目标:** 生成报告文件后,将文件转换成富文本形式通过调用ERP接口实现上传测试报告。
### 需求描述
#### 报告文件获取
- 功能测试报告路径:[AuxiliaryTool/FunctionalTestReportGeneration/reports/****.docx]
### 测试报告上传逻辑
#### 文件获取
- 在执行脚本后会在[AuxiliaryTool/FunctionalTestReportGeneration/reports/]目录下输出一份word文档。
- 使用Spire.Doc库将文件内容转换为富文本参数,但在接口传参中content不支持base64格式,所以图片需要单独处理。
- 示例代码:
```ignorelang
from spire.doc import *
from spire.doc.common import *
import requests
import os
import tempfile
def extract_images_from_word(doc):
"""从Word文档中提取所有图片,返回图片字节数组列表"""
images = []
# 遍历文档中的所有节
for section in doc.Sections:
# 遍历节中的所有段落
for paragraph in section.Paragraphs:
# 遍历段落中的所有子对象
for child_obj in paragraph.ChildObjects:
# 判断是否为图片对象
if child_obj.DocumentObjectType == DocumentObjectType.Picture:
picture = child_obj
# 获取图片数据
images.append(picture.Image.ImageData)
return images
def word_to_html_upload_images(file_path, api_key, base_url):
"""
将Word转HTML,图片单独上传获取URL
Args:
file_path: Word文件路径
api_key: ERP接口密钥
base_url: ERP接口地址
Returns:
HTML字符串,图片已替换为ERP URL
"""
# 1. 加载Word文档
document = Document()
document.LoadFromFile(file_path)
# 2. 提取所有图片
image_data_list = extract_images_from_word(document)
# 3. 上传图片获取URL列表
image_urls = []
temp_dir = tempfile.mkdtemp()
for idx, img_data in enumerate(image_data_list):
# 保存临时图片文件
temp_img_path = os.path.join(temp_dir, f"image_{idx}.png")
with open(temp_img_path, 'wb') as f:
f.write(img_data)
# 上传到ERP
with open(temp_img_path, 'rb') as f:
files = {'file': f}
headers = {'X-Api-Key': api_key}
resp = requests.post(
f'{base_url}/openclaw/upload/richtext',
headers=headers,
files=files
)
if resp.json()['success'] == 1:
image_urls.append(resp.json()['data']['url'])
else:
print(f"图片{idx}上传失败: {resp.json().get('msg')}")
image_urls.append(None)
# 4. 转换为HTML(不嵌入图片)
temp_html = os.path.join(temp_dir, "temp.html")
document.HtmlExportOptions.ImageEmbedded = False
document.SaveToFile(temp_html, FileFormat.Html)
# 5. 读取HTML并替换图片路径为URL
with open(temp_html, 'r', encoding='utf-8') as f:
html_content = f.read()
# Spire.Doc生成的图片路径格式如: temp_html_files/image1.png
# 需要替换为ERP返回的URL
for idx, url in enumerate(image_urls):
if url:
# 替换本地路径为ERP URL
old_pattern = f'temp_html_files/image{idx+1}.png'
html_content = html_content.replace(old_pattern, url)
# 清理临时文件
import shutil
shutil.rmtree(temp_dir, ignore_errors=True)
# 6. 去除水印
html_content = html_content.replace('Evaluation Warning: The document was created with Spire.Doc for Python.', '')
return html_content
```
#### ERP对接方式
- API key值为:9adc1ce611544fae9bbbc5f6b1d6ce87
- 生产环境为:https://office.ubainsyun.com:5082/api/uerp
- 读取ERP对接文档的第4.3节内容了解请求参数及接口路径
- developtesting_id:暂时为空;
- type_id:先默认为`2`
- content:文件转换的富文本参数;
- copyuserList:先为空;
- descript:先为空
- 接口调用补充重试机制,如ERP接口返回错误则进行重试,最多重试3次。
#### 交互方式
- 运行脚本生成报告文件后,提示是否需要上传测试报告至ERP测试单
- 选是:则自动进行上传测试报告到ERP测试单的操作;
- 选否:则不执行上传测试报告操作。
#### 日志审计
- 记录每一步骤的日志信息打印至控制台。
## 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论