5 Commits

Author SHA1 Message Date
11073690e5 TASK:允许修改订阅结束时间到今天之后的日期 2026-05-22 10:35:57 +08:00
litianxiang
921d2d956e minio缓存 2026-05-20 15:09:26 +08:00
litianxiang
d700f94f9d flux test 2026-05-14 16:36:55 +08:00
litianxiang
b277479e73 豆包模型更换 2026-05-13 20:52:28 +08:00
litianxiang
83cbd57dea 登录鉴权按照Source判断id来自于何处 2026-05-13 09:40:30 +08:00
12 changed files with 205 additions and 125 deletions

View File

@@ -263,6 +263,12 @@
<version>2.15.1</version> <version>2.15.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.stripe</groupId> <groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId> <artifactId>stripe-java</artifactId>

View File

@@ -4,11 +4,13 @@ import com.ai.da.common.response.Response;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.ai.da.common.response.ResultEnum; import com.ai.da.common.response.ResultEnum;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/** /**
* @author: dangweijian * @author: dangweijian
@@ -36,6 +38,14 @@ public class ExceptionCatch {
return Response.error(e.getCode(), e.getMsg()); return Response.error(e.getCode(), e.getMsg());
} }
@ResponseBody
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnauthorizedException.class)
public Response<String> unauthorizedExceptionCatch(UnauthorizedException e) {
log.error("Unauthorized: {}", e.getMessage());
return Response.error(401, e.getMessage());
}
@ResponseBody @ResponseBody
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public Response<String> exceptionCatch(Exception e) { public Response<String> exceptionCatch(Exception e) {

View File

@@ -0,0 +1,12 @@
package com.ai.da.common.config.exception;
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
public UnauthorizedException() {
super("Gateway token verification failed");
}
}

View File

@@ -18,8 +18,8 @@ public class ModelConstants {
// 模型名称常量 // 模型名称常量
public static final String PRINTBOARD_ADVANCED_T2I = "qwen-image"; public static final String PRINTBOARD_ADVANCED_T2I = "qwen-image";
public static final String MOODBOARD_ADVANCED = "doubao-seedream-3-0-t2i-250415"; public static final String MOODBOARD_ADVANCED = "doubao-seedream-4-5-251128";
public static final String PRINTBOARD_HIGH_T2I = "doubao-seedream-3-0-t2i-250415"; public static final String PRINTBOARD_HIGH_T2I = "doubao-seedream-4-0-250828-high";
public static final String PRINTBOARD_HIGH_I2I = "doubao-seedream-4-0-250828-fast"; public static final String PRINTBOARD_HIGH_I2I = "doubao-seedream-4-0-250828-fast";
public static final String PRINTBOARD_ADVANCED_I2I = "doubao-seedream-4-0-250828"; public static final String PRINTBOARD_ADVANCED_I2I = "doubao-seedream-4-0-250828";
public static final String IMAGEN_MODEL = "imagen-4.0-generate-001"; public static final String IMAGEN_MODEL = "imagen-4.0-generate-001";

View File

@@ -3,17 +3,39 @@ package com.ai.da.common.context;
import com.ai.da.model.vo.AuthPrincipalVo; import com.ai.da.model.vo.AuthPrincipalVo;
public class UserContext { public class UserContext {
private static ThreadLocal<AuthPrincipalVo> userHolder = new ThreadLocal<AuthPrincipalVo>(); private static final ThreadLocal<AuthPrincipalVo> userHolder = new ThreadLocal<>();
public static void setUserHolder(AuthPrincipalVo authPrincipalVo) {
userHolder.set(authPrincipalVo);
}
public static AuthPrincipalVo getUserHolder() { public static AuthPrincipalVo getUserHolder() {
return userHolder.get(); AuthPrincipalVo holder = userHolder.get();
if (holder == null) {
throw new RuntimeException("User not authenticated");
}
if (!"AIDA".equals(holder.getSource())) {
throw new RuntimeException("Access denied: source must be AIDA");
}
return holder;
} }
public static void delete() { public static void delete() {
userHolder.remove(); userHolder.remove();
} }
public static void setUserHolder(AuthPrincipalVo authPrincipalVo) { public static Long getUserId() {
userHolder.set(authPrincipalVo); return getUserHolder().getId();
}
public static Long getBuyerId() {
AuthPrincipalVo holder = userHolder.get();
if (holder == null) {
throw new RuntimeException("User not authenticated");
}
if (!"BUYER".equals(holder.getSource())) {
throw new RuntimeException("Access denied: source must be BUYER");
}
return holder.getId();
} }
} }

View File

@@ -52,6 +52,18 @@ public class MinioUtil {
return minioClient; return minioClient;
} }
@Autowired
private RedisUtil redisUtil;
/**
* Redis缓存key前缀用于Minio签名URL缓存
*/
private static final String REDIS_MINIO_URL_PREFIX = "minio:url:";
/**
* 签名URL缓存过期时间默认1天
*/
private static final long URL_CACHE_EXPIRE_SECONDS = 24 * 60 * 60;
/** /**
* description: 判断bucket是否存在不存在则创建 * description: 判断bucket是否存在不存在则创建
* *
@@ -392,6 +404,11 @@ public class MinioUtil {
* @return 文件的临时URL如果出现异常则返回null * @return 文件的临时URL如果出现异常则返回null
*/ */
public String getPreSignedUrl(String bucketName, String fileName, int expiry) { public String getPreSignedUrl(String bucketName, String fileName, int expiry) {
String cacheKey = REDIS_MINIO_URL_PREFIX + bucketName + "/" + fileName;
Object cachedUrl = redisUtil.getFromString(cacheKey);
if (cachedUrl != null) {
return cachedUrl.toString();
}
try { try {
String lowerName = fileName.toLowerCase(); String lowerName = fileName.toLowerCase();
@@ -419,8 +436,9 @@ public class MinioUtil {
builder.extraQueryParams(queryParams); builder.extraQueryParams(queryParams);
} }
String presignedObjectUrl = minioClient.getPresignedObjectUrl(builder.build());
return minioClient.getPresignedObjectUrl(builder.build()); redisUtil.addToString(cacheKey, presignedObjectUrl, URL_CACHE_EXPIRE_SECONDS);
return presignedObjectUrl;
} catch (MinioException | InvalidKeyException } catch (MinioException | InvalidKeyException
| IOException | NoSuchAlgorithmException | IllegalArgumentException e) { | IOException | NoSuchAlgorithmException | IllegalArgumentException e) {
e.printStackTrace(); e.printStackTrace();

View File

@@ -358,6 +358,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
principal.setUsername(account.getUserName()); principal.setUsername(account.getUserName());
principal.setLanguage(account.getLanguage()); principal.setLanguage(account.getLanguage());
principal.setCountry(account.getCountry()); principal.setCountry(account.getCountry());
//区分买家端登录
principal.setSource("AIDA");
String token2 = tokenGenerateUtils.createToken(principal); String token2 = tokenGenerateUtils.createToken(principal);
// 本地 JVM 缓存(适配旧逻辑) // 本地 JVM 缓存(适配旧逻辑)
LocalCacheUtils.setTokenCache(String.valueOf(account.getId()), token2); LocalCacheUtils.setTokenCache(String.valueOf(account.getId()), token2);

View File

@@ -1553,11 +1553,11 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
if (imagePath != null) { if (imagePath != null) {
requestBuilder.image(finalImagePath1); requestBuilder.image(finalImagePath1);
} }
if (useModel.equals(ModelConstants.PRINTBOARD_HIGH_I2I)) { if (useModel.equals(ModelConstants.PRINTBOARD_HIGH_I2I)|| useModel.equals(ModelConstants.PRINTBOARD_HIGH_T2I)) {
GenerateImagesRequest.OptimizePromptOptions optimizePromptOptions = new GenerateImagesRequest.OptimizePromptOptions(); GenerateImagesRequest.OptimizePromptOptions optimizePromptOptions = new GenerateImagesRequest.OptimizePromptOptions();
optimizePromptOptions.setMode("fast"); optimizePromptOptions.setMode("fast");
requestBuilder.optimizePromptOptions(optimizePromptOptions); requestBuilder.optimizePromptOptions(optimizePromptOptions);
//由于PRINTBOARD_HIGH_I2I与PRINTBOARD_ADVANCED_I2I使用模型一致为了区别积分扣除PRINTBOARD_HIGH_I2I加入了-fast但传入模型时需要去掉-fast用PRINTBOARD_ADVANCED_I2I的常量做替代 //由于PRINTBOARD_HIGH_T2I,PRINTBOARD_HIGH_I2I与PRINTBOARD_ADVANCED_I2I使用模型一致为了区别积分扣除PRINTBOARD_HIGH_I2I加入了-fast或者-high,但传入模型时需要去掉-fast或者-high用PRINTBOARD_ADVANCED_I2I的常量做替代
requestBuilder.model(ModelConstants.PRINTBOARD_ADVANCED_I2I); requestBuilder.model(ModelConstants.PRINTBOARD_ADVANCED_I2I);
} }
@@ -4225,8 +4225,11 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
} }
// 发送POST请求到Flux API // 发送POST请求到Flux API
long start = System.currentTimeMillis();
String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString()); String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString());
JSONObject respObj = JSONUtil.parseObj(resp); JSONObject respObj = JSONUtil.parseObj(resp);
long end = System.currentTimeMillis();
log.info("flux 耗时:{}ms", end - start);
log.info("flux 发起生成请求返回结果: {}", respObj); log.info("flux 发起生成请求返回结果: {}", respObj);
// 从响应中提取任务ID // 从响应中提取任务ID

View File

@@ -169,7 +169,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
} }
/** /**
* 处理结束时间(只能延长) * 处理结束时间,不允许订阅结束时间早于当前时间和订阅开始时间
*/ */
private void handlePeriodEnd(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) { private void handlePeriodEnd(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) {
Long newEnd = dto.getCurrentPeriodEnd(); Long newEnd = dto.getCurrentPeriodEnd();
@@ -177,9 +177,20 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
return; return;
} }
if (newEnd < plan.getCurrentPeriodEnd()) { long currentTimeSec = System.currentTimeMillis() / 1000;
long startTime = plan.getCurrentPeriodStart();
// 检查是否早于开始时间不能等于否则周期长度为0
if (newEnd <= startTime) {
throw new BusinessException( throw new BusinessException(
"the.subscription.end.date.can.be.extended.only.not.reduced" "end.time.cannot.be.earlier.than.or.equal.to.start.time"
);
}
// 检查是否早于当前时间(不能等于,否则立即过期)
if (newEnd <= currentTimeSec) {
throw new BusinessException(
"end.time.cannot.be.earlier.than.or.equal.to.the.current.time"
); );
} }

View File

@@ -11,14 +11,6 @@ spring:
application: application:
name: aida-back name: aida-back
# ---------- 副数据源back 私有,由 Nacos 统一管理) ----------
# ---------- Token 生成参数(由 TokenGenerateUtils 使用) ----------
security:
jwtSecret: JWTSECRET
jwtTokenHeader: Authorization
jwtTokenPrefix: Bearer-
jwtExpiration: 8640000000
# ---------- MinIO Buckets ---------- # ---------- MinIO Buckets ----------
minio: minio:

View File

@@ -211,6 +211,8 @@ please.specify.the.organizationId=Please specify the organizationId.
switch.failed.sub-account.not.under.your.active.subscription=Switch failed. Sub-account not under your active subscription. switch.failed.sub-account.not.under.your.active.subscription=Switch failed. Sub-account not under your active subscription.
Sub-accounts.cannot.be.admins=Sub-accounts in a subscription cannot be designated as admins. Sub-accounts.cannot.be.admins=Sub-accounts in a subscription cannot be designated as admins.
only.subscription.plans.with.a.PENDING.status.can.have.their.start.time.modified=Only subscription plans with a PENDING status can have their start time modified. only.subscription.plans.with.a.PENDING.status.can.have.their.start.time.modified=Only subscription plans with a PENDING status can have their start time modified.
end.time.cannot.be.earlier.than.or.equal.to.start.time=End time cannot be earlier than or equal to start time.
end.time.cannot.be.earlier.than.or.equal.to.the.current.time=End time cannot be earlier than or equal to the current time.
the.subscription.end.date.can.be.extended.only.not.reduced=The subscription end date can be extended only, not reduced. the.subscription.end.date.can.be.extended.only.not.reduced=The subscription end date can be extended only, not reduced.
total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts=Total sub-account quota cannot be lower than existing sub-accounts. total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts=Total sub-account quota cannot be lower than existing sub-accounts.
the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used=The credit limit set cannot be lower than the amount of credits already used. the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used=The credit limit set cannot be lower than the amount of credits already used.

View File

@@ -207,6 +207,8 @@ please.specify.the.organizationId=请指定organizationId
switch.failed.sub-account.not.under.your.active.subscription=切换失败,该子账号不属于您当前管理的订阅计划 switch.failed.sub-account.not.under.your.active.subscription=切换失败,该子账号不属于您当前管理的订阅计划
Sub-accounts.cannot.be.admins=在订阅中的子账号不能被指定为管理员 Sub-accounts.cannot.be.admins=在订阅中的子账号不能被指定为管理员
only.subscription.plans.with.a.PENDING.status.can.have.their.start.time.modified=只有PENDING状态的订阅计划可以修改订阅开始时间 only.subscription.plans.with.a.PENDING.status.can.have.their.start.time.modified=只有PENDING状态的订阅计划可以修改订阅开始时间
end.time.cannot.be.earlier.than.or.equal.to.start.time=订阅结束时间不能早于或等于开始时间
end.time.cannot.be.earlier.than.or.equal.to.the.current.time=订阅结束时间不能早于或等于当前时间
the.subscription.end.date.can.be.extended.only.not.reduced=订阅的到期时间不能缩短,只能延长 the.subscription.end.date.can.be.extended.only.not.reduced=订阅的到期时间不能缩短,只能延长
total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts=设置的子账号总数量不能低于现存已添加的子账号数量 total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts=设置的子账号总数量不能低于现存已添加的子账号数量
the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used=设置的积分上限不能低于已使用的积分量 the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used=设置的积分上限不能低于已使用的积分量