67 Commits

Author SHA1 Message Date
litianxiang
401910901a Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge
# Conflicts:
#	src/main/java/com/ai/da/python/PythonService.java
2026-01-15 17:21:30 +08:00
0787025151 TASK:merge 模式返回mask 2026-01-15 14:07:18 +08:00
08b26872ff Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-14 14:28:05 +08:00
5bbf1326bb TASK:design single部分cv操作前置,新增merge | default两种designType 2026-01-14 14:26:47 +08:00
litianxiang
c5e27cd220 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-14 13:55:01 +08:00
litianxiang
ce95cb5080 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-12 14:42:02 +08:00
litianxiang
6400e79929 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-09 14:49:25 +08:00
13151b65f5 TASK:限制同一个管理员不允许绑定不同组织的订阅计划 2026-01-07 15:24:38 +08:00
9f523d5953 TASK:分页获取所有用户id,添加按邮箱模糊查询 2026-01-07 11:26:39 +08:00
4879cfeb60 BUGFIX: 2026-01-07 09:53:58 +08:00
9e252b16ef BUGFIX: 2026-01-06 17:36:12 +08:00
e64add14af BUGFIX:更新订阅计划时根据业务需要对参数进行判断并在需要时更新管理员信息 2026-01-06 17:29:33 +08:00
3beb27e491 TASK:获取用户id信息做分页;订阅计划添加国家或地区字段 2026-01-06 09:56:21 +08:00
501032ef17 TASK:试用用户试用期延长至7天 2025-12-31 13:15:13 +08:00
litianxiang
cb25bdd2e0 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2025-12-29 14:57:41 +08:00
cd767dce6f BUGFIX:新用户不能获取历史系统通知 2025-12-24 11:47:11 +08:00
bf95b85841 to dev 2025-12-23 16:28:07 +08:00
9e58bd9e7d Merge branch 'release/3.1' into dev/3.1_release_merge
# Conflicts:
#	src/main/java/com/ai/da/common/utils/SendRequestUtil.java
2025-12-23 16:16:30 +08:00
d0ec5c5c26 BUGFIX:邮件发送失败 2025-12-23 16:03:48 +08:00
ab8aa5ea5c Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2025-12-23 14:07:53 +08:00
aefcd2fdb0 BUGFIX:邮件发送失败 2025-12-23 14:07:26 +08:00
e74eab1070 BUGFIX:邮件发送失败 2025-12-23 14:05:20 +08:00
litianxiang
34da437a26 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2025-12-22 11:36:23 +08:00
35edaa0f27 CONFIG: 拦截器配置 2025-12-19 21:43:02 +08:00
f43099e19e CONFIG: redis 配置修改 2025-12-19 21:21:21 +08:00
8079877734 CONFIG: TO DEV 2025-12-19 17:40:37 +08:00
ef686e38ac CONFIG: TO PROD 2025-12-19 17:33:51 +08:00
100019d2a4 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2025-12-19 16:00:55 +08:00
litianxiang
12af237d76 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2025-12-19 15:47:36 +08:00
litianxiang
44dbbb2a4b 更换Moodboard和Printboard提示词 2025-12-19 15:47:07 +08:00
9f42e153a4 Merge branch 'release/3.1' into dev/3.1_release_merge
# Conflicts:
#	.gitea/workflows/develop_build_manual.yaml
2025-12-19 15:01:32 +08:00
4fa70a1c90 BUGFIX:订阅计划创建不允许指定子账号为管理员 2025-12-18 18:03:03 +08:00
dfb34916e7 BUGFIX:订阅计划与子账号新增 2025-12-18 14:52:12 +08:00
9f7987306c BUGFIX:订阅计划 2025-12-18 13:38:38 +08:00
litianxiang
d3fc70fbf2 fix:edit产品图照成排序问题,恢复原逻辑 2025-12-17 16:08:49 +08:00
litianxiang
17645d17e5 关闭batch MQ监听 2025-12-17 16:02:49 +08:00
litianxiang
258eea5277 edit 产品图失败会导致sort不对试验5 2025-12-17 15:43:26 +08:00
litianxiang
bb1d3bd359 Revert "edit 产品图失败会导致sort不对试验5"
This reverts commit 6a8c87ed95.
2025-12-17 15:41:02 +08:00
litianxiang
6a8c87ed95 edit 产品图失败会导致sort不对试验5 2025-12-17 15:40:47 +08:00
litianxiang
eb3826927d edit 产品图失败会导致sort不对试验4 2025-12-17 15:33:14 +08:00
litianxiang
7720c8c771 edit 产品图失败会导致sort不对试验3 2025-12-17 14:12:20 +08:00
litianxiang
b459148d58 edit 产品图失败会导致sort不对试验2 2025-12-17 13:49:07 +08:00
litianxiang
eadda18d1e Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2025-12-17 13:28:12 +08:00
litianxiang
d403df51ec edit 产品图失败会导致sort不对试验 2025-12-17 13:28:04 +08:00
6903b98b60 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2025-12-17 13:20:34 +08:00
81bf65515c Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2025-12-17 13:19:54 +08:00
8e984eb283 BUGFIX:订阅计划 2025-12-17 13:18:24 +08:00
litianxiang
afc6041570 临时测试修改 2025-12-17 13:10:09 +08:00
bce368248c Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2025-12-16 16:26:27 +08:00
ca7121dcda BUGFIX:订阅计划 2025-12-16 16:24:52 +08:00
litianxiang
9a206f9979 生产环境配置callbackUrl 2025-12-16 14:10:22 +08:00
litianxiang
eb7a46c7e8 fix:callbackUrl 2025-12-16 13:45:21 +08:00
litianxiang
95ef68a784 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2025-12-16 13:20:31 +08:00
c5af194876 BUGFIX 2025-12-16 10:42:31 +08:00
da84e1e4b4 注解修改 2025-12-15 18:39:16 +08:00
95a9c81a1b Merge branch 'dev/dev_xp' into dev/3.1_release_merge
# Conflicts:
#	src/main/java/com/ai/da/model/dto/SubscriptionPlanDTO.java
#	src/main/java/com/ai/da/model/vo/SubscriptionPlanVO.java
2025-12-15 18:34:30 +08:00
81c0d7eeac TASK:订阅计划相关 2025-12-15 18:30:28 +08:00
6cd42b799a 删除 .gitea/workflows/prod_build_schedule.yaml 2025-12-01 10:22:29 +08:00
6e1ed7f9b8 删除 .gitea/workflows/prod_build_manual.yaml 2025-12-01 10:22:25 +08:00
b7be16738b 删除 .gitea/workflows/develop_build_manual.yaml 2025-12-01 10:22:21 +08:00
6da5e91ec1 删除 .gitea/workflows/develop_build_commit.yaml 2025-12-01 10:22:17 +08:00
a710fdd432 删除 docker-compose.yml 2025-11-30 11:01:12 +08:00
d598f53d3c Merge branch 'dev/3.1_release_merge' into release/3.1
# Conflicts:
#	.gitea/workflows/prod_build_schedule.yaml
2025-11-28 17:16:55 +08:00
96170a9956 更新 .gitea/workflows/prod_build_manual.yaml 2025-11-28 15:25:50 +08:00
8205fb5290 上传文件至「.gitea/workflows」 2025-11-28 15:25:41 +08:00
fcbe4762b3 TO prod 2025-11-27 17:38:24 +08:00
e750adcc94 TO prod 2025-11-27 17:35:47 +08:00
43 changed files with 1119 additions and 272 deletions

View File

@@ -636,26 +636,26 @@ public class GenerateConsumer {
public void getPoseTransformationResult(Message msg, Channel channel) {
processPoseTransformResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.designBatch}")
@RabbitHandler
public void getDesignBatchResult(Message msg, Channel channel) {
processDesignBatchResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.toProductImageBatch}")
@RabbitHandler
public void getToProductImageBatchResult(Message msg, Channel channel) {
processToProductImageBatchResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.relightBatch}")
@RabbitHandler
public void getRelightBatchResult(Message msg, Channel channel) {
processRelightBatchResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.poseTransformBatch}")
@RabbitHandler
public void getPoseTransformBatchResult(Message msg, Channel channel) {
processPoseTransformBatchResult(msg, channel);
}
// @RabbitListener(queues = "#{rabbitMQProperties.queues.designBatch}")
// @RabbitHandler
// public void getDesignBatchResult(Message msg, Channel channel) {
// processDesignBatchResult(msg, channel);
// }
// @RabbitListener(queues = "#{rabbitMQProperties.queues.toProductImageBatch}")
// @RabbitHandler
// public void getToProductImageBatchResult(Message msg, Channel channel) {
// processToProductImageBatchResult(msg, channel);
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.relightBatch}")
// @RabbitHandler
// public void getRelightBatchResult(Message msg, Channel channel) {
// processRelightBatchResult(msg, channel);
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.poseTransformBatch}")
// @RabbitHandler
// public void getPoseTransformBatchResult(Message msg, Channel channel) {
// processPoseTransformBatchResult(msg, channel);
// }
}

View File

@@ -308,7 +308,7 @@ public class MyTaskScheduler {
private MinioUtil minioUtil;
@Resource
private UserLikeService userLikeService;
public void clearMinio() {
/* public void clearMinio() {
List<CollectionElement> collectionElements = collectionElementMapper.selectDeleteList();
for (CollectionElement collectionElement : collectionElements) {
String url = collectionElement.getUrl();
@@ -472,7 +472,7 @@ public class MyTaskScheduler {
// }
// String preSignedUrl = minioUtil.getPreSignedUrl("aida-clothing/image/image_1698374859.3031476.png", 10000);
// System.out.println(preSignedUrl);
}
}*/
@Resource
private AttributeRetrievalMapper attributeRetrievalMapper;

View File

@@ -3,6 +3,7 @@ package com.ai.da.common.task;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.service.AccountService;
import com.ai.da.service.SubscriptionPlanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -18,6 +19,8 @@ public class AccountTask {
private AccountService accountService;
@Resource
private RedisUtil redisUtil;
@Resource
private SubscriptionPlanService subscriptionPlanService;
/**
* 每周日晚上刷新 年付用户、月付用户的积分
@@ -86,4 +89,14 @@ public class AccountTask {
public void checkEduAdminExpireStatus() {
accountService.checkEduAdminExpireStatus();
}
@Scheduled(cron = "0 5 0 * * ?")
public void activeSubscriptionPlan() {
subscriptionPlanService.activeSubscriptionPlan(null);
}
@Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void expireSubscription() {
subscriptionPlanService.expireSubscription();
}
}

View File

@@ -40,7 +40,7 @@ public class SubscriptionReminderTask {
REMINDER_DAYS_CONFIG.put("year", 14);
}
@Scheduled(cron = "0 0 9 * * ?")
// @Scheduled(cron = "0 0 9 * * ?")
public void subscriptionReminder() {
// 获取所有需要通知的订阅
List<SubscriptionInfo> subscriptionInfos = getDueSubscriptions();
@@ -97,7 +97,7 @@ public class SubscriptionReminderTask {
return subscriptionInfoMapper.selectList(qw);
}
@Scheduled(cron = "0 0 9 * * ?")
// @Scheduled(cron = "0 0 9 * * ?")
public void trialReminder() {
// 今天的 00:00:00 和 23:59:59
LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay();

View File

@@ -883,8 +883,8 @@ public class SendEmailUtil {
if (!type.equals("cancel") && !type.equals("fail_new")) {
// 返回的resp是一个SendEmailResponse的实例与请求对象对应
// SendEmailResponse respUser = client.SendEmail(user);
log.info("邮件主题:{}发送结果toUser###{}, email:{}", user.getSubject(), /*SendEmailResponse.toJsonString(respUser)*/null, receiverAddress);
SendEmailResponse respUser = client.SendEmail(user);
log.info("邮件主题:{}发送结果toUser###{}, email:{}", user.getSubject(), SendEmailResponse.toJsonString(respUser), receiverAddress);
}
if (!type.startsWith("reminder")) {
SendEmailResponse respMerchant = client.SendEmail(merchant);
@@ -1024,7 +1024,7 @@ public class SendEmailUtil {
log.info("邮件发送结果res###{}", SendEmailResponse.toJsonString(resp));
} catch (TencentCloudSDKException e) {
log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail");
// throw new BusinessException("failed.to.send.mail");
}
}

View File

@@ -114,8 +114,8 @@ public class SendRequestUtil {
public String sendFluxPost(String url, String requestBodyStr) {
// 尝试两个API key
String[] apiKeys = {"d447a0ac-2291-4f1c-9a36-f7614c385989",
"84e8f5d5-b0b3-49aa-b244-ab7ba27e7ae7"};
String[] apiKeys = {"84e8f5d5-b0b3-49aa-b244-ab7ba27e7ae7",
"d447a0ac-2291-4f1c-9a36-f7614c385989"};
boolean[] notified = {false, false}; // 记录是否已发送过不足提醒
for (int i = 0; i < apiKeys.length; i++) {
@@ -140,8 +140,8 @@ public class SendRequestUtil {
if (status == 402 || status == 403) {
if (!notified[i]) {
SendEmailUtil.commonExceptionReminder(
"Flux账户积分不足flux生成任务失败",
new String[]{"xupei3360@163.com, fangjianliao@aidlab.hk, investigation@aidlab.hk"}
"Flux账户积分不足flux生成任务失败key:" + apiKeys[i],
new String[]{"xupei3360@163.com", "fangjianliao@aidlab.hk", "investigation@aidlab.hk"}
);
notified[i] = true;
}

View File

@@ -15,16 +15,16 @@ import com.ai.da.model.vo.PersonalHomepageVO;
import com.ai.da.service.AccountService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -139,21 +139,21 @@ public class AccountController {
@Operation(summary = "aws状态检测")
@GetMapping("/healthy")
@ResponseStatus(HttpStatus.OK)
public Response<Map<String,Integer>> checkStatus(){
Map<String,Integer> returnMap = new HashMap<>();
returnMap.put("code",200);
public Response<Map<String, Integer>> checkStatus() {
Map<String, Integer> returnMap = new HashMap<>();
returnMap.put("code", 200);
return Response.success(returnMap);
}
@Operation(summary = "查询账号到期时间")
@PostMapping("/getExpiredTime")
public Response<Long> getExpiredTime(){
public Response<Long> getExpiredTime() {
return Response.success(accountService.getExpiredTime());
}
@Operation(summary = "免密登录")
@PostMapping("/noLoginRequired")
public Response<AccountLoginVO> noLoginRequired(@RequestBody NoLoginRequiredDTO noLoginRequiredDTO, HttpServletRequest request){
public Response<AccountLoginVO> noLoginRequired(@RequestBody NoLoginRequiredDTO noLoginRequiredDTO, HttpServletRequest request) {
return Response.success(accountService.noLoginRequired(noLoginRequiredDTO, request));
}
@@ -191,6 +191,7 @@ public class AccountController {
/**
* 参与活动 获取福利
*
* @return
*/
/* @Operation(summary = "参与活动 获取福利")
@@ -201,7 +202,7 @@ public class AccountController {
@Operation(summary = "将用户账号过期时间设置为过期当天的235959")
@GetMapping("/setUserValidToDayEnd")
public Response<List<Long>> setUserValidToDayEnd(){
public Response<List<Long>> setUserValidToDayEnd() {
return Response.success(accountService.setUserValidToDayEnd());
}
@@ -223,19 +224,19 @@ public class AccountController {
@Operation(summary = "获取个人主页信息")
@GetMapping("/personalHomepage")
public Response<PersonalHomepageVO> getPersonalHomepage(@RequestParam("id") Long id){
public Response<PersonalHomepageVO> getPersonalHomepage(@RequestParam("id") Long id) {
return Response.success(accountService.getPersonalHomepage(id));
}
@Operation(summary = "getUsernameModifyTimes")
@GetMapping("/getNicknameModifyTimes")
public Response<Long> getNicknameModifyTimes(){
public Response<Long> getNicknameModifyTimes() {
return Response.success(accountService.getNicknameModifyTimes());
}
@Operation(summary = "editUserName")
@GetMapping("/editUserName")
public Response<String> editUserName(@RequestParam("newUserName") String newUserName){
public Response<String> editUserName(@RequestParam("newUserName") String newUserName) {
accountService.editUserName(newUserName);
return Response.success("success");
}

View File

@@ -179,8 +179,10 @@ public class ConvenientInquiryController {
@Operation(summary = "获取所有用户id")
@GetMapping("/getAllUserId")
public Response<List<Map<String, Object>>> getAllUsrIdList() {
return Response.success(convenientInquiryService.getAllUserIdList());
public Response<IPage<Map<String, Object>>> getAllUserIdList(@Parameter(description = "page") @RequestParam Integer page,
@Parameter(description = "size") @RequestParam Integer size,
@Parameter(description = "email 模糊查询") @RequestParam(required = false) String email) {
return Response.success(convenientInquiryService.getAllUserIdList(page, size, email));
}
@Operation(summary = "获取所有交易信息")

View File

@@ -1,12 +1,15 @@
package com.ai.da.controller;
import com.ai.da.common.response.Response;
import com.ai.da.common.task.SubscriptionReminderTask;
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
import com.ai.da.model.dto.SubscriptionPlanDTO;
import com.ai.da.model.dto.SubscriptionPlanPageQuery;
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
import com.ai.da.model.vo.SubscriptionPlanVO;
import com.ai.da.service.SubscriptionPlanService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
@@ -14,6 +17,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "订阅计划模块")
@Slf4j
@RequiredArgsConstructor
@@ -23,6 +28,8 @@ public class SubscriptionPlanController {
private final SubscriptionPlanService subscriptionPlanService;
private final SubscriptionReminderTask subscriptionReminderTask;
@Operation(summary = "创建订阅计划")
@PostMapping("/createPlan")
public Response<String> createPlan(@Valid @RequestBody SubscriptionPlanDTO subscriptionPlanDTO) {
@@ -38,6 +45,12 @@ public class SubscriptionPlanController {
}
@Operation(summary = "搜索订阅计划")
@PostMapping("/searchByOrganizationIdAndStatus")
public Response<List<SubscriptionPlan>> searchByOrganizationIdAndStatus(@Valid @RequestBody SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
return Response.success(subscriptionPlanService.searchByOrganizationIdAndStatus(subscriptionPlanPageQuery));
}
@Operation(summary = "分页搜索订阅计划")
@PostMapping("/searchByPage")
public Response<IPage<SubscriptionPlanVO>> searchByPage(@Valid @RequestBody SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
IPage<SubscriptionPlanVO> subscriptionPlanVOIPage = subscriptionPlanService.searchByPage(subscriptionPlanPageQuery);
@@ -53,18 +66,27 @@ public class SubscriptionPlanController {
@Operation(summary = "管理员切换订阅计划")
@GetMapping("/switchSubscriptionPlan")
public Response<String> switchSubscriptionPlan(@RequestParam Long subscriptionPlanId, @RequestParam Long adminAccId) {
subscriptionPlanService.switchSubscriptionPlan(subscriptionPlanId, adminAccId);
public Response<String> switchSubscriptionPlan(@RequestParam Long targetSubscriptionPlanId, @RequestParam(required = false) Long adminAccId) {
subscriptionPlanService.switchSubscriptionPlan(targetSubscriptionPlanId, adminAccId);
return Response.success();
}
@Operation(summary = "子账号切换订阅计划")
@GetMapping("/switchSubAccSubscriptionPlan")
public Response<String> switchSubAccSubscriptionPlan(@RequestParam Long targetSubscriptionPlanId, @RequestParam Long subAccId) {
subscriptionPlanService.switchSubAccSubscriptionPlan(targetSubscriptionPlanId, subAccId);
return Response.success();
}
// @Hidden
@Operation(summary = "activeSubscriptionPlan")
@GetMapping("/activeSubscriptionPlan")
public Response<String> activeSubscriptionPlan() {
subscriptionPlanService.activeSubscriptionPlan();
subscriptionPlanService.activeSubscriptionPlan(null);
return Response.success();
}
// @Hidden
@Operation(summary = "expireSubscription")
@GetMapping("/expireSubscription")
public Response<String> expireSubscription() {
@@ -72,5 +94,19 @@ public class SubscriptionPlanController {
return Response.success();
}
@Operation(summary = "subscriptionReminder")
@GetMapping("/subscriptionReminder")
public Response<String> subscriptionReminder() {
subscriptionReminderTask.subscriptionReminder();
return Response.success();
}
@Operation(summary = "trialReminder")
@GetMapping("/trialReminder")
public Response<String> trialReminder() {
subscriptionReminderTask.trialReminder();
return Response.success();
}
}

View File

@@ -4,6 +4,7 @@ import com.ai.da.common.config.mybatis.plus.CommonMapper;
import com.ai.da.mapper.primary.entity.Notification;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -20,5 +21,5 @@ public interface NotificationMapper extends CommonMapper<Notification> {
void setPersonalNotificationAllRead(String type, Long receiverId, LocalDateTime time);
List<Long> getUnreadSysNotification(Long accountId);
List<Long> getUnreadSysNotification(Long accountId, Date createTime);
}

View File

@@ -1,7 +1,29 @@
package com.ai.da.mapper.primary;
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
import com.ai.da.model.vo.SubscriptionPlanVO;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface SubscriptionPlanMapper extends BaseMapper<SubscriptionPlan> {
/**
* 关联查询订阅计划信息(包含管理员邮箱)- 分页
*/
@Select("""
SELECT sp.*,
a.user_email AS adminAccEmail,
a.user_name AS adminAccName,
o.name AS organizationName
FROM t_subscription_plan sp
LEFT JOIN t_account a ON sp.admin_acc_id = a.id
LEFT JOIN t_organization o on sp.organization_id = o.id
${ew.customSqlSegment}
""")
Page<SubscriptionPlanVO> selectWithEmailPage(Page<SubscriptionPlanVO> page,
@Param(Constants.WRAPPER) Wrapper<?> wrapper);
}

View File

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import java.io.Serializable;
@@ -76,13 +77,13 @@ public class Account implements Serializable {
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createDate;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateDate;
private Integer isTrial;
@@ -142,4 +143,26 @@ public class Account implements Serializable {
private String givenName;
private Long subscriptionPlanId;
// 在类内部定义的枚举
@Getter
public enum SystemRole {
VISITOR("游客", 0),
YEARLY("年付用户", 1),
MONTHLY("月付用户", 2),
TRIAL("试用用户", 3),
EVENT_USER("参加活动获取30天有效期和6000个积分的用户", 4),
ENTERPRISE_ADMIN("企业管理员账号", 5),
ENTERPRISE_SUB("企业子账号", 6),
EDUCATION_ADMIN("学校管理员", 7),
EDUCATION_SUB("学校子账号", 8);
private final String desc;
private final int code;
SystemRole(String desc, int code) {
this.desc = desc;
this.code = code;
}
}
}

View File

@@ -67,6 +67,11 @@ public class SubscriptionPlan extends BaseEntity{
*/
private String status;
/**
* 国家或地区
*/
private String countryOrRegion;
// 在类内部定义的枚举
@Getter
public enum SubscriptionStatus {

View File

@@ -43,6 +43,10 @@ public class DesignSingleIncludeLayersDTO implements Serializable {
@Schema(description = "项目id")
private Long projectId;
@NotBlank(message = "designType cannot be empty")
@Schema(description = "default -> 新增sketch || merge")
private String designType;
@Override
public String toString() {
return "DesignSingleIncludeLayersDTO{" +

View File

@@ -73,4 +73,10 @@ public class DesignSingleItemDTO implements Serializable {
@Schema(description = "45")
private double rotate;
@Schema(description = "带overall印花未分割图片")
private String undividedLayerBase64;
@Schema(description = "带overall/single印花未分割图片")
private String undividedLayerWithSinglePrintBase64;
}

View File

@@ -20,17 +20,17 @@ public class PartialDesignDTO implements Serializable {
@Schema(description = "图片的base64格式")
private String partialDesignBase64;
@Schema(description = "图层信息")
private List<String> layers;
/* @Schema(description = "图层信息")
private List<String> layers;*/
public PartialDesignDTO(String partialDesignMinioPath) {
this.partialDesignMinioPath = partialDesignMinioPath;
}
public PartialDesignDTO(String partialDesignMinioPath, List<String> layers) {
/* public PartialDesignDTO(String partialDesignMinioPath, List<String> layers) {
this.partialDesignMinioPath = partialDesignMinioPath;
this.layers = layers;
}
}*/
public PartialDesignDTO(String partialDesignMinioPath, String partialDesignPath) {
this.partialDesignMinioPath = partialDesignMinioPath;

View File

@@ -14,4 +14,6 @@ public class SubAccountPageDTO extends PageQueryBaseVo {
private List<String> email;
private List<String> userName;
private Long subscriptionPlanId;
}

View File

@@ -2,7 +2,9 @@ package com.ai.da.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import java.math.BigDecimal;
@@ -13,18 +15,22 @@ public class SubscriptionPlanDTO {
@Schema(description = "组织id")
@NotNull(message = "Please select an organizationId.")
@Positive(message = "组织ID必须大于0")
private Long organizationId;
@Schema(description = "当前订阅开始时间")
@NotNull(message = "Please set a subscription start time.")
@Min(value = 0, message = "开始时间不能小于0")
private Long currentPeriodStart;
@Schema(description = "当前订阅结束时间")
@NotNull(message = "Please set a subscription end time.")
@Min(value = 0, message = "结束时间不能小于0")
private Long currentPeriodEnd;
@Schema(description = "当前订阅总的子账号数量")
@NotNull(message = "Please set the sub-account number.")
@Min(value = 0, message = "子账号数量不能小于0")
private Integer accountNum;
@Schema(description = "当前订阅可用积分上限")
@@ -33,6 +39,16 @@ public class SubscriptionPlanDTO {
@Schema(description = "管理员账户id")
@NotNull(message = "Please assign an administrator account.")
@Positive(message = "管理员账号ID必须大于0")
private Long adminAccId;
@Schema(description = "订阅计划命名")
private String name;
@Schema(description = "订阅计划状态")
private String status;
@Schema(description = "国家或地区")
private String CountryOrRegion;
}

View File

@@ -12,9 +12,12 @@ public class SubscriptionPlanPageQuery extends QueryPageByTimeDTO {
@Schema(description = "组织id")
private Long organizationId;
@Schema(description = "管理id")
@Schema(description = "管理id")
private Long adminAccId;
@Schema(description = "状态 PENDING||ACTIVE||EXPIRED")
private List<String> status;
@Schema(description = "国家或地区")
private String countryOrRegion;
}

View File

@@ -29,4 +29,10 @@ public class UpdateSubscriptionPlanDTO {
@Schema(description = "管理员账户id")
private Long adminAccId;
@Schema(description = "订阅重命名")
private String name;
@Schema(description = "国家或地区")
private String countryOrRegion;
}

View File

@@ -1,5 +1,5 @@
package com.ai.da.model.vo;
package com.ai.da.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import com.ai.da.mapper.primary.entity.AccountExtend;
@@ -79,4 +79,10 @@ public class AccountLoginVO {
private String givenName;
private Long organizationId;
private String organizationName;
private Long subscriptionPlanId;
}

View File

@@ -15,6 +15,9 @@ public class SubscriptionPlanVO {
@Schema(description = "组织id")
private Long organizationId;
@Schema(description = "组织名称")
private String organizationName;
@Schema(description = "当前订阅开始时间")
private Long currentPeriodStart;
@@ -30,8 +33,19 @@ public class SubscriptionPlanVO {
@Schema(description = "管理员账户id")
private Long adminAccId;
@Schema(description = "管理员账户邮箱")
private String adminAccEmail;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "状态")
private String status;
@Schema(description = "命名")
private String name;
@Schema(description = "国家或地区")
private String countryOrRegion;
}

View File

@@ -2789,7 +2789,7 @@ public class PythonService {
DesignPythonObject pythonObject = new DesignPythonObject();
designPythonObjects.setProcess_id(designSingleDTO.getProcessId());
pythonObject.setItems(coverToDesignSinglePythonItem(designSingleDTO, designLibraryModelPoint, singleOverall));
pythonObject.setBasic(coverToSingleBasic(singleOverall, switchCategory, designLibraryModelPoint, previewOrSubmit));
pythonObject.setBasic(coverToSingleBasic(singleOverall, switchCategory, designLibraryModelPoint, designSingleDTO.getDesignType()));
objects.add(pythonObject);
return designPythonObjects;
}
@@ -2855,6 +2855,9 @@ public class PythonService {
designSingleItem.getPartialDesign().getPartialDesignMinioPath());
resolveDesignElement(designSingleItem.getTrims(), printToPython);
log.info("组装参数【服装:{}的maskUrl: {}】",designSingleItem.getType(), designSingleItem.getMaskUrl());
String mergeImagePath = !StringUtil.isNullOrEmpty(printToPython.getPartial())
? printToPython.getPartial() : designSingleItem.getPath();
response.add(new DesignPythonItem(
designSingleItem.getType(),
designSingleItem.getPath(),
@@ -2871,7 +2874,8 @@ public class PythonService {
gradientString,
designSingleItem.getMaskUrl(),
designSingleItem.getTranspose(),
designSingleItem.getRotate()
designSingleItem.getRotate(),
mergeImagePath
));
});
@@ -3049,7 +3053,7 @@ public class PythonService {
*/
private DesignPythonBasic coverToSingleBasic(String singleOverall, String switchCategory,
DesignLibraryModelPointVO designLibraryModelPoint,
String previewOrSubmit) {
String designType) {
DesignPythonBasic basic = new DesignPythonBasic();
basic.setSingle_overall(singleOverall);
basic.setSwitch_category(switchCategory);
@@ -3061,7 +3065,8 @@ public class PythonService {
basic.setScale_earrings(0.16);
basic.setBody_point_test(getMap(designLibraryModelPoint));
basic.setLayer_order(Boolean.TRUE);
basic.setPreview_submit(previewOrSubmit);
// basic.setPreview_submit(previewOrSubmit);
basic.setDesign_type(designType);
return basic;
}

View File

@@ -16,7 +16,7 @@ public class DesignPythonBasic {
private String single_overall;
private String preview_submit;
// private String preview_submit;
private String switch_category;
/**
@@ -40,4 +40,7 @@ public class DesignPythonBasic {
private Boolean layer_order = Boolean.FALSE;
// default | merge
private String design_type = "default";
}

View File

@@ -97,6 +97,12 @@ public class DesignPythonItem {
*/
private double rotate;
/**
* 前端处理了print之后的结果图python对该图进行分割
* designType为merge时该字段必须有值否则会导致python端没有数据返回
*/
private String merge_image_path;
public static List<String> OUTWEAR_DRESS_BLOUSE = Arrays.asList(CollectionLevel2TypeEnum.OUTWEAR.getRealName(),
CollectionLevel2TypeEnum.DRESS.getRealName(), CollectionLevel2TypeEnum.BLOUSE.getRealName());
@@ -153,7 +159,8 @@ public class DesignPythonItem {
public DesignPythonItem(String type, String path, String color, PrintToPython print, Long businessId,
Long image_id, List<Long> offset, Float[] resize_scale, Integer priority, String gradient,
String gradientString, String seg_mask_url, int[] transpose, double rotate) {
String gradientString, String seg_mask_url, int[] transpose, double rotate,
String merge_image_path) {
this.type = type;
this.path = path;
this.color = color;
@@ -169,6 +176,7 @@ public class DesignPythonItem {
this.seg_mask_url = seg_mask_url;
this.transpose = transpose;
this.rotate = rotate;
this.merge_image_path = merge_image_path;
}
public DesignPythonItem(String type, String path, String color, PrintToPython print, String icon, Long businessId, Long image_id) {

View File

@@ -51,7 +51,7 @@ public interface ConvenientInquiryService extends IService<Questionnaire> {
IPage<Account> getUserInfo(QueryUserConditionsVO queryUserConditionsVO);
List<Map<String, Object>> getAllUserIdList();
IPage<Map<String, Object>> getAllUserIdList(Integer pageNum, Integer pageSize, String email);
PageBaseResponse<PaymentInfoVO> queryTransactionRecords(QueryPaymentInfoDTO queryPaymentInfoDTO);

View File

@@ -53,7 +53,7 @@ public interface DesignItemService extends IService<DesignItem> {
DesignSingleVO designSingleIncludeLayers(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO);
Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers);
Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers, DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO);
Map<String, String> setTypeAndUndividedLayer(JSONArray layers);

View File

@@ -8,19 +8,25 @@ import com.ai.da.model.vo.SubscriptionPlanVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface SubscriptionPlanService extends IService<SubscriptionPlan> {
void createPlan(SubscriptionPlanDTO subscriptionPlanDTO);
void updatePlan(UpdateSubscriptionPlanDTO updateDTO);
List<SubscriptionPlan> searchByOrganizationIdAndStatus(SubscriptionPlanPageQuery subscriptionPlanPageQuery);
IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery);
void deletePlan(Long id);
void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId);
void activeSubscriptionPlan();
void switchSubAccSubscriptionPlan(Long subscriptionPlanId, Long subAccId);
void activeSubscriptionPlan(Long planId);
void expireSubscription();
}

View File

@@ -1284,7 +1284,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
account.setSystemUser(3);
account.setIsTrial(1);
account.setCredits(BigDecimal.valueOf(50));
account.setValidEndTime(toDayEnd(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli()));
// 广场用户试用延长至7天
account.setValidEndTime(toDayEnd(Instant.now().plus(7, ChronoUnit.DAYS).toEpochMilli()));
account.setIsBeginner(1);
account.setValidStartTime(Instant.now().toEpochMilli());
account.setCreateDate(new Date());
@@ -2436,6 +2437,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
Account adminAcc = accountMapper.selectById(authPrincipalVo.getId());
int subUserRole = getSubUserRole(adminAcc.getSystemUser());
if (Objects.isNull(adminAcc.getSubscriptionPlanId())) {
throw new BusinessException("relate.to.any.subscription");
}
if (addSubAccountDTO.getId() == null) {
return createSubAccount(addSubAccountDTO, adminAcc, subUserRole);
@@ -2458,7 +2462,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Transactional(rollbackFor = Exception.class)
public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account adminAcc, int subUserRole) {
QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().eq(Account::getOrganizationId, adminAcc.getOrganizationId());
qw.lambda().eq(Account::getOrganizationId, adminAcc.getOrganizationId())
.eq(Account::getSubscriptionPlanId, adminAcc.getSubscriptionPlanId());
List<Account> accounts = accountMapper.selectList(qw);
// 校验子账号总数是否达上限
@@ -2502,12 +2507,13 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
}
// 判断当前账号的有效期是否与管理员同步
if (subAccount.getValidEndTime() < adminAcc.getValidEndTime()){
if (Objects.isNull(subAccount.getValidEndTime()) || subAccount.getValidEndTime() < adminAcc.getValidEndTime()){
subAccount.setValidEndTime(adminAcc.getValidEndTime());
}
subAccount.setSystemUser(subUserRole);
subAccount.setOrganizationId(adminAcc.getOrganizationId());
subAccount.setOrganizationName(adminAcc.getOrganizationName());
subAccount.setParentId(adminAcc.getId());
subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId());
if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())) {
@@ -2572,7 +2578,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
subAccount.setIsTrial(0);
subAccount.setIsBeginner(1);
subAccount.setParentId(adminAcc.getId());
// subAccount.setOrganizationName(adminAcc.getOrganizationName());
subAccount.setOrganizationName(adminAcc.getOrganizationName());
subAccount.setOrganizationId(adminAcc.getOrganizationId());
subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId());
accountMapper.insert(subAccount);
@@ -2747,7 +2753,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
for (Long id : addSubAccountDTO.getDeleteIdList()) {
Account account = baseMapper.selectById(id);
if (Objects.nonNull(account.getParentId()) && account.getParentId().equals(adminAccId)) {
BigDecimal unusedCredits = account.getCreditsUsageLimit().subtract(Objects.isNull(account.getCreditsUsage()) ? BigDecimal.ZERO : account.getCreditsUsage());
BigDecimal unusedCredits =
Objects.requireNonNullElse(account.getCreditsUsageLimit(), BigDecimal.ZERO)
.subtract(Objects.requireNonNullElse(account.getCreditsUsage(), BigDecimal.ZERO));
BigDecimal finalCredits = calculateFinalCredits(account, unusedCredits);
int userRole = determineUserRole(account.getId());
@@ -2820,7 +2828,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
Account account = accountMapper.selectById(authPrincipalVo);
QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().ne(Account::getId, account.getId());
qw.lambda().ne(Account::getId, account.getId()).ne(Account::getSystemUser, 7);
qw.lambda().eq(Account::getOrganizationId, account.getOrganizationId());
if (StringUtils.isNotBlank(subAccountPageDTO.getStartTime())) {
qw.lambda().ge(Account::getCreateDate, subAccountPageDTO.getStartTime());
@@ -2838,6 +2846,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
} else if (subAccountPageDTO.getUserName() != null && subAccountPageDTO.getUserName().size() > 1) {
qw.lambda().in(Account::getUserName, subAccountPageDTO.getUserName());
}
if (Objects.nonNull(subAccountPageDTO.getSubscriptionPlanId())) {
qw.lambda().eq(Account::getSubscriptionPlanId, subAccountPageDTO.getSubscriptionPlanId());
}
// 执行分页查询
IPage<Account> page = accountMapper.selectPage(new Page<>(subAccountPageDTO.getPage(), subAccountPageDTO.getSize()), qw);
@@ -3183,6 +3194,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
}
response.setUsernameModify(getNicknameModifyTimes());
response.setOrganizationId(account.getOrganizationId());
response.setOrganizationName(account.getOrganizationName());
response.setSubscriptionPlanId(account.getSubscriptionPlanId());
return response;
}
@@ -3555,7 +3569,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
// 只有当前子账号数量为0时允许批量上传
QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().eq(Account::getSystemUser, 8)
.eq(Account::getOrganizationId, parent.getOrganizationId());
.eq(Account::getOrganizationId, parent.getOrganizationId())
.eq(Account::getSubscriptionPlanId, parent.getSubscriptionPlanId());
List<Account> accounts = accountMapper.selectList(qw);
if (!accounts.isEmpty()) {
throw new BusinessException("permit.bulk.creation", ResultEnum.PROMPT.getCode());

View File

@@ -798,27 +798,46 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
return accountMapper.selectPage(new Page<>(queryUserConditionsVO.getPage(), queryUserConditionsVO.getSize()), queryWrapper);
}
public List<Map<String, Object>> getAllUserIdList() {
public IPage<Map<String, Object>> getAllUserIdList(Integer pageNum, Integer pageSize, String email) {
Long accountId = UserContext.getUserHolder().getId();
Account account = accountMapper.selectById(accountId);
// 允许查看数据的用户id
if (Objects.nonNull(account.getSystemUser())
&& (account.getSystemUser().equals(5)
|| account.getSystemUser().equals(7)
|| ADMIN_IDS.contains(account.getId())
|| ADMIN_IDS_READ_ONLY.contains(account.getId())
)) {
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id as value, user_name as label");
if ((account.getSystemUser().equals(7) || account.getSystemUser().equals(5))
&& !StringUtil.isNullOrEmpty(account.getOrganizationName())) {
queryWrapper.lambda().eq(Account::getOrganizationName, account.getOrganizationName());
}
return accountMapper.selectMaps(queryWrapper);
} else {
// 权限校验
if (Objects.isNull(account.getSystemUser())
|| (!account.getSystemUser().equals(5)
&& !account.getSystemUser().equals(7)
&& !ADMIN_IDS.contains(account.getId())
&& !ADMIN_IDS_READ_ONLY.contains(account.getId()))) {
throw new BusinessException("have.no.permission", ResultEnum.PROMPT.getCode());
}
// return maps.stream().map(map -> (Long)map.get("id")).collect(Collectors.toList());
// 创建分页对象
Page<Account> page = new Page<>(pageNum, pageSize);
// 构建查询条件
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "user_name", "user_email");
if ((account.getSystemUser().equals(7) || account.getSystemUser().equals(5))
&& !StringUtil.isNullOrEmpty(account.getOrganizationName())) {
queryWrapper.lambda().eq(Account::getOrganizationName, account.getOrganizationName());
}
if (!StringUtil.isNullOrEmpty(email)) {
queryWrapper.lambda().like(Account::getUserEmail, email);
}
// 执行分页查询
IPage<Account> accountPage = accountMapper.selectPage(page, queryWrapper);
// 转换为 IPage<Map> 并重命名字段
return accountPage.convert(acc -> {
Map<String, Object> map = new HashMap<>();
map.put("value", acc.getId());
map.put("label", acc.getUserName());
map.put("email", acc.getUserEmail());
return map;
});
}
/**

View File

@@ -269,9 +269,11 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
BigDecimal existingCredits = account.getCredits();
BigDecimal subtract = existingCredits.subtract(new BigDecimal(value));
BigDecimal creditsUsage = null;
if (!StringUtil.isNullOrEmpty(account.getOrganizationName())){
// if (!StringUtil.isNullOrEmpty(account.getOrganizationName())){
if (Objects.nonNull(account.getSubscriptionPlanId())){
creditsUsage = Objects.isNull(account.getCreditsUsage()) ? BigDecimal.ZERO : account.getCreditsUsage();
creditsUsage = creditsUsage.add(new BigDecimal(value));
BigDecimal added = creditsUsage.add(new BigDecimal(value));
creditsUsage = added.compareTo(account.getCreditsUsageLimit()) > 0 ? account.getCreditsUsageLimit() : added;
}
accountService.updateCreditsAndEndTime(account, subtract.toString(), null, creditsUsage);

View File

@@ -365,7 +365,7 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
, JSONObject outfit, String timeZone, List<DesignSingleItemDTO> designSingleItemDTOList
, Map<String, List<String>> priorityAndUndividedLayer
, boolean changeModelFlag
, Long modelId, String modelType, boolean isSingleCollectionFlag) {
, Long modelId, String modelType, boolean isSingleCollectionFlag, String designType) {
DesignItem designItem = new DesignItem();
// String url = pythonObjects.getObjects().get(0).getBasic().getSave_name();
@@ -425,9 +425,12 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
designItemDetail.setIconPath(detail.getIcon());
// designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getType().toLowerCase()));
if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(0));
designItemDetail.setUndividedLayerWithSinglePrint(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1));
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(0))) {
designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getPriority().toString()).getFirst());
}
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1))) {
designItemDetail.setUndividedLayerWithSinglePrint(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1));
}
}
// 印花存储在design_item_detail_print表中 这里还要存吗?
// DesignPythonItemPrint printObject = detail.getPrintToPython();
@@ -481,6 +484,7 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
priorityOffset = designSingleItemDTOList.stream()
.collect(Collectors.toMap(DesignSingleItemDTO::getPriority, DesignSingleItemDTO::getOffset));
}
List<TDesignPythonOutfitDetail> list = new ArrayList<>();
for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i);
@@ -708,6 +712,17 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
log.info("set partialDesignBase64为空便于日志打印");
i.getPartialDesign().setPartialDesignBase64(null);
}
// undividedLayerBase64 undividedLayerWithSinglePrintBase64 置空
// 前端合成的未分割的图
if (!StringUtil.isNullOrEmpty(i.getUndividedLayerBase64())) {
log.info("set UndividedLayerBase64为空便于日志打印");
i.setUndividedLayerBase64(null);
}
// 前端合成的未分割的图
if (!StringUtil.isNullOrEmpty(i.getUndividedLayerWithSinglePrintBase64())) {
log.info("set UndividedLayerWithSinglePrintBase64为空便于日志打印");
i.setUndividedLayerWithSinglePrintBase64(null);
}
});
log.info("designSingle request入参 ==> " + JSONObject.toJSONString(clone));
@@ -802,7 +817,7 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
maskBase64ToPath(designSingleIncludeLayersDTO, setNull);
// maskBase64ToPath(designSingleIncludeLayersDTO, Boolean.TRUE);
partialDesignBase64ToImage(designSingleIncludeLayersDTO, userId, designSingleIncludeLayersDTO.getIsPreview());
partialDesignBase64ToImage(designSingleIncludeLayersDTO, userId, designSingleIncludeLayersDTO.getIsPreview(), designSingleIncludeLayersDTO.getDesignType());
// 组装入参
DesignPythonObjects objects = pythonService.covertDesignSingleParam(
@@ -820,13 +835,13 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
JSONObject outfit = data.getJSONObject("0");
JSONArray layers = outfit.getJSONArray("layers");
Map<String, List<String>> priorityAndUndividedLayer = setPriorityAndUndividedLayer(layers);
Map<String, List<String>> priorityAndUndividedLayer = setPriorityAndUndividedLayer(layers, designSingleIncludeLayersDTO);
if (!designSingleIncludeLayersDTO.getIsPreview()) {
// 更新及保存图层信息
tDesignPythonOutfitDetails = saveDesignSingleItemDetailAndLayers(objects, design.getId(), designSingleIncludeLayersDTO.getDesignItemId()
, userId, outfit, designSingleIncludeLayersDTO.getTimeZone()
, designSingleIncludeLayersDTO.getDesignSingleItemDTOList()
, priorityAndUndividedLayer, changeModelFlag, modelId, modelType, isSingleCollectionFlag);
, priorityAndUndividedLayer, changeModelFlag, modelId, modelType, isSingleCollectionFlag, designSingleIncludeLayersDTO.getDesignType());
saveCollectionElement(designSingleIncludeLayersDTO);
} else {
@@ -921,12 +936,12 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
item.setMaskUrl(path);
}
}
log.info("服装{} 的maskUrl为null", item.getType());
// log.info("服装{} 的maskUrl为null", item.getType());
}
});
}
private void partialDesignBase64ToImage(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO, Long accountId, boolean preview) {
private void partialDesignBase64ToImage(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO, Long accountId, boolean preview, String designType) {
designSingleIncludeLayersDTO.getDesignSingleItemDTOList().forEach(item -> {
PartialDesignDTO partialDesignDTO = item.getPartialDesign();
if (!Objects.isNull(item.getPartialDesign())
@@ -948,21 +963,79 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
item.getPartialDesign().setPartialDesignMinioPath(newPath);
} else if (Objects.isNull(item.getPartialDesign())
|| StringUtil.isNullOrEmpty(item.getPartialDesign().getPartialDesignMinioPath())) {
if (designType.equals("merge")) {
log.error("merge模式下必须提供partialDesign");
throw new BusinessException("required.partialDesign");
}
item.setPartialDesign(new PartialDesignDTO(null));
}
});
}
private void undividedLayerBase64ToImage(List<DesignSingleItemDTO> designSingleItemDTOS) {
designSingleItemDTOS.forEach(item -> {
if (!StringUtil.isNullOrEmpty(item.getUndividedLayerBase64())) {
if (item.getUndividedLayerBase64().startsWith("data:image") && !item.getUndividedLayerBase64().startsWith("https://")) {
// 将原图地址作为修改后的图片地址,放在不同的桶
String filename = "image/image_" + UUID.randomUUID();
String path = minioUtil.base64UploadToPath(item.getUndividedLayerBase64(), clothingBucket, filename);
log.info("undividedLayer 新的path为{}", path);
if (StringUtil.isNullOrEmpty(path)) {
log.error("undividedLayer图片base64上传失败");
throw new BusinessException("file.upload.fail");
}
item.setUndividedLayerBase64(path);
} else {
item.setUndividedLayerBase64(null);
}
}
if (!StringUtil.isNullOrEmpty(item.getUndividedLayerWithSinglePrintBase64())) {
if (item.getUndividedLayerWithSinglePrintBase64().startsWith("data:image") && !item.getUndividedLayerWithSinglePrintBase64().startsWith("https://")) {
// 将原图地址作为修改后的图片地址,放在不同的桶
String filename = "image/image_" + UUID.randomUUID();
String path = minioUtil.base64UploadToPath(item.getUndividedLayerWithSinglePrintBase64(), clothingBucket, filename);
log.info("getUndividedLayerWithSinglePrint 新的path为{}", path);
if (StringUtil.isNullOrEmpty(path)) {
log.error("getUndividedLayerWithSinglePrintBase64图片base64上传失败");
throw new BusinessException("file.upload.fail");
}
item.setUndividedLayerWithSinglePrintBase64(path);
} else {
item.setUndividedLayerWithSinglePrintBase64(null);
}
}
});
}
@Override
public Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers) {
HashMap<String, List<String>> priorityAndLayer = new HashMap<>();
for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i);
String priority = jsonObject.getString("priority");
String category = jsonObject.getString("image_category").split("_")[0];
if (!category.equals("body") && !priorityAndLayer.containsKey(priority))
priorityAndLayer.put(priority, Arrays.asList(jsonObject.getString("pattern_overall_image_url"), jsonObject.getString("pattern_print_image_url")));
public Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers, DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO) {
String designType = "default";
if (Objects.nonNull(designSingleIncludeLayersDTO)) {
designType = designSingleIncludeLayersDTO.getDesignType();
}
HashMap<String, List<String>> priorityAndLayer = new HashMap<>();
if (designType.equals("default")) {
for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i);
String priority = jsonObject.getString("priority");
String category = jsonObject.getString("image_category").split("_")[0];
if (!category.equals("body") && !priorityAndLayer.containsKey(priority)) {
// pattern_overall_image_url | pattern_print_image_url 这俩字段来源有俩merge模式下来自前端default模式下来自python
priorityAndLayer.put(priority, Arrays.asList(jsonObject.getString("pattern_overall_image_url"), jsonObject.getString("pattern_print_image_url")));
}
}
} else {
if (designSingleIncludeLayersDTO.getIsPreview()) {
// 如果是预览,则不处理、不存储前端传过来的数据
return priorityAndLayer;
}
undividedLayerBase64ToImage(designSingleIncludeLayersDTO.getDesignSingleItemDTOList());
for (DesignSingleItemDTO designSingleItemDTO : designSingleIncludeLayersDTO.getDesignSingleItemDTOList()) {
priorityAndLayer.put(designSingleItemDTO.getPriority().toString(), Arrays.asList(designSingleItemDTO.getUndividedLayerBase64(), designSingleItemDTO.getUndividedLayerWithSinglePrintBase64()));
}
}
return priorityAndLayer;
}
@@ -1119,8 +1192,12 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
designItemClothesDetailVO.setPartialDesign(new PartialDesignDTO(partialDesignMinioPath, preSignedUrl));
if (priorityAndUndividedLayer.containsKey(singleItem.getPriority().toString())) {
designItemClothesDetailVO.setUndividedLayer(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(0), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
designItemClothesDetailVO.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(0))) {
designItemClothesDetailVO.setUndividedLayer(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).getFirst(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
}
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1))) {
designItemClothesDetailVO.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
}
}
body.setLayersObject(layersObject.stream().filter(layers -> layers.getImageCategory().equals("body")).collect(Collectors.toList()));

View File

@@ -715,7 +715,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
(existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
log.info("all typeLayers Map:{}", typeAndUndividedLayer);
Map<String, List<String>> priorityAndUndividedLayer = designItemService.setPriorityAndUndividedLayer(layers);
Map<String, List<String>> priorityAndUndividedLayer = designItemService.setPriorityAndUndividedLayer(layers, null);
for (DesignPythonItem detail : item.getItems()) {
if (null == detail) {
continue;

View File

@@ -1203,6 +1203,9 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
* @param modelName advanced high normal
*/
private HashMap<String, String> chooseModelAndPrompt(GenerateThroughImageTextDTO generateDTO, String modelName) {
if (StringUtil.isNullOrEmpty(modelName)){
throw new BusinessException("system error");
}
HashMap<String, String> modelAndPromptMap = new HashMap<>();
boolean isUseImage;
if (Objects.isNull(generateDTO.getCollectionElementId())
@@ -1218,7 +1221,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
String style = generateDTO.getText().substring(0, firstCommaIndex).trim();
String prompt = generateDTO.getText().substring(firstCommaIndex + 1).trim();
prompt = getPrintboardPrompt(style, prompt);
prompt = getPrintboardPrompt(style, prompt,modelName);
modelAndPromptMap.put(ModelConstants.PROMPT, prompt);
@@ -1239,7 +1242,14 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
} else if (ModelConstants.MOODBOARD.equals(generateDTO.getLevel1Type())) {
String prompt = generateDTO.getText() + "high-resolution, ultra-detailed, realistic textures, perfect anatomy, cinematic lighting, 8k render, editorial photography style";
String userInput = generateDTO.getText();
String systemPrompt = "high-resolution, ultra-detailed, realistic textures, cinematic lighting, 8k render, editorial photography style";
String prompt;
if (userInput == null || userInput.trim().isEmpty()) {
throw new BusinessException("prompt null");
} else {
prompt = "Theme: " + userInput.trim() + "\nRequirement: " + systemPrompt;
}
modelAndPromptMap.put(ModelConstants.PROMPT, prompt);
if (ModelConstants.ADVANCED.equals(modelName)) {
@@ -1560,19 +1570,40 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
private String getPrintboardPrompt(String style, String prompt) {
private String getPrintboardPrompt(String style, String userInput, String modelName) {
String systemPrompt = null;
String prompt;
if ("Painting Style".equals(style)) {
prompt = "1.Requirements: Create a seamless, tiling fashion printboard pattern for apparel. The output must be stylish, contemporary, and suitable for real garment printing. Design pattern, seamless, highly detailed, elegant composition, visually balanced, professional textile print\n" +
"2.Core Theme: " + prompt + "\n" +
"3.Style: painting_style-The painting style refers to the overall approach, techniques, and artistic philosophy used in the artwork. For fashion designs that will be applied to printboards, it is important to define the unique stylistic elements that would translate well into wearable patterns.";
if (ModelConstants.ADVANCED.equals(modelName)) {
systemPrompt = "Tileable seamless pattern, elegant composition, visually balanced, Light watercolor, Giplie Studio (style) pattern with even color field background, high-quality digital print, zero perspective depth, harmonious visual balance, consistent color tone.";
} else if (ModelConstants.HIGH.equals(modelName)) {
systemPrompt = "Design pattern, seamless, highly detailed, elegant composition, visually balanced. \n" +
"Painting style: traditional painting, hand-painted, brush strokes.";
}
} else if ("Illustration Style".equals(style)) {
prompt = "1.Requirements: Create a seamless, tiling fashion printboard pattern for apparel. The output must be stylish, contemporary, and suitable for real garment printing. Design pattern, seamless, highly detailed, elegant composition, visually balanced, professional textile print\n" +
"2.Core Theme: " + prompt + "\n" +
"3.Style: illustration_style-Illustration style focuses on the visual storytellingaspect, often used to depict narratives, characters, or thematic concepts. Forfashion, this style can introduce vivid and artistic interpretations, often aligned with specific themes.";
if (ModelConstants.ADVANCED.equals(modelName)) {
systemPrompt = "Tileable seamless pattern, elegant composition, visually balanced, flat graphic, clean lines pattern with even color field background, high-quality digital print, zero perspective depth, harmonious visual balance, consistent color tone. ";
} else if (ModelConstants.HIGH.equals(modelName)) {
systemPrompt = "Design pattern, seamless, highly detailed, elegant composition, visually balanced. \n" +
"Illustration Style: flat graphic, clean lines.";
}
} else if ("Real Style".equals(style)) {
prompt = "1.Requirements: Create a seamless, tiling fashion printboard pattern for apparel. The output must be stylish, contemporary, and suitable for real garment printing. Design pattern, seamless, highly detailed, elegant composition, visually balanced, professional textile print\n" +
"2.Core Theme: " + prompt + "\n" +
"3.Style: real_style-Real style in fashion is all about authenticity. It featuresnatural fabrics, simple cuts that mirror real life silhouettes, and colors inspired bythe everyday world, exuding a down-to-earth and genuine charm.";
if (ModelConstants.ADVANCED.equals(modelName)) {
systemPrompt = "Tileable seamless pattern, even color field background, photorealistic style pattern, high-quality digital print, zero perspective depth, harmonious visual balance, consistent color tone. ";
} else if (ModelConstants.HIGH.equals(modelName)) {
systemPrompt = "Design pattern, seamless, highly detailed, elegant composition, visually balanced. \n" +
"Flat textile pattern printed directly on fabric surface, no three-dimensional objects, no items placed on cloth. \n" +
"Real style: fabric print, realistic woven/printed pattern, detailed surface pattern only";
}
}else {
throw new BusinessException("style error:"+ style);
}
if (userInput == null || userInput.trim().isEmpty()) {
throw new BusinessException("prompt null");
} else {
prompt = "Theme: " + userInput.trim() + "\nRequirement: " + systemPrompt;
}
return prompt;
}

View File

@@ -79,6 +79,7 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
throw new BusinessException("type.cannot.be.empty");
}
Long accountId = UserContext.getUserHolder().getId();
Account account = accountService.getById(accountId);
// 查动态
if (!StringUtils.isNullOrEmpty(getNotificationDTO.getType()) && getNotificationDTO.getType().equals("newPosted")) {
return getNewPosted(accountId, getNotificationDTO.getPage(), getNotificationDTO.getSize());
@@ -92,6 +93,7 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
if (getNotificationDTO.getType().equals("system")) {
queryWrapper.lambda().eq(Notification::getType, "system")
.gt(Notification::getCreateTime, account.getCreateDate())
.and(wrapper -> wrapper
.isNull(Notification::getReceiverId)
.or()
@@ -103,7 +105,7 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
Page<Notification> notificationPage = baseMapper.selectPage(new Page<>(getNotificationDTO.getPage(), getNotificationDTO.getSize()), queryWrapper);
List<Long> unreadSysNotificationIds = baseMapper.getUnreadSysNotification(accountId);
List<Long> unreadSysNotificationIds = baseMapper.getUnreadSysNotification(accountId, account.getCreateDate());
IPage<NotificationVO> convert = notificationPage.convert(o -> {
NotificationVO notificationVO = CopyUtil.copyObject(o, NotificationVO.class);
Account senderAccount = accountService.getById(notificationVO.getSenderId());
@@ -247,9 +249,11 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
private Long getUnreadSystemNotification(Long receiverId) {
// Long accountId = UserContext.getUserHolder().getId();
Account account = accountService.getById(receiverId);
// 计算总的系统通知数量
QueryWrapper<Notification> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Notification::getType, "system")
.gt(Notification::getCreateTime, account.getCreateDate())
.and(wrapper -> wrapper
.isNull(Notification::getReceiverId)
.or()
@@ -302,6 +306,7 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
// 一键已读
public void setReadAll(String type) {
Long accountId = UserContext.getUserHolder().getId();
Account account = accountService.getById(accountId);
// 指定某个用户的某种类型的数据,将未读数据全部已读
if (!type.equals("system")) {
// 个人消息已读
@@ -309,7 +314,7 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
} else {
// 系统消息已读
// 1、先确定当前用户未读的系统消息有哪些
List<Long> unreadSysNotificationIds = baseMapper.getUnreadSysNotification(accountId);
List<Long> unreadSysNotificationIds = baseMapper.getUnreadSysNotification(accountId, account.getCreateDate());
// 2、将未读的设为已读
if (!unreadSysNotificationIds.isEmpty()) setReadStatusSystem(unreadSysNotificationIds);
}

View File

@@ -1029,14 +1029,16 @@ public class StripeServiceImpl implements StripeService {
}else {
List<SubscriptionInfo> activeSubscriptions = subscriptionInfoList.stream()
.filter(subscription -> "active".equals(subscription.getStatus()))
.collect(Collectors.toList());
.toList();
if (!StringUtil.isNullOrEmpty(type) && type.equals("cancel")){
subscriptionInfo = subscriptionInfoList.get(0);
}else if (activeSubscriptions.isEmpty()){
subscriptionInfo = subscriptionInfoList.getFirst();
} else if (!StringUtil.isNullOrEmpty(type) && type.equals("reminder_expire")) {
subscriptionInfo = subscriptionInfoList.getFirst();
} else if (activeSubscriptions.isEmpty() && type.equals("cancel")){
log.info("不发送邮件,原因:【当前邮件类型:{}, 但是状态为active的subscriptionInfo为空】", type);
return false;
}else {
subscriptionInfo = activeSubscriptions.get(0);
} else {
subscriptionInfo = activeSubscriptions.getFirst();
}
}
}else if (!StringUtil.isNullOrEmpty(orderNo)) {

View File

@@ -11,22 +11,25 @@ import com.ai.da.mapper.primary.SubscriptionPlanMapper;
import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.mapper.primary.entity.Organization;
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
import com.ai.da.model.dto.AddSubAccountDTO;
import com.ai.da.model.dto.SubscriptionPlanDTO;
import com.ai.da.model.dto.SubscriptionPlanPageQuery;
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
import com.ai.da.model.vo.SubscriptionPlanVO;
import com.ai.da.service.AccountService;
import com.ai.da.service.SubscriptionPlanService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Function;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
@@ -36,8 +39,11 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Objects;
import java.util.*;
import static com.ai.da.mapper.primary.entity.Account.SystemRole.EDUCATION_SUB;
import static com.ai.da.mapper.primary.entity.SubscriptionPlan.SubscriptionStatus.ACTIVE;
import static com.ai.da.mapper.primary.entity.SubscriptionPlan.SubscriptionStatus.PENDING;
@Slf4j
@Service
@@ -46,6 +52,8 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
private final AccountMapper accountMapper;
private final AccountService accountService;
private final OrganizationMapper organizationMapper;
private final RedisUtil redisUtil;
@@ -53,9 +61,16 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
// 创建
@Override
public void createPlan(SubscriptionPlanDTO subscriptionPlanDTO) {
// 参数校验
validateCreatePlanParams(subscriptionPlanDTO);
SubscriptionPlan subscriptionPlan = CopyUtil.copyObject(subscriptionPlanDTO, SubscriptionPlan.class);
subscriptionPlan.setStatus(SubscriptionPlan.SubscriptionStatus.PENDING.name());
subscriptionPlan.setName("DEFAULT_NAME");
if (StringUtils.isBlank(subscriptionPlanDTO.getStatus())) {
subscriptionPlan.setStatus(PENDING.name());
}
if (Objects.isNull(subscriptionPlan.getName())) {
subscriptionPlan.setName("DEFAULT_NAME");
}
subscriptionPlan.setCreditUsage(BigDecimal.ZERO);
subscriptionPlan.setCreateTime(LocalDateTime.now());
if (Objects.isNull(subscriptionPlanDTO.getCreditLimit())) {
@@ -63,52 +78,343 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
}
baseMapper.insert(subscriptionPlan);
if (subscriptionPlan.getStatus().equals(SubscriptionPlan.SubscriptionStatus.ACTIVE.name())) {
// 执行一次激活扫描器
activeSubscriptionPlan(subscriptionPlan.getId());
}
}
private void validateCreatePlanParams(SubscriptionPlanDTO subscriptionPlanDTO) {
// 校验组织是否存在
Organization organization = organizationMapper.selectById(subscriptionPlanDTO.getOrganizationId());
if (Objects.isNull(organization)) {
throw new BusinessException("unknown.organization");
}
// 校验结束时间是否大于开始时间
if (subscriptionPlanDTO.getCurrentPeriodEnd() <= subscriptionPlanDTO.getCurrentPeriodStart()) {
throw new BusinessException("end.time.must.be.later.than.the.start.time");
}
// 校验管理员账号id是否存在
Account account = accountMapper.selectById(subscriptionPlanDTO.getAdminAccId());
if (Objects.isNull(account)) {
throw new BusinessException("unknown.administrator.user");
}
if (account.getSystemUser().equals(8) || account.getSystemUser().equals(6)) {
throw new BusinessException("Sub-accounts.cannot.be.admins");
}
// 保证订阅计划绑定的管理员所属组织的唯一性
checkAdminCrossOrg(subscriptionPlanDTO.getAdminAccId(), subscriptionPlanDTO.getOrganizationId());
}
// 判断指定的管理员是否已绑定其他组织的订阅计划
private void checkAdminCrossOrg(Long adminAccId, Long organizationId) {
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(SubscriptionPlan::getAdminAccId, adminAccId)
.ne(SubscriptionPlan::getOrganizationId, organizationId);
Long count = baseMapper.selectCount(queryWrapper);
if (count > 0) {
throw new BusinessException("administrator.user.is.already.bound.to.different.organization");
}
}
// 更新 到期时间、积分总量、已使用积分量
@Transactional(rollbackFor = Exception.class)
@Override
public void updatePlan(UpdateSubscriptionPlanDTO updateDTO) {
if (Objects.isNull(updateDTO.getId())) {
public void updatePlan(UpdateSubscriptionPlanDTO dto) {
if (dto.getId() == null) {
throw new BusinessException("id.cannot.be.empty");
}
SubscriptionPlan subscriptionPlan = baseMapper.selectById(updateDTO.getId());
if (Objects.isNull(subscriptionPlan)) {
SubscriptionPlan plan = baseMapper.selectById(dto.getId());
if (plan == null) {
throw new BusinessException("unknown.subscription.plan");
}
if (Objects.nonNull(updateDTO.getCurrentPeriodStart()) && !updateDTO.getCurrentPeriodStart().equals(subscriptionPlan.getCurrentPeriodStart())) {
subscriptionPlan.setCurrentPeriodStart(updateDTO.getCurrentPeriodStart());
boolean activateToday = false;
activateToday = handlePeriodStart(dto, plan);
handlePeriodEnd(dto, plan);
handleAccountNum(dto, plan);
handleCreditLimit(dto, plan);
handleBasicInfo(dto, plan);
plan.setUpdateTime(LocalDateTime.now());
updateById(plan);
postUpdateProcess(plan, activateToday);
}
// ===================== 字段处理 =====================
/**
* 处理开始时间,返回是否需要当天激活
*/
private boolean handlePeriodStart(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) {
Long newStart = dto.getCurrentPeriodStart();
if (newStart == null || newStart.equals(plan.getCurrentPeriodStart())) {
return false;
}
if (Objects.nonNull(updateDTO.getCurrentPeriodEnd()) && !updateDTO.getCurrentPeriodEnd().equals(subscriptionPlan.getCurrentPeriodEnd())) {
subscriptionPlan.setCurrentPeriodEnd(updateDTO.getCurrentPeriodEnd());
if (ACTIVE.name().equals(plan.getStatus())) {
throw new BusinessException(
"only.subscription.plans.with.a.PENDING.status.can.have.their.start.time.modified"
);
}
if (Objects.nonNull(updateDTO.getAccountNum()) && !updateDTO.getAccountNum().equals(subscriptionPlan.getAccountNum())) {
subscriptionPlan.setAccountNum(updateDTO.getAccountNum());
plan.setCurrentPeriodStart(newStart);
return isToday(newStart);
}
/**
* 处理结束时间(只能延长)
*/
private void handlePeriodEnd(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) {
Long newEnd = dto.getCurrentPeriodEnd();
if (newEnd == null || newEnd.equals(plan.getCurrentPeriodEnd())) {
return;
}
if (Objects.nonNull(updateDTO.getCreditLimit()) && !updateDTO.getCreditLimit().equals(subscriptionPlan.getCreditLimit())) {
subscriptionPlan.setCreditLimit(updateDTO.getCreditLimit());
if (newEnd < plan.getCurrentPeriodEnd()) {
throw new BusinessException(
"the.subscription.end.date.can.be.extended.only.not.reduced"
);
}
if (Objects.nonNull(updateDTO.getAdminAccId()) && !updateDTO.getAdminAccId().equals(subscriptionPlan.getAdminAccId())) {
subscriptionPlan.setAdminAccId(updateDTO.getAdminAccId());
plan.setCurrentPeriodEnd(newEnd);
}
/**
* 处理账号数量
*/
private void handleAccountNum(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) {
Integer newAccountNum = dto.getAccountNum();
if (newAccountNum == null || newAccountNum.equals(plan.getAccountNum())) {
return;
}
subscriptionPlan.setUpdateTime(LocalDateTime.now());
updateById(subscriptionPlan);
if (newAccountNum < plan.getAccountNum()) {
long usedSubAccounts = countExistingSubAccounts(plan.getId());
if (newAccountNum < usedSubAccounts + 1) {
throw new BusinessException(
"total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts"
);
}
}
plan.setAccountNum(newAccountNum);
}
/**
* 处理积分上限
*/
private void handleCreditLimit(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) {
BigDecimal newLimit = dto.getCreditLimit();
if (newLimit == null || newLimit.equals(plan.getCreditLimit())) {
return;
}
if (newLimit.compareTo(plan.getCreditUsage()) < 0) {
throw new BusinessException(
"the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used"
);
}
plan.setCreditLimit(newLimit);
}
/**
* 基础字段
*/
private void handleBasicInfo(UpdateSubscriptionPlanDTO dto, SubscriptionPlan plan) {
if (dto.getAdminAccId() != null
&& !dto.getAdminAccId().equals(plan.getAdminAccId())) {
// 保证订阅计划绑定的管理员所属组织的唯一性
checkAdminCrossOrg(dto.getAdminAccId(), plan.getOrganizationId());
plan.setAdminAccId(dto.getAdminAccId());
}
if (StringUtils.isNotBlank(dto.getName())
&& !dto.getName().equals(plan.getName())) {
plan.setName(dto.getName());
}
if (StringUtils.isNotBlank(dto.getCountryOrRegion())
&& !dto.getCountryOrRegion().equals(plan.getCountryOrRegion())) {
plan.setCountryOrRegion(dto.getCountryOrRegion());
}
}
// ===================== 更新后处理 =====================
private void postUpdateProcess(SubscriptionPlan plan, boolean activateToday) {
if (ACTIVE.name().equals(plan.getStatus())) {
syncAdminAndSubAccounts(plan);
return;
}
if (activateToday) {
activeSubscriptionPlan(plan.getId());
}
}
// ===================== 账号同步 =====================
private void syncAdminAndSubAccounts(SubscriptionPlan plan) {
Account admin = findActiveAdmin(plan);
if (admin != null) {
syncAdminAccount(admin, plan);
}
syncSubAccounts(plan);
}
private Account findActiveAdmin(SubscriptionPlan plan) {
return accountMapper.selectOne(
new QueryWrapper<Account>().lambda()
.eq(Account::getId, plan.getAdminAccId())
.eq(Account::getSubscriptionPlanId, plan.getId())
);
}
private void syncAdminAccount(Account admin, SubscriptionPlan plan) {
long planEndMillis = toMillis(plan.getCurrentPeriodEnd());
if (!Objects.equals(admin.getValidEndTime(), planEndMillis)) {
admin.setValidEndTime(planEndMillis);
}
if (admin.getCreditsUsageLimit().compareTo(plan.getCreditLimit()) != 0) {
// 这里计算修改前后的差值,上限增长,则差为正,上限下降,则差为负;
BigDecimal delta = plan.getCreditLimit()
.subtract(admin.getCreditsUsageLimit());
// 因为管理员的积分中可能包含自己购买的积分所以这里直接将差值添加到管理员的credit中
admin.setCredits(admin.getCredits().add(delta));
admin.setCreditsUsageLimit(plan.getCreditLimit());
}
accountMapper.updateById(admin);
}
private void syncSubAccounts(SubscriptionPlan plan) {
accountMapper.update(
null,
new UpdateWrapper<Account>().lambda()
.set(Account::getValidEndTime, toMillis(plan.getCurrentPeriodEnd()))
.eq(Account::getSubscriptionPlanId, plan.getId())
.eq(Account::getSystemUser, EDUCATION_SUB.getCode())
);
}
// ===================== 辅助方法 =====================
private long countExistingSubAccounts(Long planId) {
return accountMapper.selectCount(
new QueryWrapper<Account>().lambda()
.eq(Account::getSubscriptionPlanId, planId)
.eq(Account::getSystemUser, EDUCATION_SUB.getCode())
);
}
private boolean isToday(Long timestampSeconds) {
return timestampSeconds >= getTodayStartTimestamp()
&& timestampSeconds < getTodayEndTimestamp();
}
private long toMillis(Long seconds) {
return seconds * 1000;
}
@Override
public List<SubscriptionPlan> searchByOrganizationIdAndStatus(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(SubscriptionPlan::getAdminAccId, UserContext.getUserHolder().getId());
if (Objects.nonNull(subscriptionPlanPageQuery.getOrganizationId())){
queryWrapper.lambda().eq(SubscriptionPlan::getOrganizationId, subscriptionPlanPageQuery.getOrganizationId());
} else {
throw new BusinessException("please.specify.the.organizationId");
}
if (!CollectionUtils.isEmpty(subscriptionPlanPageQuery.getStatus())) {
queryWrapper.lambda().in(SubscriptionPlan::getStatus, subscriptionPlanPageQuery.getStatus());
}
if (StringUtils.isNotBlank(subscriptionPlanPageQuery.getCountryOrRegion())){
queryWrapper.lambda().like(SubscriptionPlan::getCountryOrRegion, subscriptionPlanPageQuery.getCountryOrRegion());
}
queryWrapper.lambda().orderByAsc(SubscriptionPlan::getCurrentPeriodStart);
return baseMapper.selectList(queryWrapper);
}
public void updatePlan() {
@Override
public IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
// 1. 参数校验
validatePageQuery(subscriptionPlanPageQuery);
// 2. 构建查询条件不需要包含is_deleted条件因为SQL中已包含
QueryWrapper<SubscriptionPlan> queryWrapper = buildQueryWrapperWithoutDeleted(subscriptionPlanPageQuery);
// 3. 执行自定义的分页查询
Page<SubscriptionPlanVO> page = new Page<>(
subscriptionPlanPageQuery.getPage(),
subscriptionPlanPageQuery.getSize()
);
// 查询数据
return baseMapper.selectWithEmailPage(page, queryWrapper);
}
/**
* 构建查询条件不包含is_deleted条件因为SQL中已包含
*/
private QueryWrapper<SubscriptionPlan> buildQueryWrapperWithoutDeleted(SubscriptionPlanPageQuery query) {
QueryWrapper<SubscriptionPlan> wrapper = new QueryWrapper<>();
// 精确匹配条件 - 需要指定表别名
if (query.getId() != null) {
wrapper.eq("sp.id", query.getId());
}
if (query.getOrganizationId() != null) {
wrapper.eq("sp.organization_id", query.getOrganizationId());
}
if (query.getAdminAccId() != null) {
wrapper.eq("sp.admin_acc_id", query.getAdminAccId());
}
// 时间范围查询
if (StringUtils.isNotBlank(query.getStartTime())) {
LocalDateTime startTime = parseDateTime(query.getStartTime());
wrapper.ge("sp.create_time", startTime);
}
if (StringUtils.isNotBlank(query.getEndTime())) {
LocalDateTime endTime = parseDateTime(query.getEndTime());
wrapper.le("sp.create_time", endTime);
}
// 状态匹配
if (!CollectionUtils.isEmpty(query.getStatus())) {
wrapper.in("sp.status", query.getStatus());
}
if (StringUtils.isNotBlank(query.getCountryOrRegion())) {
wrapper.like("sp.country_or_region", query.getCountryOrRegion());
}
// 按创建时间倒序排序
wrapper.ne("sp.is_deleted", 1)
.orderByDesc("sp.create_time");
return wrapper;
}
// 查找 根据入参提供的参数进行分页查询
@Override
/* @Override
public IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
// 1. 参数校验
validatePageQuery(subscriptionPlanPageQuery);
@@ -121,8 +427,9 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
IPage<SubscriptionPlan> resultPage = baseMapper.selectPage(page, queryWrapper);
// 4. 转换为VO并返回
// todo 用户邮箱
return resultPage.convert((Function<SubscriptionPlan, SubscriptionPlanVO>) plan -> CopyUtil.copyObject(plan, SubscriptionPlanVO.class));
}
}*/
/**
* 参数校验
@@ -275,7 +582,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
}
// 检查是否在有效期内
if (plan.getCurrentPeriodEnd() != null && isExpired(plan.getCurrentPeriodEnd())) {
if (plan.getCurrentPeriodEnd() != null && !isExpired(plan.getCurrentPeriodEnd())) {
throw new BusinessException("valid.subscription.period");
}
}
@@ -295,29 +602,28 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
return currentPeriodEnd < currentTimestamp;
}
// todo 切换管理员的账号
// 定时器更新管理员状态
/* public void updateEduAdminAccount() {
// 1. 扫描所有的订阅计划的开始时间currentPeriodStart
// 2. 当检测到有今天内开始有效的订阅,更新绑定的管理员的账号
// 3. 更新管理的信息包括,①根据订阅结束时间延长管理员账号有效期 ②根据积分上限更新管理员的积分,如果当前管理员正处于一个订阅中,则不做更新,但是允许切换
}*/
public void activeSubscriptionPlan() {
public void activeSubscriptionPlan(Long planId) {
log.info("开始执行订阅计划生效检查...");
// 1. 扫描所有的订阅计划的开始时间currentPeriodStart找出今天开始生效的计划
List<SubscriptionPlan> todayActivePlans = findTodayActivePlans();
// 支持按id激活
List<SubscriptionPlan> todayActivePlans = new ArrayList<>();
if (Objects.nonNull(planId)) {
SubscriptionPlan subscriptionPlan = baseMapper.selectById(planId);
if (Objects.nonNull(subscriptionPlan)){
todayActivePlans.add(subscriptionPlan);
}
} else {
// 1. 扫描所有的订阅计划的开始时间currentPeriodStart找出今天开始生效的计划
todayActivePlans = findTodayActivePlans();
if (CollectionUtils.isEmpty(todayActivePlans)) {
log.info("今日没有需要生效的订阅计划");
return;
if (CollectionUtils.isEmpty(todayActivePlans)) {
log.info("今日没有需要生效的订阅计划");
return;
}
log.info("发现{}个今日生效的订阅计划", todayActivePlans.size());
}
log.info("发现{}个今日生效的订阅计划", todayActivePlans.size());
// 2. 处理每个今天开始生效的订阅计划
for (SubscriptionPlan plan : todayActivePlans) {
@@ -348,7 +654,8 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
// 查询今天开始生效的订阅计划
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("is_deleted", 0) // 未删除
queryWrapper.eq("is_deleted", 0) // 未删除
.in("status", Arrays.asList(PENDING.name(), ACTIVE.name())) // 还未被激活的,或者设置为激活状态但是未被实际激活的
.between("current_period_start", todayStart, todayEnd) // 今天开始生效
.orderByAsc("current_period_start"); // 按开始时间排序
@@ -376,68 +683,128 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
}
// 4. 更新管理员账号信息
updateAdminAccount(adminAccount, plan);
updateAccount(adminAccount, plan, true);
log.info("订阅计划处理完成ID: {}", plan.getId());
}
/**
* 更新管理员账号信息
* 更新账号信息(管理员和子账号)
*/
private void updateAdminAccount(Account account, SubscriptionPlan plan) {
private void updateAccount(Account account, SubscriptionPlan plan, boolean isAdmin) {
// 如果是管理员的切换,先再次记录一下已使用的积分
if (isAdmin && Objects.nonNull(account.getSubscriptionPlanId())) {
SubscriptionPlan currentPlan = baseMapper.selectById(account.getSubscriptionPlanId());
if (currentPlan.getCreditUsage().compareTo(account.getCreditsUsage()) < 0) {
updateSubscriptionPlanUsage(currentPlan, account.getCreditsUsage());
}
}
Account updateAccount = new Account();
updateAccount.setId(account.getId());
// ① 根据订阅结束时间延长管理员账号有效期validEndTime
// Long newValidEndTime = calculateNewValidEndTime(account, plan);
// 通用更新逻辑
updateAccount.setValidEndTime(plan.getCurrentPeriodEnd() * 1000);
// ② 根据积分上限更新管理员的积分credits和身份systemUser
// 暂时保留管理员自己购买的积分
if ((account.getSystemUser() == 5 || account.getSystemUser() == 7)
&& Objects.nonNull(account.getCreditsUsageLimit())) {
BigDecimal leftCredits = account.getCredits().add(account.getCreditsUsage()).subtract(account.getCreditsUsageLimit());
if (leftCredits.compareTo(BigDecimal.ZERO) > 0) {
updateAccount.setCredits(plan.getCreditLimit().add(leftCredits));
} else {
updateAccount.setCredits(plan.getCreditLimit());
}
} else {
updateAccount.setCredits(plan.getCreditLimit().add(account.getCredits()));
}
// 根据组织ID判断用户类型
Integer newSystemUser = determineSystemUserType(plan);
updateAccount.setSystemUser(newSystemUser);
// 更新子账号数量限制
updateAccount.setCreditsUsage(plan.getCreditUsage());
updateAccount.setCreditsUsageLimit(plan.getCreditLimit());
updateAccount.setSubAccountNum(plan.getAccountNum());
// 关联订阅计划ID
updateAccount.setSubscriptionPlanId(plan.getId());
// 更新组织信息
updateAccount.setOrganizationId(plan.getOrganizationId());
Organization organization = organizationMapper.selectById(plan.getOrganizationId());
if (Objects.nonNull(organization)) {
updateAccount.setOrganizationId(plan.getOrganizationId());
updateAccount.setOrganizationName(organization.getName());
} else {
log.error("organization id为 {} 的组织,在表中找不到对应记录", plan.getOrganizationId());
throw new BusinessException("unknown.organization");
}
// 如果是组织管理员设置isAdmin标志
/* if (isOrganizationAdmin(newSystemUser)) {
updateAccount.setIsAdmin(1);
}*/
// 根据用户类型设置不同的业务逻辑
if (isAdmin) {
updateAdminSpecificFields(updateAccount, account, plan);
} else {
updateSubAccountSpecificFields(updateAccount, account, plan);
}
// 执行更新
int rows = accountMapper.updateById(updateAccount);
if (rows > 0) {
log.info("管理员账号更新成功ID: {}, 有效期至: {}, 用户类型: {}, 积分: {}",
log.info("{}更新成功ID: {}, 有效期至: {}, 用户类型: {}, 积分: {}",
isAdmin ? "管理员账号" : "子账号",
account.getId(),
formatTimestamp(plan.getCurrentPeriodEnd()),
newSystemUser,
plan.getCreditLimit());
updateAccount.getSystemUser(),
updateAccount.getCredits());
} else {
log.error("管理员账号更新失败ID: {}", account.getId());
log.error("{}更新失败ID: {}", isAdmin ? "管理员账号" : "子账号", account.getId());
}
// 仅子账号需要更新订阅计划的使用量
if (!isAdmin) {
updateSubscriptionPlanUsage(plan, plan.getCreditUsage().add(updateAccount.getCreditsUsageLimit()));
}
}
/**
* 更新管理员特有的字段
*/
private void updateAdminSpecificFields(Account updateAccount, Account originalAccount, SubscriptionPlan plan) {
// 保留管理员自己购买的积分
if ((originalAccount.getSystemUser() == 5 || originalAccount.getSystemUser() == 7)
&& Objects.nonNull(originalAccount.getCreditsUsageLimit())) {
BigDecimal leftCredits = originalAccount.getCredits()
.add(originalAccount.getCreditsUsage())
.subtract(originalAccount.getCreditsUsageLimit());
updateAccount.setCredits(leftCredits.compareTo(BigDecimal.ZERO) > 0
? plan.getCreditLimit().subtract(plan.getCreditUsage()).add(leftCredits)
: plan.getCreditLimit().subtract(plan.getCreditUsage()));
} else {
updateAccount.setCredits(plan.getCreditLimit().subtract(plan.getCreditUsage()).add(originalAccount.getCredits()));
}
// 设置用户类型和其他字段
updateAccount.setSystemUser(determineSystemUserType(plan, true));
updateAccount.setCreditsUsage(plan.getCreditUsage());
updateAccount.setCreditsUsageLimit(plan.getCreditLimit());
updateAccount.setSubAccountNum(plan.getAccountNum());
updateAccount.setId(originalAccount.getId());
}
/**
* 更新子账号特有的字段
*/
private void updateSubAccountSpecificFields(Account updateAccount, Account originalAccount, SubscriptionPlan plan) {
// 计算可分配的积分
BigDecimal remainingCredits = plan.getCreditLimit().subtract(plan.getCreditUsage());
BigDecimal allocatedCredits = calculateAllocatedCredits(remainingCredits);
updateAccount.setParentId(plan.getAdminAccId());
updateAccount.setCreditsUsageLimit(allocatedCredits);
updateAccount.setCredits(allocatedCredits.add(originalAccount.getCredits()));
updateAccount.setCreditsUsage(BigDecimal.ZERO);
updateAccount.setSystemUser(determineSystemUserType(plan, false));
}
/**
* 计算分配给子账号的积分
*/
private BigDecimal calculateAllocatedCredits(BigDecimal remainingCredits) {
if (remainingCredits.compareTo(new BigDecimal(1000)) > 0) {
return new BigDecimal(1000);
} else if (remainingCredits.compareTo(new BigDecimal(100)) > 0) {
return new BigDecimal(100);
} else if (remainingCredits.compareTo(BigDecimal.ZERO) > 0) {
return remainingCredits;
} else {
return BigDecimal.ZERO;
}
}
/**
* 更新订阅计划使用量(仅子账号)
*/
private void updateSubscriptionPlanUsage(SubscriptionPlan plan, BigDecimal newCreditsUsage) {
plan.setCreditUsage(newCreditsUsage);
plan.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(plan);
}
/**
@@ -463,7 +830,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
/**
* 根据计划确定用户类型
*/
private Integer determineSystemUserType(SubscriptionPlan plan) {
private Integer determineSystemUserType(SubscriptionPlan plan, boolean isAdmin) {
// 根据组织ID, 去organization表中查询判断是学校还是企业
Long orgId = plan.getOrganizationId();
@@ -473,16 +840,18 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
log.error("未知组织id: {}", orgId);
throw new BusinessException("unknown.organization");
}
if (organization.getType().equals("Enterprise")) {
return 5; // 学校管理员
if (organization.getType().equals("Enterprise") && isAdmin) {
return 5; // 企业管理员
} else if (organization.getType().equals("Enterprise")) {
return 6; // 企业子账号
} else if (organization.getType().equals("Education") && isAdmin) {
return 7; // 学校管理员
} else if (organization.getType().equals("Education")) {
return 7; // 企业管理员
return 8; // 学校子账号
} else {
log.error("组织id未知组织类型");
throw new BusinessException("unknown.type");
}
// 默认为教育管理员
return 7;
}
/**
@@ -563,6 +932,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("is_deleted", 0)
.ne("status", "EXPIRED")
.between("current_period_end", yesterday, now) // 过去24小时内到期
.orderByAsc("current_period_end");
@@ -600,6 +970,8 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
// 5. 管理员当前未使用这个订阅,只处理订阅关系状态
log.info("订阅{}已过期,但管理员{}未激活此订阅", plan.getId(), adminAccount.getId());
}
// 6. 处理该订阅下的子账号
processChildAccountsForExpiredSubscription(plan);
}
/**
@@ -636,7 +1008,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
adminAccount.getId(), otherActiveSubscriptions.size(), earliestActivePlan.getId());
// 3. 切换到开始最早的订阅
updateAdminAccount(adminAccount, earliestActivePlan);
updateAccount(adminAccount, earliestActivePlan, true);
// 4. 修改订阅状态
earliestActivePlan.setStatus(SubscriptionPlan.SubscriptionStatus.ACTIVE.name());
@@ -648,14 +1020,10 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
log.info("管理员{}没有其他活跃订阅,降级为游客", adminAccount.getId());
// downgradeAccountToVisitor(adminAccount);
// todo toVisitor 需要更新其他字段如subscriptionPlanId, parentId
accountMapper.toVisitor(adminAccount.getId());
log.info("管理员账号{}已降级为游客", adminAccount.getId());
}
// 7. 处理该订阅下的子账号
processChildAccountsForExpiredSubscription(expiringPlan);
}
/**
@@ -703,40 +1071,138 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
}
/**
* 管理员切换当前管理的订阅
* 切换订阅计划(管理员和子账号)
* @param subscriptionPlanId 订阅计划ID
* @param targetAccId 目标账号ID管理员或子账号ID
* @param isAdmin 是否是管理员切换
*/
public void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId) {
// 1. 权限校验
Long accountId = UserContext.getUserHolder().getId();
public void switchSubscriptionPlan(Long subscriptionPlanId, Long targetAccId, boolean isAdmin) {
// 1. 获取当前用户和基础数据校验
Long currentUserId = UserContext.getUserHolder().getId();
// 获取订阅计划
SubscriptionPlan subscriptionPlan = baseMapper.selectById(subscriptionPlanId);
if (Objects.isNull(subscriptionPlan)) {
throw new BusinessException("unknown.subscription.plan");
}
if (subscriptionPlan.getStatus().equals(SubscriptionPlan.SubscriptionStatus.EXPIRED.name())) {
throw new BusinessException("subscription.has.expired");
}
if (!accountId.equals(subscriptionPlan.getAdminAccId()) && !accountId.equals(87L)) {
throw new BusinessException("have.no.permission");
validateSubscriptionPlan(subscriptionPlan);
// 权限校验
validatePermission(currentUserId, subscriptionPlan);
// 确定目标账号ID
Long finalTargetAccId = determineTargetAccountId(currentUserId, targetAccId, isAdmin);
// 获取目标账号
Account targetAccount = accountMapper.selectById(finalTargetAccId);
if (Objects.isNull(targetAccount)) {
throw new BusinessException(isAdmin ? "unknown.administrator.user" : "unknown.sub.account");
}
// 2. 更新管理员积分
if (!accountId.equals(87L)) {
adminAccId = accountId;
// 管理员权限校验
if (isAdmin) {
validateAdminPermission(finalTargetAccId, subscriptionPlan);
} else {
validateSubAccountPermission(currentUserId, subscriptionPlan, targetAccount);
}
Account account = accountMapper.selectById(adminAccId);
if (Objects.isNull(account)) {
throw new BusinessException("unknown.administrator.user");
}
if (!adminAccId.equals(subscriptionPlan.getAdminAccId())) {
log.info("用户:{} 没有权限管理订阅:{}", adminAccId, subscriptionPlanId);
throw new BusinessException("no.permission.manage.subscription", ResultEnum.PROMPT.getCode());
}
if (account.getSubscriptionPlanId().equals(subscriptionPlanId)) {
log.info("用户:{} 当前正在管理订阅 {}", adminAccId, subscriptionPlanId);
// 检查是否已经是当前管理的订阅
if (targetAccount.getSubscriptionPlanId() != null &&
targetAccount.getSubscriptionPlanId().equals(subscriptionPlanId)) {
log.info("用户:{} 当前正在订阅 {} 中", finalTargetAccId, subscriptionPlanId);
return;
}
updateAdminAccount(account, subscriptionPlan);
// 2. 执行切换操作
if (isAdmin) {
// 管理员直接更新
updateAccount(targetAccount, subscriptionPlan, true);
} else {
// 子账号需要先从原订阅移除
accountService.removeSubAccount(new AddSubAccountDTO(Collections.singletonList(finalTargetAccId)), currentUserId);
updateAccount(accountMapper.selectById(finalTargetAccId), subscriptionPlan, false);
}
}
/**
* 管理员切换当前管理的订阅(保持原有接口)
*/
public void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId) {
switchSubscriptionPlan(subscriptionPlanId, adminAccId, true);
}
/**
* 子账号切换订阅计划(保持原有接口)
*/
public void switchSubAccSubscriptionPlan(Long subscriptionPlanId, Long subAccId) {
switchSubscriptionPlan(subscriptionPlanId, subAccId, false);
}
/**
* 校验订阅计划
*/
private void validateSubscriptionPlan(SubscriptionPlan subscriptionPlan) {
if (Objects.isNull(subscriptionPlan)) {
throw new BusinessException("unknown.subscription.plan");
}
long now = System.currentTimeMillis() / 1000;
if (subscriptionPlan.getStatus().equals(SubscriptionPlan.SubscriptionStatus.EXPIRED.name())
|| subscriptionPlan.getCurrentPeriodEnd() <= now) {
throw new BusinessException("subscription.has.expired");
}
}
/**
* 权限校验
*/
private void validatePermission(Long userId, SubscriptionPlan subscriptionPlan) {
// 87L是特殊权限用户
if (!userId.equals(subscriptionPlan.getAdminAccId()) && !userId.equals(87L)) {
throw new BusinessException("have.no.permission");
}
}
/**
* 确定目标账号ID
*/
private Long determineTargetAccountId(Long currentUserId, Long targetAccId, boolean isAdmin) {
// 如果是管理员切换且当前用户不是特殊权限用户则使用当前用户ID
if (isAdmin && !currentUserId.equals(87L)) {
return currentUserId;
}
return targetAccId;
}
/**
* 管理员权限校验
*/
private void validateAdminPermission(Long adminAccId, SubscriptionPlan subscriptionPlan) {
if (!adminAccId.equals(subscriptionPlan.getAdminAccId())) {
log.info("用户:{} 没有权限管理订阅:{}", adminAccId, subscriptionPlan.getId());
throw new BusinessException("no.permission.manage.subscription", ResultEnum.PROMPT.getCode());
}
}
/**
* 子账号权限校验
*/
private void validateSubAccountPermission(Long adminAccId, SubscriptionPlan subscriptionPlan, Account subAccount) {
// 验证管理员是否有权限
validateAdminPermission(adminAccId, subscriptionPlan);
Account adminAcc = accountMapper.selectById(adminAccId);
if (!subAccount.getSubscriptionPlanId().equals(adminAcc.getSubscriptionPlanId())) {
throw new BusinessException("switch.failed.sub-account.not.under.your.active.subscription");
}
// 校验子账号总数是否达上限
QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().eq(Account::getOrganizationId, subscriptionPlan.getOrganizationId())
.eq(Account::getSubscriptionPlanId, subscriptionPlan.getId());
List<Account> accounts = accountMapper.selectList(qw);
if (accounts.size() >= subscriptionPlan.getAccountNum()) {
throw new BusinessException("Error: Sub-account quota reached (Max: " +
adminAcc.getSubAccountNum() + "). Upgrade to create more.");
}
}
}

View File

@@ -1461,10 +1461,12 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
if (StringUtil.isNullOrEmpty(fluxResult)) {
toProductImageResult.setStatus("Fail");
toProductImageResultMapper.updateById(toProductImageResult);
sortRank(toProductImageResult);
results.add(new MagicToolResultVO(taskId, "Fail"));
} else if (fluxResult.equals("Fail") || fluxResult.equals("Pending")) {
toProductImageResult.setStatus(fluxResult);
toProductImageResultMapper.updateById(toProductImageResult);
sortRank(toProductImageResult);
results.add(new MagicToolResultVO(taskId, fluxResult));
} else {
results.add(processFluxResult(fluxResult, toProductImageResult, taskId, toProductImageRecord.getPrompt()));
@@ -1515,6 +1517,28 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
return results;
}
/*
重排Relight失败sort
*/
private void sortRank(ToProductImageResult toProductImageResult) {
LambdaQueryWrapper<CollectionSort> collectionSortLambdaQueryWrapper = new LambdaQueryWrapper<>();
collectionSortLambdaQueryWrapper.eq(CollectionSort::getRelationId, toProductImageResult.getId()).orderByDesc(CollectionSort::getCreateTime).last("LIMIT 1");
List<CollectionSort> collectionSorts = collectionSortMapper.selectList(collectionSortLambdaQueryWrapper);
if (!collectionSorts.isEmpty()) {
collectionSortMapper.deleteById(collectionSorts.get(0));
List<CollectionSort> collectionSorts1 = collectionSortMapper.selectList(new LambdaQueryWrapper<CollectionSort>().eq(CollectionSort::getParentId, collectionSorts.get(0).getParentId()).gt(CollectionSort::getSort, collectionSorts.get(0).getSort()));
if (!collectionSorts1.isEmpty()) {
for (CollectionSort collectionSort : collectionSorts1) {
collectionSort.setSort(collectionSort.getSort() - 1);
collectionSortMapper.updateById(collectionSort);
}
}
}
}
public static String convert(InputStream inputStream) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
return reader.lines().collect(Collectors.joining("\n"));

View File

@@ -158,4 +158,4 @@ google.client.id=157095842121-kdd1fdf8m8nudvj9sprstb2k2prnf9e4.apps.googleuserco
#google.client.secret=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
google.client.secret=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
google.redirect.uri=https://develop.api.aida.com.hk/api/third/party/auth/google_callback
design.callback.url=https://develop,api.aida.com.hk/api/third/party/receiveDesignResults
design.callback.url=https://develop.api.aida.com.hk/api/third/party/receiveDesignResults

View File

@@ -22,7 +22,7 @@ spring.security.jwtExpiration=604800000
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\
/api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\
/api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**,/api/credits/**,/api/inquiry/**,/api/tasks/**,/api/python/prepareForSR,/api/alipay-hk/**,/api/portfolio/**,\
/api/stripe/**,/api/message/**,/api/tags/**,/notification/**,/api/affiliate/**,/api/project/**,/api/llm/**
/api/stripe/**,/api/message/**,/api/tags/**,/notification/**,/api/affiliate/**,/api/project/**,/api/llm/**, /api/subscription_plan/**
spring.security.authApi=/auth/login
@@ -71,15 +71,15 @@ spring.rabbitmq.username=rabbit
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/
spring.redis.host=172.31.11.32
#spring.redis.host=18.167.251.121
spring.redis.port=6379
spring.redis.database=2
spring.redis.password=Aidlab
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=5
spring.data.redis.host=172.31.11.32
#spring.data.redis.host=18.167.251.121
spring.data.redis.port=6379
spring.data.redis.database=2
spring.data.redis.password=Aidlab
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.max-wait=5
redis.key.orderForGenerate=OrderForGenerate
redis.key.generateCancelSet=GenerateCancelSet
@@ -155,4 +155,5 @@ FREEPIK_API_KEY=FPSX94e5917d376a4facb87dabbaa0319c72
google.client.id=29310152396-nnsd3h533fld665oguu8ovrt1nukmt46.apps.googleusercontent.com
google.client.secret=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
google.redirect.uri=https://www.api.aida.com.hk/api/third/party/auth/google_callback
google.redirect.uri=https://www.api.aida.com.hk/api/third/party/auth/google_callback
design.callback.url=https://api.aida.com.hk/api/third/party/receiveDesignResults

View File

@@ -60,7 +60,8 @@
SELECT system_notification_id
FROM `t_sys_notification_read_status`
WHERE account_id = #{accountId}
)
)
AND create_time > #{createTime}
</select>

View File

@@ -192,7 +192,7 @@ unknown.mode=unknown mode
unknown.subscription.plan=unknown subscription plan
unknown.subscription.status=Unknown subscription status.
subscription.has.expired=Switch failed. The subscription has expired.
unknown.administrator.user=Switch failed. Unknown administrator user.
unknown.administrator.user=Operation failed. Unknown administrator user.
no.permission.manage.subscription=Switch failed. You do not have permission to manage this subscription.
unknown.organization=Unknown organization.
valid.subscription.period=The plan is still within its valid period. Please delete it after it expires.
@@ -205,6 +205,16 @@ invalid.time.format=Invalid time format. Please use the yyyy-MM-dd HH:mm:ss form
the.start.time.cannot.be.later.than.the.end.time=The start time cannot be later than the end time.
page.size.limit=The number of items per page must be between 1 and 100.
page.num.limit=The page number must be greater than 0.
end.time.must.be.later.than.the.start.time=The subscription end time must be later than the start time.
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.
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.
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.
administrator.user.is.already.bound.to.different.organization=This administrator user is already bound to a subscription plan of a different organization.
required.partialDesign='partialDesign' (base64 or path) is required when updating an individual outfit.
# 可能会报异常
# Informative:
@@ -221,6 +231,7 @@ the.workspaceName.already.exists=A workspace with this name already exists.
unable.to.delete.the.workspace.you.are.currently.using=The workspace you are currently using cannot be deleted. Please select another workspace before trying to delete.
classificationName.already.exists=The label name you've entered already exists. Please enter a different label name to avoid duplication.
account.expired=Your subscription has expired, and your account has been reset to a visitor account. Please log in again from the [Individual] entry. If you have any questions, please contact us at info@code-create.com.hk
relate.to.any.subscription=Your administrator account is not currently linked to any subscription. If you have any questions, please contact us at info@code-create.com.hk
# Warnings:
# 用来提醒用户可能会导致不良后果的操作,但不一定是错误。用户需要认真考虑是否继续当前操作。

View File

@@ -188,7 +188,7 @@ unknown.mode=未知模式
unknown.subscription.plan=未知订阅计划
unknown.subscription.status=未知订阅状态
subscription.has.expired=切换失败,订阅已过期
unknown.administrator.user=切换失败,未知管理员用户
unknown.administrator.user=操作失败,未知管理员用户
no.permission.manage.subscription=切换失败,您没有权限管理该订阅
unknown.organization=未知组织
valid.subscription.period=计划仍在有效期内,请到期后再删除
@@ -201,6 +201,16 @@ invalid.time.format=时间格式错误请使用yyyy-MM-dd HH:mm:ss格式
the.start.time.cannot.be.later.than.the.end.time=开始时间不能晚于结束时间
page.size.limit=每页数量必须在1-100之间
page.num.limit=页码必须大于0
end.time.must.be.later.than.the.start.time=订阅结束时间必须晚于开始时间
please.specify.the.organizationId=请指定organizationId
switch.failed.sub-account.not.under.your.active.subscription=切换失败,该子账号不属于您当前管理的订阅计划
Sub-accounts.cannot.be.admins=在订阅中的子账号不能被指定为管理员
only.subscription.plans.with.a.PENDING.status.can.have.their.start.time.modified=只有PENDING状态的订阅计划可以修改订阅开始时间
the.subscription.end.date.can.be.extended.only.not.reduced=订阅的到期时间不能缩短,只能延长
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=设置的积分上限不能低于已使用的积分量
administrator.user.is.already.bound.to.different.organization=该管理员用户已与其他组织的订阅计划绑定
required.partialDesign=修改单套搭配必须提供'partialDesign'(base64 或 path)
# 可能会报异常
# Informative:
@@ -239,6 +249,7 @@ chat-bot.interface.exception=聊天机器人接口出现错误。(请稍后再
compose-layer.interface.exception=图层合并时出现问题。请稍后再试。如果问题持续请联系我们的help@aida.com.hk
cloth-classification.interface.exception=获取服装类别时出现问题。请稍后再试。如果问题持续请联系我们的help@aida.com.hk
account.expired=您的订阅已过期,账号已被重置为访客身份,请从【个人账号】入口重新登录。如有疑问,请联系 info@code-create.com.hk。
relate.to.any.subscription=您的管理员账号暂未关联任何订阅,如有疑问,请联系我们 info@code-create.com.hk
# 多语言返回
OVERALL=整体