二
源码取证
问题行标红,行前插入了 [BUG-n] 注释;每段下方可打开原始/已注释副本
严重BUG-1
回调可被伪造扣款:secret 硬编码 + “无冻结也结算”
资金安全
callback 允许“无有效冻结 + succeeded”按实际费用直接扣款;验签 secret 是源码硬编码默认值且已明文进入交付包(start.sh、SQL dump)。拿到交付包的人可伪造 succeeded 回调、填超大 usage,把任意租户余额扣成负数,无 HMAC、无时间戳防重放。
修复建议:改为 HMAC 签名(secret+requestId+usage)并带时间戳防重放;密钥仅从环境变量注入、源码不落默认值;无冻结的 succeeded 必须回查任务/幂等键真实性后再结算。
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:200-207
原始副本 ·
已注释副本
200 @Override
201 @Transactional(rollbackFor = Exception.class)
202 public R<Void> callback(CallbackReq req) {
▸// [BUG-1] 验签只与硬编码默认值比对,无防重放;secret 还在源码与 start.sh 中明文存在
203 // 1. 验签
204 if (req.getSecret() == null || !callbackSecret.equals(req.getSecret())) {
205 log.warn("[Billing] callback 签名校验失败, requestId={}", req.getRequestId());
206 return R.fail("签名校验失败");
207 }
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:244-254
原始副本 ·
已注释副本
▸// [BUG-1] 无有效冻结 + succeeded 直接进入补结算:伪造 usage 即可让目标租户扣款
244 if (freeze == null || (freeze.getStatus() != null && freeze.getStatus() != FREEZE_STATUS_ACTIVE)) {
245 if (!isSuccess) {
246 // 没冻结(或已处理过)+ failed:没有可退的,幂等返回
247 log.info("[Billing] 没有可退的冻结, requestId={}", req.getRequestId());
248 return R.ok();
249 }
250 // 无有效冻结但报成功(边界:冻结已被 TTL 定时任务释放):
251 // 钱还是要收,按补结算直接扣实际费用
252 log.warn("[Billing] 无有效冻结但收到 succeeded,进入补结算, requestId={}, freezeStatus={}",
253 req.getRequestId(), freeze == null ? "null" : freeze.getStatus());
254 return settleWithoutFreeze(req, null);
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:342-348
原始副本 ·
已注释副本
342 long actualCost = calcActualCost(req, price);
▸// [BUG-1] 补结算直接 decrBalance,扣的是攻击者伪造的 actualCost
343 int rows = userBalanceMapper.decrBalance(req.getTenantId(), actualCost);
344 if (rows == 0) {
345 log.error("[Billing] 补结算扣款失败,需追缴, tenantId={}, actualCost={}, requestId={}",
346 req.getTenantId(), actualCost, req.getRequestId());
347 // TODO: 接告警/写待追缴表
348 }
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:39-40
原始副本 ·
已注释副本
▸// [BUG-1] 回调验签密钥的硬编码默认值,等于随交付包公开
39 @Value("${ruoyi.callback.secret:AIGateWay76428398263229873982}")
40 private String callbackSecret;
▸# [BUG-1] RUOYI_API_KEY 明文,且与 callback secret 是同一个值,随交付包分发
13export RUOYI_API_KEY="AIGateWay76428398263229873982"
严重BUG-2
preCheck 内部接口无鉴权:@InnerOnly 切点写错包名
访问控制
@InnerOnly 切面引用的是不存在的包 com.ruoyi.common.annotation.InnerOnly(实际注解在 org.dromara.common.core.validate.inner),切面永不生效;preCheck 自身也不校验任何 secret。外部可达时,任意人可对任意租户反复预扣冻结(锁死余额的 DoS)。
修复建议:修正切点注解包名为 org.dromara.common.core.validate.inner.InnerOnly;preCheck 增加与 callback 相同的密钥校验;并建议仅监听内网网卡、反向代理层做来源限制。
RuoYi-Vue-Plus/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/inner/InnerOnlyAspect.java:40-46
原始副本 ·
已注释副本
▸// [BUG-2] 切点引用不存在的包 com.ruoyi.common.annotation.InnerOnly,AOP 永不生效
40 @Before("@annotation(com.ruoyi.common.annotation.InnerOnly)")
41 public void checkInner(JoinPoint point) {
42 HttpServletRequest request = ServletUtils.getRequest();
43 String ip = ServletUtils.getClientIP(request);
44
45 boolean isInner = INNER_NETWORKS.stream().anyMatch(ip::startsWith);
46 if (!isInner) {
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/controller/AiOneApiController.java:21-31
原始副本 ·
已注释副本
21 @SaIgnore
22 @InnerOnly
▸// [BUG-2] preCheck 仅靠已失效的 @InnerOnly,无任何鉴权
23 @PostMapping("/billing/preCheck")
24 public R<PreCheckResp> preCheck(@RequestBody @Validated PreCheckReq req) {
25 return billingService.preCheck(req);
26 }
27
28 @SaIgnore
29 @InnerOnly
▸// [BUG-2] callback 仅靠已失效的 @InnerOnly
30 @PostMapping("/billing/callback")
31 public R<Void> callback(@RequestBody @Validated CallbackReq req) {
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:55-72
原始副本 ·
已注释副本
▸// [BUG-2] preCheck 入口完全不校验 secret/签名,任何人可调用并冻结任意租户资金
55 public R<PreCheckResp> preCheck(PreCheckReq req) {
56 PreCheckResp resp = new PreCheckResp();
57 resp.setTenantStatus(false);
58 resp.setWalletStatus(false);
59 resp.setAllow(false);
60
61 // 1. 参数校验
62 if (req.getTenantId() == null || req.getTenantId().trim().isEmpty()) {
63 resp.setMessage("tenantId 不能为空");
64 return R.ok(resp);
65 }
66 if (req.getModel() == null || req.getModel().trim().isEmpty()) {
67 resp.setMessage("model 不能为空");
68 return R.ok(resp);
69 }
70 if (req.getRequestId() == null || req.getRequestId().trim().isEmpty()) {
71 resp.setMessage("requestId 不能为空");
72 return R.ok(resp);
严重BUG-3
余额预扣无防透支条件(并发可超扣)
资金安全
decrBalance 注释声称“带防透支条件”,SQL 实际没有 balance >= amount;“查余额 → 扣减”两步非原子,两个并发请求可同时通过余额检查,余额被扣成负数。
修复建议:SQL 增加 AND balance >= #{amount};rows==0 视为余额不足并回滚冻结记录。
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/AiUserBalanceMapper.java:28-37
原始副本 ·
已注释副本
28 /**
29 * 扣减余额(带防透支条件)
30 * 返回 0 表示余额不足或租户不存在
31 */
▸// [BUG-3] 注释写“带防透支条件”,实际 SQL 没有 balance >= amount 条件
32 @Update("UPDATE ai_user_balance " +
33 "SET balance = balance - #{amount}, " +
34 " total_consume = total_consume + #{amount}, " +
35 " update_time = now() " +
36 "WHERE tenant_id = #{tenantId} ")
37 int decrBalance(@Param("tenantId") String tenantId, @Param("amount") Long amount);
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:151-162
原始副本 ·
已注释副本
151 // 6. 余额检查
152 if (currentBalance < estimatedCost) {
153 resp.setMessage(String.format("余额不足: 预计需要 %d, 剩余 %d", estimatedCost, currentBalance));
154 return R.ok(resp);
155 }
156
157 // 7. 数据库扣减(原子操作,行锁防并发透支)
▸// [BUG-3] 先 SELECT 查余额、再 UPDATE 扣减,非原子,并发下会超扣
158 int rows = userBalanceMapper.decrBalance(req.getTenantId(), estimatedCost);
159 if (rows == 0) {
160 // 并发场景:余额刚好被其他请求扣完
161 resp.setMessage("用户账号预扣异常");
162 return R.ok(resp);
高BUG-4
视频预扣固定价与实际 token 结算价不一致
计费一致性
视频预扣用全局固定 freeze_video(2 元),结算却按 ai_model_price 的 token 实算;doubao-seedance-2.0 实际约 2.72 元,低于预扣的系统性低估差额在“补扣失败”路径只有日志、无追缴,是资损点。
修复建议:预扣价按模型取 ai_model_price 的预估上限(或固定价大于等于最贵模型实际价);补扣失败写入“待追缴”表并告警。
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:113-128
原始副本 ·
已注释副本
▸// [BUG-4] 预扣用全局固定 freeze_video,与模型价格表无关
113 if ("video".equals(req.getTaskType()) || "image".equals(req.getTaskType())) {
114 // 视频/图片:读 sys_config 固定价
115 String configKey = "freeze_" + req.getTaskType();
116 String configValue = sysConfigService.getConfigByKey(configKey);
117 if (configValue == null || configValue.trim().isEmpty()) {
118 resp.setMessage("系统配置缺失: " + configKey);
119 return R.ok(resp);
120 }
121 try {
122 estimatedCost = Long.parseLong(configValue.trim());
123 } catch (NumberFormatException e) {
124 resp.setMessage("系统配置格式错误: " + configValue);
125 return R.ok(resp);
126 }
127 resp.setInputPrice(0L);
128 resp.setOutputPrice(estimatedCost);
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:495-514
原始副本 ·
已注释副本
495 * 计算实际费用
496 * 视频/图片:固定价(actualCost = estimatedCost)
497 * 文本:按实际 token 计算
498 */
▸// [BUG-4] 结算却按模型价格表 token 实算(seedance-2.0 实际约 2.72 元 > 预扣 2 元)
499 private long calcActualCost(CallbackReq req, AiModelPrice price) {
500 // 文本模型:按实际 token 计算
501 if (price == null) {
502 log.warn("[Billing] 模型价格未配置, model={}, 按0计费", req.getModel());
503 return 0L;
504 }
505
506 int promptTokens = (req.getUsage() != null && req.getUsage().getPromptTokens() != null)
507 ? req.getUsage().getPromptTokens() : 0;
508 int completionTokens = (req.getUsage() != null && req.getUsage().getCompletionTokens() != null)
509 ? req.getUsage().getCompletionTokens() : 0;
510
511 long inputCost = calcCost(promptTokens, price.getInputPrice());
512 long outputCost = calcCost(completionTokens, price.getOutputPrice());
513
514 return inputCost + outputCost;
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:342-347
原始副本 ·
已注释副本
342 long actualCost = calcActualCost(req, price);
▸// [BUG-4] 低估差额补扣失败仅写日志,TODO 无追缴机制(系统性资损)
343 int rows = userBalanceMapper.decrBalance(req.getTenantId(), actualCost);
344 if (rows == 0) {
345 log.error("[Billing] 补结算扣款失败,需追缴, tenantId={}, actualCost={}, requestId={}",
346 req.getTenantId(), actualCost, req.getRequestId());
347 // TODO: 接告警/写待追缴表
高BUG-5
图片生成链路完全不计费
计费覆盖
/v1/images/generations 走 RelayImageHelper,没有 preCheck/callback;网关 GetTaskType 只返回 video/chat,永远不会发 taskType=image,RuoYi 的 freeze_image 分支是死代码。只要配上图片渠道,图片生成即免费。
修复建议:给图片链路接 preCheck/callback(taskType=image),或产品上明确下线图片能力并删除死代码。
one-api/relay/controller/image.go:106-118
原始副本 ·
已注释副本
▸// [BUG-5] RelayImageHelper 无 preCheck/callback,图片请求绕过计费
106func RelayImageHelper(c *gin.Context, relayMode int) *relaymodel.ErrorWithStatusCode {
107 ctx := c.Request.Context()
108 meta := meta.GetByContext(c)
109 imageRequest, err := getImageRequest(c, meta.Mode)
110 if err != nil {
111 logger.Errorf(ctx, "getImageRequest failed: %s", err.Error())
112 return openai.ErrorWrapper(err, "invalid_image_request", http.StatusBadRequest)
113 }
114
115 // map model name
116 var isModelMapped bool
117 meta.OriginModelName = imageRequest.Model
118 imageRequest.Model, isModelMapped = getMappedModelName(imageRequest.Model, meta.ModelMapping)
one-api/relay/billing/ruoyi.go:70-82
原始副本 ·
已注释副本
70// IsVideoChannel 根据渠道类型判断是否是视频生成
71func IsVideoChannel(channelType int) bool {
72 return channelType == channeltype.Seedance
73}
74
75// GetTaskType 根据渠道类型返回任务类型
▸// [BUG-5] GetTaskType 只返回 video/chat,网关永远不会发 taskType=image
76func GetTaskType(channelType int) string {
77 if IsVideoChannel(channelType) {
78 return "video"
79 }
80 return "chat"
81}
82
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:113-116
原始副本 ·
已注释副本
▸// [BUG-5] freeze_image 分支是死代码:网关从不带 image 类型进来
113 if ("video".equals(req.getTaskType()) || "image".equals(req.getTaskType())) {
114 // 视频/图片:读 sys_config 固定价
115 String configKey = "freeze_" + req.getTaskType();
116 String configValue = sysConfigService.getConfigByKey(configKey);
高BUG-6
one-api 充值体系与 RuoYi 计费脱节
产品一致性
one-api 前台 TopUp/兑换码只增加 one-api 用户 quota(配额),实际扣费全在 RuoYi;前台充的值永远用不上、展示的额度也不减少,存在“两个充值入口、只有一个生效”的歧义。
修复建议:下线 one-api 前台充值入口,或让充值同步写入 RuoYi 的 ai_user_balance。
one-api/controller/user.go:754-780
原始副本 ·
已注释副本
▸// [BUG-6] TopUp 只给 one-api 用户加 quota,与 RuoYi 余额体系完全脱节
754func TopUp(c *gin.Context) {
755 ctx := c.Request.Context()
756 req := topUpRequest{}
757 err := c.ShouldBindJSON(&req)
758 if err != nil {
759 c.JSON(http.StatusOK, gin.H{
760 "success": false,
761 "message": err.Error(),
762 })
763 return
764 }
765 id := c.GetInt("id")
766 quota, err := model.Redeem(ctx, req.Key, id)
767 if err != nil {
768 c.JSON(http.StatusOK, gin.H{
769 "success": false,
770 "message": err.Error(),
771 })
772 return
773 }
774 c.JSON(http.StatusOK, gin.H{
775 "success": true,
776 "message": "",
777 "data": quota,
778 })
779 return
780}
中BUG-7
video_task_max 只存不校验
功能未实现
ai_user_balance.video_task_max 有字段、有更新接口,但网关从未读取执行;唯一的“同步”逻辑在已禁用的 AiRedisSyncService 死代码里。租户视频并发上限实际不生效。
修复建议:在视频提交/调度入口按租户统计进行中任务数,超过 video_task_max 直接拒绝;同步逻辑接通或删除。
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/AiUserBalanceMapper.java:49-54
原始副本 ·
已注释副本
49 int incrBalance(@Param("tenantId") String tenantId, @Param("amount") Long amount);
50
51
52 @Update("update ai_user_balance set warn_threshold = #{warnThreshold}, video_task_max = #{videoTaskMax}, update_time = now() " +
53 "where tenant_id = #{tenantId}")
▸// [BUG-7] 仅提供更新字段的接口,任何地方都没有读取/校验该上限
54 int updateThreshold(@Param("tenantId") String tenantId, @Param("warnThreshold") Long warnThreshold, @Param("videoTaskMax") Integer videoTaskMax);
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/AiRedisSyncService.java:36-44
原始副本 ·
已注释副本
36 private static final String KEY_USER_BALANCE = "user_balance_%s";
37 private static final String KEY_WARN_THRESHOLD = "warn_threshold_%s";
▸// [BUG-7] video_task_max 只出现在已禁用的 Redis 同步死代码中
38 private static final String KEY_VIDEO_TASK_MAX = "video_task_max_%s";
39
40 @Override
41 public void run(ApplicationArguments args) {
42 log.info("[AiRedisSync] 启动时全量同步...[暂时不用]");
43 //syncAll();
44 }
中BUG-8
租户统计接口越权:可查任意租户余额
越权(IDOR)
AiTenantBalanceController 三个接口无权限注解,非管理员显式传 tenantId 即可查询任意租户的余额概览、每日调用与模型统计(仅“参数为空”时才兜底为本人租户)。
修复建议:非管理员强制把 tenantId 覆盖为登录人租户,或增加 system:tenant:balance 权限校验。
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/AiTenantBalanceController.java:29-48
原始副本 ·
已注释副本
▸// [BUG-8] 非管理员显式传 tenantId 即可查任意租户余额,无权限校验
29 public R<AiBalanceOverviewVo> getBalanceOverview(@RequestParam(value = "tenantId", required = false) String tenantId) {
30 if (tenantId == null) {
31 LoginUser loginUser = LoginHelper.getLoginUser();
32 if (!loginUser.getAdmin()) {
33 tenantId = loginUser.getTenantId();
34 }
35 }
36 return R.ok(balanceService.getOverview(tenantId));
37 }
38
39 @GetMapping("/balance/daily/stat")
40 public R<List<AiDailyStatVo>> getDailyStats(@RequestParam(value = "tenantId", required = false) String tenantId,
41 @RequestParam(value = "days", required = false, defaultValue = "30") Integer days) {
42 if (tenantId == null) {
43 LoginUser loginUser = LoginHelper.getLoginUser();
44 if (!loginUser.getAdmin()) {
45 tenantId = loginUser.getTenantId();
46 }
47 }
48 return R.ok(expenseRecordService.getDailyStats(tenantId, days));
中BUG-9
视频模型缺价格行时回调永远失败
健壮性
callback 查不到 ai_model_price 直接抛异常,outbox 按退避重试 7 天后转人工对账;令牌可用的 doubao-seedance-1-5-pro-251215 在价格表里就没有行,会一直卡在这条路径。
修复建议:价格缺失改为明确错误码+告警(而非静默重试 7 天);交付时核对“令牌模型清单 = 价格表清单”。
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:257-267
原始副本 ·
已注释副本
257 // 4. 查模型价格(必须判空:查不到价格直接抛异常让事务回滚,
258 // oneAPI outbox 会按退避重试;静默 NPE 会写出脏数据/卡死回调)
259 AiModelPrice price = modelPriceMapper.selectOne(
260 Wrappers.<AiModelPrice>lambdaQuery()
261 .eq(AiModelPrice::getModelCode, req.getModel())
262 .eq(AiModelPrice::getStatus, 0)
263 .last("limit 1")
264 );
265 if (price == null) {
266 log.error("[Billing] 模型价格配置缺失, model={}, requestId={}", req.getModel(), req.getRequestId());
▸// [BUG-9] 查不到模型价格直接抛异常 → outbox 退避重试 7 天后人工对账
267 throw new RuntimeException("模型价格配置缺失: " + req.getModel());
低BUG-10
视频任务查询接口与交付文档不一致
文档一致性
实现是 GET /v1/videos/task?id=...(query 参数),交付 PDF 写的是 /v1/videos/task/{id}(路径参数),客户对接会 404。
修复建议:统一文档与实现,或把路由改为路径参数。
20 seedanceRouter := router.Group("/v1/videos")
21 seedanceRouter.Use(middleware.TokenAuth())
22 {
▸// [BUG-10] 实际路由 /v1/videos/task?id=...,与 PDF 的 /v1/videos/task/{id} 不一致
23 seedanceRouter.GET("/task", controller.QuerySeedanceTask)
24 }
one-api/controller/relay.go:218-221
原始副本 ·
已注释副本
218func QuerySeedanceTask(c *gin.Context) {
▸// [BUG-10] 用 query 参数取 id,与文档的路径参数不一致
219 taskID := c.Query("id")
220 if taskID == "" {
221 c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
低BUG-11
冻结 ID 多实例可能碰撞
健壮性
冻结 ID 用“当前毫秒时间戳 + rand(10000)”生成,多实例并发或同毫秒内可能主键冲突,导致请求失败。
修复建议:改用 UUID 或雪花算法。
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:165-167
原始副本 ·
已注释副本
165 // 8. 写入冻结记录
▸// [BUG-11] 时间戳+rand(10000) 生成的冻结 ID,多实例并发会撞主键
166 String freezeId = "frz_" + System.currentTimeMillis() + "_" + new Random().nextInt(10000);
167 AiFreezeRecord record = new AiFreezeRecord();
低BUG-12
CallbackReq 无字段校验
健壮性
回调 DTO 没有任何校验注解,空 requestId、空 tenantId、负数 token 等非法输入可直接进入结算状态机,产生异常状态。
修复建议:DTO 增加 @NotBlank/@Min 等校验,非法回调直接拒绝。
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/domain/oneAPI/CallbackReq.java:5-16
原始副本 ·
已注释副本
5@Data
▸// [BUG-12] DTO 无任何校验注解,非法输入可直接进入结算逻辑
6public class CallbackReq {
7 private String requestId;
8 private String tenantId;
9 private String model;
10 private String status; // succeeded / failed / expired / cancelled
11 private String taskType; // chat / video / image
12 private Usage usage;
13 private String videoUrl;
14 private String errorMsg;
15 private String secret;
16}
中BUG-13
冻结 TTL 释放认领后早退不回滚
资金状态一致性
releaseOneExpiredFreeze 先用条件 UPDATE 把 status 0→2 认领,随后发现消费记录已存在时直接 return false(非异常),事务提交后状态停在“已退款”但钱没有退,状态与资金不一致。
修复建议:早退路径改为抛异常触发回滚,或在 return 前把状态恢复为 0。
RuoYi-Vue-Plus/ruoyi-admin/src/main/java/org/dromara/web/service/impl/AiBillingServiceImpl.java:437-445
原始副本 ·
已注释副本
437 // 2. 幂等兜底:消费记录已存在 = callback 已处理,只清理状态不重复退款
438 Long expenseCount = expenseRecordMapper.selectCount(
439 Wrappers.<AiExpenseRecord>lambdaQuery()
440 .eq(AiExpenseRecord::getRequestId, freeze.getRequestId())
441 );
▸// [BUG-13] 已认领 0→2 后 return false 不抛异常:状态停在“已退款”但钱没退
442 if (expenseCount != null && expenseCount > 0) {
443 log.warn("[Billing] 冻结释放时发现消费记录已存在,跳过退款, freezeId={}, requestId={}", freezeId, freeze.getRequestId());
444 return false;
445 }
低BUG-14
OneHttpConfig 拼接 URL 不做编码
健壮性
buildUrl 把 query 参数原样拼进 URL(注释还特意说明不编码),关键词含中文/空格时会生成非法 URL,请求 500。
修复建议:对 query 参数做 URLEncode,或改用 URI 组件构建。
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/config/OneHttpConfig.java:78-94
原始副本 ·
已注释副本
▸// [BUG-14] query 参数原样拼接不做编码,中文/空格关键词会 500
78 public String buildUrl(String path, Map<String, String> queryParams) {
79 String baseUrl = getBaseUrl();
80 String apiPath = path.startsWith("/") ? path.substring(1) : path;
81 StringBuilder url = new StringBuilder(baseUrl + apiPath);
82 if (!CollectionUtils.isEmpty(queryParams)) {
83 url.append("?");
84 int idx = 0;
85 for (Map.Entry<String, String> entry : queryParams.entrySet()) {
86 if (idx > 0) {
87 url.append("&");
88 }
89 url.append(entry.getKey()).append("=").append(entry.getValue());
90 idx++;
91 }
92 }
93 return url.toString();
94 }
低BUG-15
AiRedisSyncService 整套死代码
维护性
启动时只打日志“[暂时不用]”,全量/每日同步全部注释禁用,与当前 HTTP 计费双轨并存,容易误导后续维护者以为余额在 Redis 中同步。
修复建议:删除该类,或接通并保持与 DB 计费一致。
RuoYi-Vue-Plus/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/AiRedisSyncService.java:41-50
原始副本 ·
已注释副本
▸// [BUG-15] 启动只打日志“[暂时不用]”,同步逻辑全部注释禁用
41 public void run(ApplicationArguments args) {
42 log.info("[AiRedisSync] 启动时全量同步...[暂时不用]");
43 //syncAll();
44 }
45
46 //@Scheduled(cron = "0 0 2 * * ?")
47 public void dailyRefresh() {
48 log.info("[AiRedisSync] 开始每日兜底刷新...");
49 //syncAll();
50 }