From e7437a39b265949d4a6a612c442081efcdca943f Mon Sep 17 00:00:00 2001 From: ziin Date: Wed, 4 Feb 2026 20:01:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(speech):=20=E4=B8=BA=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E8=BD=AC=E6=96=87=E5=AD=97=E6=8E=A5=E5=8F=A3=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?VIP=E6=AF=8F=E6=97=A5=E8=B0=83=E7=94=A8=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../keyborad/controller/SpeechController.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/main/java/com/yolo/keyborad/controller/SpeechController.java b/src/main/java/com/yolo/keyborad/controller/SpeechController.java index 27afa21..23e0242 100644 --- a/src/main/java/com/yolo/keyborad/controller/SpeechController.java +++ b/src/main/java/com/yolo/keyborad/controller/SpeechController.java @@ -1,16 +1,31 @@ package com.yolo.keyborad.controller; +import cn.dev33.satoken.stp.StpUtil; import com.yolo.keyborad.common.BaseResponse; +import com.yolo.keyborad.common.ErrorCode; import com.yolo.keyborad.common.ResultUtils; +import com.yolo.keyborad.config.AppConfig; +import com.yolo.keyborad.config.NacosAppConfigCenter; +import com.yolo.keyborad.exception.BusinessException; +import com.yolo.keyborad.model.entity.KeyboardUser; import com.yolo.keyborad.model.vo.SpeechToTextVO; import com.yolo.keyborad.service.DeepgramService; +import com.yolo.keyborad.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + /** * 语音服务控制器 * @@ -22,13 +37,71 @@ import org.springframework.web.multipart.MultipartFile; @Tag(name = "语音服务", description = "语音相关功能接口") public class SpeechController { + private static final String SPEECH_DAILY_LIMIT_PREFIX = "speech:daily:limit:"; + @Resource private DeepgramService deepgramService; + @Resource + private UserService userService; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + private final NacosAppConfigCenter.DynamicAppConfig cfgHolder; + + public SpeechController(NacosAppConfigCenter.DynamicAppConfig cfgHolder) { + this.cfgHolder = cfgHolder; + } + @PostMapping("/transcribe") @Operation(summary = "语音转文字", description = "上传音频文件并转换为文本") public BaseResponse transcribe(@RequestPart("file") MultipartFile file) { + // 获取当前登录用户ID + Long userId = StpUtil.getLoginIdAsLong(); + + // 查询用户信息 + KeyboardUser user = userService.getById(userId); + // 获取VIP等级(null视为0) + int vipLevel = user != null && user.getVipLevel() != null ? user.getVipLevel() : 0; + + // vipLevel >= 2 不做限制,直接放行 + if (vipLevel < 2) { + AppConfig appConfig = cfgHolder.getRef().get(); + String redisKey = SPEECH_DAILY_LIMIT_PREFIX + userId; + Integer dailyLimit = appConfig.getUserRegisterProperties().getVipFreeTrialTalk(); + + // 获取当前使用次数 + String countStr = stringRedisTemplate.opsForValue().get(redisKey); + int currentCount = countStr != null ? Integer.parseInt(countStr) : 0; + + // 检查是否超出限制 + if (currentCount >= dailyLimit) { + log.warn("用户 {} VIP等级 {} 已达到每日语音转文字次数限制 {}", userId, vipLevel, dailyLimit); + throw new BusinessException(ErrorCode.VIP_TRIAL_LIMIT_REACHED); + } + } + + // 调用语音转文字服务 SpeechToTextVO result = deepgramService.transcribe(file); + + // 成功后扣减次数(仅 vipLevel < 2 的用户) + if (vipLevel < 2) { + String redisKey = SPEECH_DAILY_LIMIT_PREFIX + userId; + Long newCount = stringRedisTemplate.opsForValue().increment(redisKey); + + // 设置到午夜过期(只有第一次设置时需要设置过期时间) + if (newCount != null && newCount == 1) { + LocalDateTime now = LocalDateTime.now(ZoneId.of("America/New_York")); + LocalDateTime midnight = LocalDateTime.of(LocalDate.now(ZoneId.of("America/New_York")).plusDays(1), LocalTime.MIDNIGHT); + long secondsUntilMidnight = ChronoUnit.SECONDS.between(now, midnight); + stringRedisTemplate.expire(redisKey, secondsUntilMidnight, TimeUnit.SECONDS); + } + + log.info("用户 {} VIP等级 {} 今日已使用语音转文字 {}/{} 次", userId, vipLevel, newCount, + cfgHolder.getRef().get().getUserRegisterProperties().getVipFreeTrialTalk()); + } + return ResultUtils.success(result); } }