Files
prompts/a2s-v1_2-project_stats.md
2026-07-23 14:52:14 +08:00

408 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 需求概述
统计项目运营数据相关功能开发。
## 核心技术栈
- JavaOpenJDK21
- SpringBoot3.2.9
- MybatisPlus3.5.7
- RocketMQ
- spring-boot-starter2.2.3
- rocketmq-client4.9.8
- MySQL8.0+
- Redis8.0+
## 目录说明
- 根目录:"E:\gitlab\mis-dev\mis-modules\mis-agent\src\main\java\com\lcfc\agent\stats"
- Controller"<根目录>\api"
- Service"<根目录>\service"
- 配置类:"<根目录>\conf"
- DTO"<根目录>\domain\dto"
- VO"<根目录>\domain\vo"
- Enum"<根目录>\domain\enums"
- Entity"<根目录>\domain\entity"
- Mapper
- Java源文件"<根目录>\domain\mapper"
- XML文件"E:\gitlab\mis-dev\mis-modules\mis-agent\src\main\resources\mapper"
- 消息队列相关
- 消息载体:"<根目录>\mq\event"
- 监听器:"<根目录>\mq\consumer"
- SQL文件"E:\gitlab\mis-dev\mis-modules\mis-agent\src\main\resources\sql-stats"
## 公共约定
- 枚举类、常量类、工具函数、VO、DTO均不能复用其它目录下面的可以抄一个放到<目录说明>中对应的目录下,因为该模块未来可能会独立出去
## 示例
- 接口文档https://git.jkwlstv.cn/doc/prompts/raw/branch/main/example_rest_api_doc.md
- MySQL建表语句https://git.jkwlstv.cn/doc/prompts/raw/branch/main/example_mysql_ddl.md
- 实体类https://git.jkwlstv.cn/doc/prompts/raw/branch/main/example_entity.md
- 枚举类https://git.jkwlstv.cn/doc/prompts/raw/branch/main/example_enum.md
## 任务清单
### 生成表结构
- 数据库表结构字段名称、实体类字段名称,采用驼峰与下划线映射的方式。
- 查看<Entity>目录下所有被`@TableName`注解修饰的源文件生成MySQL建表语句建表语句存放到<SQL文件>目录下。
- 包含`project_id`字段的表都需要对它建立普通索引。
-`project_stats_registry`表中`project_name``agent_id`要创建唯一索引。
-`project_data_stats_daily`表中`project_id``daily`要创建唯一索引。
- 实体类字段包含`@NotNull`注解,你需要结合`@Schema`注解中的`defaultValue`属性判断是否需要设置默认值。
- 主键列是否需要用MySQL的自增序列取决于实体类被`@TableId`注解修饰的字段,要看其`type`属性,如果是`IdType.AUTO`则使用自增,否则不需要。
- 如果`@Schema`注解指定了`maxLength`属性,按如下原则设置字段类型:
- 数值小于或等于2000使用VARCHAR
- 数值大于2000且小于10000使用TEXT
- 数值大于10000使用LONGTEXT
### 项目日志
#### 清理过期错误日志
根据`ProjectStatsLogConfig.java``cleanExpireErrorLogRedisKey`属性从Redis中查询是否存在该Key如果存在不执行任何动作不存在则视为今天没有清理过需要执行清理动作先将该Key存储到Redis中值设置为"1",过期时间的计算公式:
```java
public static long endDayOfMills() {
LocalDateTime endOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
LocalDateTime now = LocalDateTime.now();
return Duration.between(now, endOfDay).toMillis();
}
```
RedisKey存储成功后开启一个虚拟线程物理删除N天前的日志N的数值取自`ProjectStatsLogConfig.java``errorLogExpireDays`属性。
#### 创建错误日志
执行一遍<清理过期错误日志>然后创建`ProjectStatsLog.java`对象然后调用Mapper接口相关方法保存要求如下
- 处理日志过程中发生异常打印一条log即可不要抛异常到外层。
-`ERROR`填充`logLevel`字段
- 用当前时间填充`createTime`字段
- 调用方至少要上报`description``exceptionStackTrace``keyParameters`其中之一。如果都没上报,这条日志就不记了。
- 如果`description`不为空,要看字符长度是否大于`ProjectStatsLogConfig.java``descriptionMaxLength`属性,超长则截断。
- 如果`exceptionStackTrace`不为空,要看字符长度是否大于`ProjectStatsLogConfig.java``exceptionStackTraceMaxLength`属性,超长则截断。
#### 创建普通日志
执行一遍<清理过期错误日志>然后创建`ProjectStatsLog.java`对象然后调用Mapper接口相关方法保存要求如下
- 处理日志过程中发生异常打印一条log即可不要抛异常到外层。
-`INFO`填充`logLevel`字段
- 用当前时间填充`createTime`字段
- 调用方至少要上报`description``keyParameters`其中之一。如果都没上报,这条日志就不记了。
- 如果`description`不为空,要看字符长度是否大于`ProjectStatsLogConfig.java``descriptionMaxLength`属性,超长则截断。
### 监听器
按照业务逻辑实现各个监听器的`onMessage`方法。使用try-catch语法捕获异常如果遇到异常打印一条log并参考<创建错误日志>生成1条错误日志即可。
#### 匹配项目注册表
根据`projectName``agentId`字段去查询`project_stats_registry`表,查不到数据参考<创建错误日志>生成1条错误日志`description`字段赋值"项目未注册"`keyParameters`赋值`onMessage`方法入参的对象。查得到数据视为满足要求,返回`ProjectStatsRegistry`对象,并继续执行业务逻辑。
#### ProjectAgentChatLifeCycleEventConsumer.java
先<匹配项目注册表>满足要求再按下述要求继续执行:
1. 根据今天的日期,查看`project_data_stats_daily`是否有数据,没数据`insert`否则`update`,请通过`ON DUPLICATE KEY UPDATE`实现。
2.`inputToken``outputToken``callMcpToolCount``callSkillCount`字段累加到`project_data_stats_daily``project_data_stats`相关字段。
3. 如果`kbDocUseInfo`不为空,要把相同的`kbId`合并成一条,把`docIds`去重合并。
4. 如果`mcpUseInfo`不为空,要把相同的`mcpServerId`合并成一条,把`toolNames`去重合并。
5. 如果`skillUseInfo`不为空,要把`skillNames`去重合并。
6. 每收到1条数据`project_data_stats_daily``project_data_stats`中的`chatNum`字段+1。
7.`userId`去重合并到`project_data_stats_daily``project_data_stats`中的`user_id`,同时更新`userNum`字段,数值是`userId`的个数。
8. 修改数据库表数据时,所有包含`updateTime`的表,都要设置为当前时间。
#### ProjectInfoUpdateEventConsumer.java
如果`ProjectInfoUpdatePayload.java`中的`before``after`的字段其中1个是Null都不执行任何操作否则按如下要求执行
1. 根据`before`中的`projectName``agentId`字段查询`project_stats_registry`表。查不到数据执行第2步然后结束。查到数据从第3步继续执行。
2. 参考<创建错误日志>生成1条错误日志`description`赋值"项目未注册"`keyParameters`赋值`ProjectInfoUpdatePayload.java`对象。
3. 参考<创建普通日志>生成1条普通日志`description`赋值"项目信息变更"`keyParameters`赋值`ProjectInfoUpdatePayload.java`对象。
4.`after`中的字段去覆盖第1步查出来数据注意只有不为空才更新
#### ProjectUserDisLikeEventConsumer.java
先<匹配项目注册表>满足要求则执行:
-`dateTime`获取`LocalDate`值,结合`projectId``daily`去查询`ProjectDataStatsDaily`,存在数据将其`userDislikeCount`字段原子化+1即可。
- 根据`projectId`查询`ProjectDataStats`,存在数据将其`userDislikeCount`字段原子化+1即可。
#### ProjectUserDisLikeUndoEventConsumer.java
先<匹配项目注册表>满足要求则执行:
-`dateTime`获取`LocalDate`值,结合`projectId``daily`去查询`ProjectDataStatsDaily`,存在数据将其`userDislikeCount`字段原子化-1即可。
- 根据`projectId`查询`ProjectDataStats`,存在数据将其`userDislikeCount`字段原子化-1即可。
#### ProjectUserFeedbackEventConsumer.java
先<匹配项目注册表>满足要求则执行:
-`dateTime`获取`LocalDate`值,结合`projectId``daily`去查询`ProjectDataStatsDaily`,存在数据将其`userFeedbackCount`字段原子化+1即可。
- 根据`projectId`查询`ProjectDataStats`,存在数据将其`userFeedbackCount`字段原子化+1即可。
#### ProjectUserFeedbackUndoEventConsumer.java
先<匹配项目注册表>满足要求则执行:
-`dateTime`获取`LocalDate`值,结合`projectId``daily`去查询`ProjectDataStatsDaily`,存在数据将其`userFeedbackCount`字段原子化-1即可。
- 根据`projectId`查询`ProjectDataStats`,存在数据将其`userFeedbackCount`字段原子化-1即可。
### ProjectStatsController.java
#### ProjectStatsControllerRules
1. 分页接口参考`AgentPageController.java`中的`searchSingleAgent`方法去实现。
2. 数据图表请求参数中的`维度`分为:日、月、年,字段请使用枚举类。与维度一同出现的参数还有`begin``end``end`的数值可以等于`begin`但不能小于它。
-`维度`等于`日``begin``end`的上报格式:"yyyy-MM-dd"
-`维度`等于`月``begin``end`的上报格式:"yyyy-MM"
-`维度`等于`年``begin``end`的上报格式:"yyyy"
3. 数据图表响应参数中的`x`代表图表的横坐标,根据不同的`维度`填充不同的数值,规则如下:
-`维度`等于`日``begin``end`之间的日期,包含`begin``end`,格式:"yyyy-MM-dd"
-`维度`等于`月``begin``end`之间的月份,包含`begin``end`,格式:"yyyy-MM"
-`维度`等于`年``begin``end`之间的年份,包含`begin``end`,格式:"yyyy"
4. 数据图表在根据`月``年`这2个维度查询图表数据的时候涉及到计算的部分不要在数据库进行1个项目1年的数据也就300多条按需查询对应字段使用Java完成计算优先使用`Stream`
5. Token的显示规则与单位请参考`AgentChatMetricsServiceImpl.java`中的`getAgentMetricsLineChartToken`处理逻辑
#### 项目数据统计(需要分页)
- 请求方式POST
- URL/project/stats/page
- Content-Typeapplication/json
使用`project_stats_registry``project_data_stats`关联查询相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|pageSize|Integer|分页参数1页的条数默认值20|
|pageNum|Integer|分页参数1页的条数默认值1|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|项目ID|String|无|
|项目名称|String|无|
|用户数|Long|无|
|对话数|Long|无|
|消耗Token|String|输入和输出token相加显示规则参考<ProjectStatsControllerRules>|
|输入Token|String|显示规则参考<ProjectStatsControllerRules>|
|输出Token|String|显示规则参考<ProjectStatsControllerRules>|
|知识库文档数|Long|用kbDocNum|
|MCP工具调用次数|Long|无|
|Skill调用次数|Long|无|
#### 累计数据
- 请求方式GET
- URL/project/stats/total?projectId={projectId}
根据`projectId`查询并计算`project_data_stats`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|累计用户数|Long|无|
|累计对话数|Long|无|
|累计消耗Token|String|累计输入和累计输出token相加显示规则参考<ProjectStatsControllerRules>|
|累计输入Token|String|显示规则参考<ProjectStatsControllerRules>|
|累计输出Token|String|显示规则参考<ProjectStatsControllerRules>|
|知识库文档数|Long|用kbDocNum|
|知识库文档有效率|String|计算公式:调用的文档数量(去重)/ kbDocNum精度四舍五入保留两位小数|
|累计点踩数|Long|无|
|点踩率|String|计算公式=累计点踩数/累计对话数,精度:四舍五入保留两位小数|
|累计反馈数|Long|无|
|累计MCP工具调用个数|Long|计算所有mcpUseInfo中的toolNames总个数|
|累计MCP工具调用次数|Long|无|
|累计Skill调用个数|Long|提取skillUseInfo中的skillNames的个数|
|累计Skill调用次数|Long|无|
#### 数据图表(用户数)
- 请求方式POST
- URL/project/stats/chart/user
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>|如果维度是`日`,直接使用`userNum`,否则把每一天数据里的`userId`合并去重后计算总个数|
#### 数据图表(对话数)
- 请求方式POST
- URL/project/stats/chart/chat
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>|如果维度是`日`,直接使用`chatNum`,否则根据维度分组并求和|
#### 数据图表消耗Token
- 请求方式POST
- URL/project/stats/chart/token
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
|type|枚举|三种类型all、input、output|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>| 如果请求参数的`type`的值是`all`,取`inputToken``outputToken`相加的值。如果是`input``inputToken`。如果是`output``inputToken`。如果维度是`日`,直接返回`type`对应的数值,否则根据维度分组并求和,显示规则参考<ProjectStatsControllerRules>|
#### 数据图表(对话点踩数)
- 请求方式POST
- URL/project/stats/chart/dislike
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>| 如果维度是`日`,直接使用`userDislikeCount`,否则根据维度分组并求和|
#### 数据图表(对话反馈数)
- 请求方式POST
- URL/project/stats/chart/feedback
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>| 如果维度是`日`,直接使用`userFeedbackCount`,否则根据维度分组并求和|
#### 数据图表MCP使用情况
- 请求方式POST
- URL/project/stats/chart/mcp/usage
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
|type|枚举值|两种类型num=个数,count=次数|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>|如果请求参数中的`type``num`,则需要对`mcpUseInfo``mcpServerId`进行分组并去重`toolNames`,计算`toolNames`的个数,如果`type``count`,则使用`callMcpToolCount`。如果维度是`日`,直接返回`type`对应的数值,否则根据维度分组并求和|
#### 数据图表Skill使用情况
- 请求方式POST
- URL/project/stats/chart/skill/usage
- Content-Typeapplication/json
查询并计算`project_data_stats_daily`相关数据。
##### 请求参数
| 参数名称 | 类型 | 说明 |
|:-----|:-----|:-----|
|projectId|String|项目ID|
|维度|枚举|参考<ProjectStatsControllerRules>|
|begin|String|参考<ProjectStatsControllerRules>|
|end|String|参考<ProjectStatsControllerRules>|
|type|枚举值|两种类型num=个数,count=次数|
##### 响应参数
| 参数名称 | 类型 | 备注 |
|:-----|:-----|:-----|
|x|List<String>|参考<ProjectStatsControllerRules>|
|y|List<String>|如果请求参数中的`type``num`,则需要对`skillUseInfo``skillNames`合并然后去重`toolNames`计算个数,如果`type``count`,则使用`callSkillCount`。如果维度是`日`,直接返回`type`对应的数值,否则根据维度分组并求和|