feat: 优化

This commit is contained in:
2026-03-08 13:55:10 +08:00
parent 2aa459889a
commit 69835ae990
9 changed files with 484 additions and 310 deletions

View File

@@ -274,9 +274,11 @@ public class TikUserFileServiceImpl implements TikUserFileService {
vo.setThumbnailUrl(thumbnailUrl);
// 图片封面URL优先缩略图否则原图
vo.setImgUrl(isImage
? (thumbnailUrl != null ? thumbnailUrl : getCachedPresignUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS))
: null);
if (isImage) {
vo.setImgUrl(thumbnailUrl != null
? thumbnailUrl
: getCachedPresignUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS));
}
return vo;
});
@@ -329,27 +331,30 @@ public class TikUserFileServiceImpl implements TikUserFileService {
public String getVideoPlayUrl(Long fileId) {
TikUserFileDO file = getUserFile(fileId);
if (StrUtil.isBlank(file.getFileUrl())) {
throw exception(FILE_NOT_EXISTS, "文件URL为空");
if (StrUtil.isBlank(file.getFilePath())) {
throw exception(FILE_NOT_EXISTS, "文件路径为空");
}
if (!StrUtil.containsIgnoreCase(file.getFileType(), "video")) {
throw exception(FILE_CATEGORY_INVALID, "文件不是视频类型");
}
// 视频播放URL不缓存每次都生成新的签名URL
return fileApi.presignGetUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS);
// 视频播放URL不缓存每次都生成新的签名URL(使用 filePath 而非 fileUrl避免域名匹配问题
return fileApi.presignGetUrl(file.getFilePath(), PRESIGN_URL_EXPIRATION_SECONDS);
}
@Override
public String getAudioPlayUrl(Long fileId) {
TikUserFileDO file = getUserFile(fileId);
if (StrUtil.isBlank(file.getFilePath())) {
throw exception(FILE_NOT_EXISTS, "文件路径为空");
}
if (!StrUtil.containsIgnoreCase(file.getFileType(), "audio")) {
throw exception(FILE_CATEGORY_INVALID, "文件不是音频类型");
}
// 音频播放URL不缓存每次都生成新的签名URL
return fileApi.presignGetUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS);
// 音频播放URL不缓存每次都生成新的签名URL(使用 filePath 而非 fileUrl避免域名匹配问题
return fileApi.presignGetUrl(file.getFilePath(), PRESIGN_URL_EXPIRATION_SECONDS);
}
@Override

View File

@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.tik.muye.aiusagestats.vo.*;
import cn.iocoder.yudao.module.tik.muye.aiserviceconfig.dal.AiServiceConfigDO;
import cn.iocoder.yudao.module.tik.muye.aiserviceconfig.mapper.AiServiceConfigMapper;
import cn.iocoder.yudao.module.tik.muye.pointrecord.dal.PointRecordDO;
import cn.iocoder.yudao.module.tik.muye.pointrecord.mapper.PointRecordMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -33,19 +32,18 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
/**
* 业务类型名称映射
*/
private static final Map<String, String> BIZ_TYPE_NAMES = new HashMap<>();
static {
BIZ_TYPE_NAMES.put("dify_chat", "AI对话");
BIZ_TYPE_NAMES.put("forecast_rewrite", "文案改写");
BIZ_TYPE_NAMES.put("voice_tts", "语音合成");
BIZ_TYPE_NAMES.put("digital_human", "数字人");
BIZ_TYPE_NAMES.put("tikhub_fetch", "TikHub获取");
BIZ_TYPE_NAMES.put("signin", "签到");
BIZ_TYPE_NAMES.put("recharge", "充值");
BIZ_TYPE_NAMES.put("exchange", "兑换");
BIZ_TYPE_NAMES.put("admin", "后台调整");
BIZ_TYPE_NAMES.put("gift", "礼包赠送");
}
private static final Map<String, String> BIZ_TYPE_NAMES = Map.of(
"dify_chat", "AI对话",
"forecast_rewrite", "文案改写",
"voice_tts", "语音合成",
"digital_human", "数字人",
"tikhub_fetch", "TikHub获取",
"signin", "签到",
"recharge", "充值",
"exchange", "兑换",
"admin", "后台调整",
"gift", "礼包赠送"
);
@Override
public AiUsageOverviewRespVO getOverview(LocalDateTime startTime, LocalDateTime endTime, String bizType) {
@@ -59,15 +57,15 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
List<AiUsageOverviewRespVO.BizTypeStats> bizTypeStats = new ArrayList<>();
for (Map<String, Object> stats : bizTypeStatsList) {
String bt = (String) stats.get("biz_type");
String bt = (String) stats.get("bizType");
// 如果指定了业务类型,只统计该类型
if (bizType != null && !bizType.isEmpty() && !bizType.equals(bt)) {
continue;
}
long callCount = getLongValue(stats, "call_count");
long consumePoints = getLongValue(stats, "consume_points");
long tokens = getLongValue(stats, "total_tokens");
long callCount = getLongValue(stats, "callCount");
long consumePoints = getLongValue(stats, "consumePoints");
long tokens = getLongValue(stats, "totalTokens");
totalCallCount += callCount;
totalConsumePoints += consumePoints;
@@ -83,15 +81,7 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
}
// 查询活跃用户数
Long activeUserCount = pointRecordMapper.selectCount(
new LambdaQueryWrapperX<PointRecordDO>()
.betweenIfPresent(PointRecordDO::getCreateTime, startTime, endTime)
.eqIfPresent(PointRecordDO::getBizType, bizType)
.eq(PointRecordDO::getStatus, "confirmed")
.eq(PointRecordDO::getType, "decrease")
.select(PointRecordDO::getUserId)
.groupBy(PointRecordDO::getUserId)
);
Long activeUserCount = pointRecordMapper.selectActiveUserCount(startTime, endTime, bizType);
return AiUsageOverviewRespVO.builder()
.totalCallCount(totalCallCount)
@@ -136,12 +126,10 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
startTime, endTime, bizType);
// 获取服务配置信息
Map<String, AiServiceConfigDO> configMap = new HashMap<>();
List<AiServiceConfigDO> configs = aiServiceConfigMapper.selectList(
new LambdaQueryWrapperX<AiServiceConfigDO>().eq(AiServiceConfigDO::getStatus, 1));
for (AiServiceConfigDO config : configs) {
configMap.put(config.getServiceCode(), config);
}
Map<String, AiServiceConfigDO> configMap = aiServiceConfigMapper.selectList(
new LambdaQueryWrapperX<AiServiceConfigDO>().eq(AiServiceConfigDO::getStatus, 1))
.stream()
.collect(Collectors.toMap(AiServiceConfigDO::getServiceCode, c -> c, (a, b) -> a));
// 转换为 VO
return appStatsList.stream()
@@ -169,17 +157,17 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
* 转换为用户统计 VO
*/
private AiUsageUserStatsRespVO convertToUserStatsVO(Map<String, Object> stats) {
Long callCount = getLongValue(stats, "call_count");
Long consumePoints = getLongValue(stats, "consume_points");
Long callCount = getLongValue(stats, "callCount");
Long consumePoints = getLongValue(stats, "consumePoints");
return AiUsageUserStatsRespVO.builder()
.userId(getLongValue(stats, "user_id"))
.userId(getLongValue(stats, "userId"))
.mobile((String) stats.get("mobile"))
.callCount(callCount)
.consumePoints(consumePoints)
.inputTokens(getLongValue(stats, "input_tokens"))
.outputTokens(getLongValue(stats, "output_tokens"))
.totalTokens(getLongValue(stats, "total_tokens"))
.inputTokens(getLongValue(stats, "inputTokens"))
.outputTokens(getLongValue(stats, "outputTokens"))
.totalTokens(getLongValue(stats, "totalTokens"))
.avgPointsPerCall(callCount > 0 ? (double) consumePoints / callCount : 0.0)
.build();
}
@@ -189,10 +177,10 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
*/
private AiUsageAppStatsRespVO convertToAppStatsVO(Map<String, Object> stats,
Map<String, AiServiceConfigDO> configMap) {
String serviceCode = (String) stats.get("service_code");
Long callCount = getLongValue(stats, "call_count");
Long consumePoints = getLongValue(stats, "consume_points");
Long totalTokens = getLongValue(stats, "total_tokens");
String serviceCode = (String) stats.get("serviceCode");
Long callCount = getLongValue(stats, "callCount");
Long consumePoints = getLongValue(stats, "consumePoints");
Long totalTokens = getLongValue(stats, "totalTokens");
AiServiceConfigDO config = configMap.get(serviceCode);
@@ -202,8 +190,8 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
.platform(config != null ? config.getPlatform() : "")
.callCount(callCount)
.consumePoints(consumePoints)
.inputTokens(getLongValue(stats, "input_tokens"))
.outputTokens(getLongValue(stats, "output_tokens"))
.inputTokens(getLongValue(stats, "inputTokens"))
.outputTokens(getLongValue(stats, "outputTokens"))
.totalTokens(totalTokens)
.avgPointsPerCall(callCount > 0 ? (double) consumePoints / callCount : 0.0)
.avgTokensPerCall(callCount > 0 ? (double) totalTokens / callCount : 0.0)
@@ -216,9 +204,9 @@ public class AiUsageStatsServiceImpl implements AiUsageStatsService {
private AiUsageTrendRespVO.TrendItem convertToTrendItem(Map<String, Object> stats) {
return AiUsageTrendRespVO.TrendItem.builder()
.time((String) stats.get("time"))
.callCount(getLongValue(stats, "call_count"))
.consumePoints(getLongValue(stats, "consume_points"))
.totalTokens(getLongValue(stats, "total_tokens"))
.callCount(getLongValue(stats, "callCount"))
.consumePoints(getLongValue(stats, "consumePoints"))
.totalTokens(getLongValue(stats, "totalTokens"))
.build();
}

View File

@@ -48,16 +48,16 @@ public interface PointRecordMapper extends BaseMapperX<PointRecordDO> {
* 按业务类型统计
*/
@Select("<script>" +
"SELECT biz_type, " +
" COUNT(*) as call_count, " +
" SUM(ABS(point_amount)) as consume_points, " +
" SUM(COALESCE(total_tokens, 0)) as total_tokens " +
"SELECT biz_type AS bizType, " +
" COUNT(*) AS callCount, " +
" SUM(ABS(point_amount)) AS consumePoints, " +
" SUM(COALESCE(total_tokens, 0)) AS totalTokens " +
"FROM muye_point_record " +
"WHERE status = 'confirmed' AND type = 'decrease' " +
"<if test='startTime != null'> AND create_time &gt;= #{startTime}</if>" +
"<if test='endTime != null'> AND create_time &lt;= #{endTime}</if>" +
"GROUP BY biz_type " +
"ORDER BY consume_points DESC" +
"ORDER BY consumePoints DESC" +
"</script>")
List<Map<String, Object>> selectBizTypeStats(@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
@@ -66,12 +66,12 @@ public interface PointRecordMapper extends BaseMapperX<PointRecordDO> {
* 按用户统计
*/
@Select("<script>" +
"SELECT user_id, mobile, " +
" COUNT(*) as call_count, " +
" SUM(ABS(point_amount)) as consume_points, " +
" SUM(COALESCE(input_tokens, 0)) as input_tokens, " +
" SUM(COALESCE(output_tokens, 0)) as output_tokens, " +
" SUM(COALESCE(total_tokens, 0)) as total_tokens " +
"SELECT user_id AS userId, mobile, " +
" COUNT(*) AS callCount, " +
" SUM(ABS(point_amount)) AS consumePoints, " +
" SUM(COALESCE(input_tokens, 0)) AS inputTokens, " +
" SUM(COALESCE(output_tokens, 0)) AS outputTokens, " +
" SUM(COALESCE(total_tokens, 0)) AS totalTokens " +
"FROM muye_point_record " +
"WHERE status = 'confirmed' AND type = 'decrease' " +
"<if test='startTime != null'> AND create_time &gt;= #{startTime}</if>" +
@@ -79,7 +79,7 @@ public interface PointRecordMapper extends BaseMapperX<PointRecordDO> {
"<if test='bizType != null and bizType != \"\"'> AND biz_type = #{bizType}</if>" +
"<if test='userId != null'> AND user_id = #{userId}</if>" +
"GROUP BY user_id, mobile " +
"ORDER BY consume_points DESC" +
"ORDER BY consumePoints DESC" +
"</script>")
List<Map<String, Object>> selectUserStats(@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
@@ -90,19 +90,19 @@ public interface PointRecordMapper extends BaseMapperX<PointRecordDO> {
* 按应用统计
*/
@Select("<script>" +
"SELECT service_code, " +
" COUNT(*) as call_count, " +
" SUM(ABS(point_amount)) as consume_points, " +
" SUM(COALESCE(input_tokens, 0)) as input_tokens, " +
" SUM(COALESCE(output_tokens, 0)) as output_tokens, " +
" SUM(COALESCE(total_tokens, 0)) as total_tokens " +
"SELECT service_code AS serviceCode, " +
" COUNT(*) AS callCount, " +
" SUM(ABS(point_amount)) AS consumePoints, " +
" SUM(COALESCE(input_tokens, 0)) AS inputTokens, " +
" SUM(COALESCE(output_tokens, 0)) AS outputTokens, " +
" SUM(COALESCE(total_tokens, 0)) AS totalTokens " +
"FROM muye_point_record " +
"WHERE status = 'confirmed' AND type = 'decrease' AND service_code IS NOT NULL AND service_code != '' " +
"<if test='startTime != null'> AND create_time &gt;= #{startTime}</if>" +
"<if test='endTime != null'> AND create_time &lt;= #{endTime}</if>" +
"<if test='bizType != null and bizType != \"\"'> AND biz_type = #{bizType}</if>" +
"GROUP BY service_code " +
"ORDER BY consume_points DESC" +
"ORDER BY consumePoints DESC" +
"</script>")
List<Map<String, Object>> selectAppStats(@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
@@ -112,10 +112,10 @@ public interface PointRecordMapper extends BaseMapperX<PointRecordDO> {
* 按时间趋势统计
*/
@Select("<script>" +
"SELECT DATE_FORMAT(create_time, #{dateFormat}) as time, " +
" COUNT(*) as call_count, " +
" SUM(ABS(point_amount)) as consume_points, " +
" SUM(COALESCE(total_tokens, 0)) as total_tokens " +
"SELECT DATE_FORMAT(create_time, #{dateFormat}) AS time, " +
" COUNT(*) AS callCount, " +
" SUM(ABS(point_amount)) AS consumePoints, " +
" SUM(COALESCE(total_tokens, 0)) AS totalTokens " +
"FROM muye_point_record " +
"WHERE status = 'confirmed' AND type = 'decrease' " +
"<if test='startTime != null'> AND create_time &gt;= #{startTime}</if>" +
@@ -129,4 +129,19 @@ public interface PointRecordMapper extends BaseMapperX<PointRecordDO> {
@Param("bizType") String bizType,
@Param("dateFormat") String dateFormat);
/**
* 统计活跃用户数(去重)
*/
@Select("<script>" +
"SELECT COUNT(DISTINCT user_id) " +
"FROM muye_point_record " +
"WHERE status = 'confirmed' AND type = 'decrease' " +
"<if test='startTime != null'> AND create_time &gt;= #{startTime}</if>" +
"<if test='endTime != null'> AND create_time &lt;= #{endTime}</if>" +
"<if test='bizType != null and bizType != \"\"'> AND biz_type = #{bizType}</if>" +
"</script>")
Long selectActiveUserCount(@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
@Param("bizType") String bizType);
}

View File

@@ -56,6 +56,22 @@ public class PointRecordRespVO {
@ExcelProperty("状态")
private String status;
@Schema(description = "服务标识", example = "writing_pro")
@ExcelProperty("服务标识")
private String serviceCode;
@Schema(description = "输入Token数")
@ExcelProperty("输入Token数")
private Integer inputTokens;
@Schema(description = "输出Token数")
@ExcelProperty("输出Token数")
private Integer outputTokens;
@Schema(description = "总Token数")
@ExcelProperty("总Token数")
private Integer totalTokens;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;