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

18 KiB
Raw Blame History

需求概述

统计项目运营数据相关功能开发。

核心技术栈

  • 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均不能复用其它目录下面的可以抄一个放到<目录说明>中对应的目录下,因为该模块未来可能会独立出去

示例

任务清单

生成表结构

  • 数据库表结构字段名称、实体类字段名称,采用驼峰与下划线映射的方式。
  • 查看目录下所有被@TableName注解修饰的源文件生成MySQL建表语句建表语句存放到<SQL文件>目录下。
  • 包含project_id字段的表都需要对它建立普通索引。
  • project_stats_registry表中project_nameagent_id要创建唯一索引。
  • project_data_stats_daily表中project_iddaily要创建唯一索引。
  • 实体类字段包含@NotNull注解,你需要结合@Schema注解中的defaultValue属性判断是否需要设置默认值。
  • 主键列是否需要用MySQL的自增序列取决于实体类被@TableId注解修饰的字段,要看其type属性,如果是IdType.AUTO则使用自增,否则不需要。
  • 如果@Schema注解指定了maxLength属性,按如下原则设置字段类型:
    • 数值小于或等于2000使用VARCHAR
    • 数值大于2000且小于10000使用TEXT
    • 数值大于10000使用LONGTEXT
  • 实体类的属性类型与表结构的字段类型映射关系:
    • String类型默认使用VARCHAT(255)
    • Long类型默认使用BIGINT
    • Integer类型默认使用INT
    • LocalDate类型默认使用DATE
    • LocalDateTime类型默认使用DATETIME
    • @TableField注解中typeHandler等于JacksonTypeHandler.class表结构字段类型默认使用JSON

项目日志

清理过期错误日志

根据ProjectStatsLogConfig.javacleanExpireErrorLogRedisKey属性从Redis中查询是否存在该Key如果存在不执行任何动作不存在则视为今天没有清理过需要执行清理动作先将该Key存储到Redis中值设置为"1",过期时间的计算公式:

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.javaerrorLogExpireDays属性。

创建错误日志

执行一遍<清理过期错误日志>然后创建ProjectStatsLog.java对象然后调用Mapper接口相关方法保存要求如下

  • 处理日志过程中发生异常打印一条log即可不要抛异常到外层。
  • ERROR填充logLevel字段
  • 用当前时间填充createTime字段
  • 调用方至少要上报descriptionexceptionStackTracekeyParameters其中之一。如果都没上报,这条日志就不记了。
  • 如果description不为空,要看字符长度是否大于ProjectStatsLogConfig.javadescriptionMaxLength属性,超长则截断。
  • 如果exceptionStackTrace不为空,要看字符长度是否大于ProjectStatsLogConfig.javaexceptionStackTraceMaxLength属性,超长则截断。

创建普通日志

执行一遍<清理过期错误日志>然后创建ProjectStatsLog.java对象然后调用Mapper接口相关方法保存要求如下

  • 处理日志过程中发生异常打印一条log即可不要抛异常到外层。
  • INFO填充logLevel字段
  • 用当前时间填充createTime字段
  • 调用方至少要上报descriptionkeyParameters其中之一。如果都没上报,这条日志就不记了。
  • 如果description不为空,要看字符长度是否大于ProjectStatsLogConfig.javadescriptionMaxLength属性,超长则截断。

监听器

按照业务逻辑实现各个监听器的onMessage方法。使用try-catch语法捕获异常如果遇到异常打印一条log并参考<创建错误日志>生成1条错误日志即可。

匹配项目注册表

根据projectNameagentId字段去查询project_stats_registry表,查不到数据参考<创建错误日志>生成1条错误日志description字段赋值"项目未注册"keyParameters赋值onMessage方法入参的对象。查得到数据视为满足要求,返回ProjectStatsRegistry对象,并继续执行业务逻辑。

ProjectAgentChatLifeCycleEventConsumer.java

先<匹配项目注册表>满足要求再按下述要求继续执行:

  1. 根据今天的日期,查看project_data_stats_daily是否有数据,没数据insert否则update,请通过ON DUPLICATE KEY UPDATE实现。
  2. inputTokenoutputTokencallMcpToolCountcallSkillCount字段累加到project_data_stats_dailyproject_data_stats相关字段。
  3. 如果kbDocUseInfo不为空,要把相同的kbId合并成一条,把docIds去重合并。
  4. 如果mcpUseInfo不为空,要把相同的mcpServerId合并成一条,把toolNames去重合并。
  5. 如果skillUseInfo不为空,要把skillNames去重合并。
  6. 每收到1条数据project_data_stats_dailyproject_data_stats中的chatNum字段+1。
  7. userId去重合并到project_data_stats_dailyproject_data_stats中的user_id,同时更新userNum字段,数值是userId的个数。
  8. 修改数据库表数据时,所有包含updateTime的表,都要设置为当前时间。

ProjectInfoUpdateEventConsumer.java

如果ProjectInfoUpdatePayload.java中的beforeafter的字段其中1个是Null都不执行任何操作否则按如下要求执行

  1. 根据before中的projectNameagentId字段查询project_stats_registry表。查不到数据执行第2步然后结束。查到数据从第3步继续执行。
  2. 参考<创建错误日志>生成1条错误日志description赋值"项目未注册"keyParameters赋值ProjectInfoUpdatePayload.java对象。
  3. 参考<创建普通日志>生成1条普通日志description赋值"项目信息变更"keyParameters赋值ProjectInfoUpdatePayload.java对象。
  4. after中的字段去覆盖第1步查出来数据注意只有不为空才更新

ProjectUserDisLikeEventConsumer.java

先<匹配项目注册表>满足要求则执行:

  • dateTime获取LocalDate值,结合projectIddaily去查询ProjectDataStatsDaily,存在数据将其userDislikeCount字段原子化+1即可。
  • 根据projectId查询ProjectDataStats,存在数据将其userDislikeCount字段原子化+1即可。

ProjectUserDisLikeUndoEventConsumer.java

先<匹配项目注册表>满足要求则执行:

  • dateTime获取LocalDate值,结合projectIddaily去查询ProjectDataStatsDaily,存在数据将其userDislikeCount字段原子化-1即可。
  • 根据projectId查询ProjectDataStats,存在数据将其userDislikeCount字段原子化-1即可。

ProjectUserFeedbackEventConsumer.java

先<匹配项目注册表>满足要求则执行:

  • dateTime获取LocalDate值,结合projectIddaily去查询ProjectDataStatsDaily,存在数据将其userFeedbackCount字段原子化+1即可。
  • 根据projectId查询ProjectDataStats,存在数据将其userFeedbackCount字段原子化+1即可。

ProjectUserFeedbackUndoEventConsumer.java

先<匹配项目注册表>满足要求则执行:

  • dateTime获取LocalDate值,结合projectIddaily去查询ProjectDataStatsDaily,存在数据将其userFeedbackCount字段原子化-1即可。
  • 根据projectId查询ProjectDataStats,存在数据将其userFeedbackCount字段原子化-1即可。

ProjectStatsController.java

业务逻辑的实现存放到"E:\gitlab\mis-dev\mis-modules\mis-agent\src\main\java\com\lcfc\agent\stats\service\ProjectStatsDataQueryService.java"。

ProjectStatsControllerRules

  1. 分页接口参考AgentPageController.java中的searchSingleAgent方法去实现。

  2. 数据图表请求参数中的维度分为:日、月、年,字段请使用枚举类。与维度一同出现的参数还有beginendend的数值可以等于begin但不能小于它。

  • 维度等于beginend的上报格式:"yyyy-MM-dd"
  • 维度等于beginend的上报格式:"yyyy-MM"
  • 维度等于beginend的上报格式:"yyyy"
  1. 数据图表响应参数中的x代表图表的横坐标,根据不同的维度填充不同的数值,规则如下:
  • 维度等于beginend之间的日期,包含beginend,格式:"yyyy-MM-dd"
  • 维度等于beginend之间的月份,包含beginend,格式:"yyyy-MM"
  • 维度等于beginend之间的年份,包含beginend,格式:"yyyy"
  1. 数据图表在根据这2个维度查询图表数据的时候涉及到计算的部分不要在数据库进行1个项目1年的数据也就300多条按需查询对应字段使用Java完成计算优先使用Stream

  2. Token的显示规则与单位请参考AgentChatMetricsServiceImpl.java中的getAgentMetricsLineChartToken处理逻辑

项目数据统计(需要分页)

  • 请求方式POST
  • URL/project/stats/page
  • Content-Typeapplication/json

使用project_stats_registryproject_data_stats关联查询相关数据。

请求参数
参数名称 类型 说明
pageSize Integer 分页参数1页的条数默认值20
pageNum Integer 分页参数1页的条数默认值1
响应参数
参数名称 类型 备注
项目ID String
项目名称 String
用户数 Long
对话数 Long
消耗Token String 输入和输出token相加显示规则参考
输入Token String 显示规则参考
输出Token String 显示规则参考
知识库文档数 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相加显示规则参考
累计输入Token String 显示规则参考
累计输出Token String 显示规则参考
知识库文档数 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
维度 枚举 参考
begin String 参考
end String 参考
响应参数
参数名称 类型 备注
x List 参考
y List 如果维度是,直接使用userNum,否则把每一天数据里的userId合并去重后计算总个数

数据图表(对话数)

  • 请求方式POST
  • URL/project/stats/chart/chat
  • Content-Typeapplication/json

查询并计算project_data_stats_daily相关数据。

请求参数
参数名称 类型 说明
projectId String 项目ID
维度 枚举 参考
begin String 参考
end String 参考
响应参数
参数名称 类型 备注
x List 参考
y List 如果维度是,直接使用chatNum,否则根据维度分组并求和

数据图表消耗Token

  • 请求方式POST
  • URL/project/stats/chart/token
  • Content-Typeapplication/json

查询并计算project_data_stats_daily相关数据。

请求参数
参数名称 类型 说明
projectId String 项目ID
维度 枚举 参考
begin String 参考
end String 参考
type 枚举 三种类型all、input、output
响应参数
参数名称 类型 备注
x List 参考
y List 如果请求参数的type的值是all,取inputTokenoutputToken相加的值。如果是inputinputToken。如果是outputinputToken。如果维度是,直接返回type对应的数值,否则根据维度分组并求和,显示规则参考

数据图表(对话点踩数)

  • 请求方式POST
  • URL/project/stats/chart/dislike
  • Content-Typeapplication/json

查询并计算project_data_stats_daily相关数据。

请求参数
参数名称 类型 说明
projectId String 项目ID
维度 枚举 参考
begin String 参考
end String 参考
响应参数
参数名称 类型 备注
x List 参考
y List 如果维度是,直接使用userDislikeCount,否则根据维度分组并求和

数据图表(对话反馈数)

  • 请求方式POST
  • URL/project/stats/chart/feedback
  • Content-Typeapplication/json

查询并计算project_data_stats_daily相关数据。

请求参数
参数名称 类型 说明
projectId String 项目ID
维度 枚举 参考
begin String 参考
end String 参考
响应参数
参数名称 类型 备注
x List 参考
y List 如果维度是,直接使用userFeedbackCount,否则根据维度分组并求和

数据图表MCP使用情况

  • 请求方式POST
  • URL/project/stats/chart/mcp/usage
  • Content-Typeapplication/json

查询并计算project_data_stats_daily相关数据。

请求参数
参数名称 类型 说明
projectId String 项目ID
维度 枚举 参考
begin String 参考
end String 参考
type 枚举值 两种类型num=个数,count=次数
响应参数
参数名称 类型 备注
x List 参考
y List 如果请求参数中的typenum,则需要对mcpUseInfomcpServerId进行分组并去重toolNames,计算toolNames的个数,如果typecount,则使用callMcpToolCount。如果维度是,直接返回type对应的数值,否则根据维度分组并求和

数据图表Skill使用情况

  • 请求方式POST
  • URL/project/stats/chart/skill/usage
  • Content-Typeapplication/json

查询并计算project_data_stats_daily相关数据。

请求参数
参数名称 类型 说明
projectId String 项目ID
维度 枚举 参考
begin String 参考
end String 参考
type 枚举值 两种类型num=个数,count=次数
响应参数
参数名称 类型 备注
x List 参考
y List 如果请求参数中的typenum,则需要对skillUseInfoskillNames合并然后去重toolNames计算个数,如果typecount,则使用callSkillCount。如果维度是,直接返回type对应的数值,否则根据维度分组并求和