67 Commits

Author SHA1 Message Date
2e7004f9dc 取消监听所有消息队列 2026-02-05 15:16:04 +08:00
497421e7fe TO DEV 2026-01-27 10:15:36 +08:00
891527426c Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge
# Conflicts:
#	src/main/java/com/ai/da/service/impl/DesignServiceImpl.java
2026-01-26 14:49:38 +08:00
litianxiang
8f0d0953b2 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-01-26 11:15:38 +08:00
f5c3621a5d bugfix: design like 2026-01-23 22:45:40 +08:00
litianxiang
9a1a0045e0 fix:like报错 2026-01-23 22:40:30 +08:00
6223c8e994 brandDNA 2026-01-23 22:25:01 +08:00
67bbee49fd Merge branch 'dev/3.1_release_merge' into release/3.1 2026-01-23 21:20:26 +08:00
ad62ceb32a Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-23 21:02:04 +08:00
082afe9e94 gradiant 置空 2026-01-23 20:57:48 +08:00
49288c3a31 TO Prod 2026-01-23 16:10:55 +08:00
81624e36db Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-01-23 15:12:15 +08:00
a526b122d1 Merge branch 'release/3.1' into dev/3.1_release_merge 2026-01-23 15:11:45 +08:00
litianxiang
d882b2e817 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-23 15:05:22 +08:00
litianxiang
ebf6427d42 fix:用户特征存入逻辑错误 2026-01-23 15:04:59 +08:00
77fe03d361 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-23 11:46:31 +08:00
7a44d67dbf BUGFIX: 系统消息发布 广播时消息数量错误 2026-01-23 11:46:08 +08:00
55ce2c6c7e Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-23 10:54:35 +08:00
a426caaca3 BUGFIX: 系统消息发布 2026-01-23 10:54:03 +08:00
7cb7ce2836 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-22 16:56:11 +08:00
8e075f1da4 BUGFIX: 通过hsv批量获取潘通信息,替换rgb 2026-01-22 16:55:00 +08:00
litianxiang
0f0fde2a3e Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-22 14:28:37 +08:00
litianxiang
8c6389a1f6 删除不用的字段 2026-01-22 14:28:10 +08:00
652f82b6a4 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-22 13:56:44 +08:00
7ca2528dcf BUGFIX: design后未存储undivided layers 2026-01-22 13:56:07 +08:00
litianxiang
a7800913d2 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-22 13:51:42 +08:00
litianxiang
1eaec64ff4 fix:GlobalAward读取配置错误 2026-01-22 13:51:13 +08:00
e603952332 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-22 11:37:25 +08:00
2bc8b8ef96 BUGFIX: single design的渐变色未存储 2026-01-22 11:36:43 +08:00
0ce968b919 BUGFIX: 用户登录时的有效期验证异常抛出导致事务回滚,用户信息修改失败 2026-01-22 10:37:23 +08:00
litianxiang
dfc9ae4db2 GlobalAward站内信url修改 2026-01-21 16:50:01 +08:00
litianxiang
a3505c6d95 GlobalAward站内信url修改 2026-01-21 15:09:45 +08:00
litianxiang
6db0afd515 GlobalAward保存成功发送站内信,根据url可跳转且召回已填写资料 2026-01-21 14:59:41 +08:00
litianxiang
b1e6183dd1 GlobalAward接口token验证,id更换为uuid 2026-01-21 14:34:43 +08:00
30d08356c0 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-21 14:14:17 +08:00
64cc29f456 TASK:Global Award邮箱验证 2026-01-21 14:13:33 +08:00
litianxiang
2b3e12a11c GlobalAward MINIO配置 2026-01-21 11:38:38 +08:00
litianxiang
d4a4724f61 GlobalAward拦截器配置 2026-01-21 10:35:22 +08:00
litianxiang
ba6e2bd24c GlobalAward拦截器配置 2026-01-21 10:21:17 +08:00
litianxiang
a38895b028 GlobalAward拦截器配置 2026-01-21 10:13:11 +08:00
litianxiang
69a95e66ca GlobalAward上传文件 2026-01-20 16:37:46 +08:00
litianxiang
40518cab37 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev-ltx
# Conflicts:
#	src/main/java/com/ai/da/controller/GlobalAwardController.java
#	src/main/java/com/ai/da/model/dto/ContestantDTO.java
#	src/main/resources/application-dev.properties
#	src/main/resources/application-prod.properties
2026-01-20 16:20:41 +08:00
litianxiang
46d61cb73f GlobalAward上传文件 2026-01-20 15:58:27 +08:00
08f20fd1fe Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-20 13:24:49 +08:00
d7edc166b3 TASK:Global Award邮箱验证 2026-01-20 13:14:50 +08:00
litianxiang
79ad02f66b fix:style为all时,like报错 2026-01-19 16:50:14 +08:00
litianxiang
5e261b55c7 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-01-19 16:41:02 +08:00
litianxiang
bc92fcbaf4 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-19 16:40:58 +08:00
litianxiang
c6aec917c2 fix:style为all时,like报错 2026-01-19 16:40:28 +08:00
6bc500e78f Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-16 17:17:25 +08:00
4c43b98c02 TASK:DB中PartialDesign为空时,取undivided_layer作为merge_image_path 2026-01-16 17:17:04 +08:00
litianxiang
5bae785a9f Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-16 16:37:49 +08:00
litianxiang
7b619aa4cb GlobalAward首次提交 2026-01-16 16:37:03 +08:00
c93ad6daa9 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-16 16:24:38 +08:00
0047be7a03 BUGFIX:PartialDesign传空时,先从数据库获取原数据 2026-01-16 16:23:56 +08:00
4ef209cfd4 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-16 14:28:27 +08:00
a19751b4b7 BUGFIX:designType参数校验;print数据验空 2026-01-16 14:28:04 +08:00
litianxiang
bb0e5a4263 Merge remote-tracking branch 'origin/dev-ltx' into dev/3.1_release_merge 2026-01-16 11:04:52 +08:00
litianxiang
9e9df5367d fix:接口SegAnything返回值处理 2026-01-16 11:04:18 +08:00
ba8a2c52de Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-01-15 17:36:37 +08:00
39d8c7efcf TASK:取消存储/返回UndividedLayer和UndividedLayerWithSinglePrint字段 2026-01-15 17:35:55 +08:00
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
litianxiang
3f5ce6e0e7 接口SegAnything返回值处理 2026-01-15 17:17:08 +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
72ad977dcb BUGFIX: 获取近期新用户图表数据允许userType为null 2026-01-12 11:55:43 +08:00
42 changed files with 6654 additions and 4683 deletions

View File

@@ -559,7 +559,7 @@ public class GenerateConsumer {
log.info("============ProcessPoseTransformResult End listening=========="); log.info("============ProcessPoseTransformResult End listening==========");
} }
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}") /*@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler @RabbitHandler
public void generateConsumer1(Message msg, Channel channel) { public void generateConsumer1(Message msg, Channel channel) {
generate(msg, channel, "consumer 1"); generate(msg, channel, "consumer 1");
@@ -635,7 +635,7 @@ public class GenerateConsumer {
@RabbitHandler @RabbitHandler
public void getPoseTransformationResult(Message msg, Channel channel) { public void getPoseTransformationResult(Message msg, Channel channel) {
processPoseTransformResult(msg, channel); processPoseTransformResult(msg, channel);
} }*/
// @RabbitListener(queues = "#{rabbitMQProperties.queues.designBatch}") // @RabbitListener(queues = "#{rabbitMQProperties.queues.designBatch}")
// @RabbitHandler // @RabbitHandler
// public void getDesignBatchResult(Message msg, Channel channel) { // public void getDesignBatchResult(Message msg, Channel channel) {

View File

@@ -33,7 +33,11 @@ public enum AuthenticationOperationTypeEnum {
*/ */
UPDATE_USERINFO, UPDATE_USERINFO,
REGISTER; REGISTER,
/**
* Global_Award 活动验证
*/
GLOBAL_AWARD;
public static AuthenticationOperationTypeEnum of(String name) { public static AuthenticationOperationTypeEnum of(String name) {
return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null); return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null);

View File

@@ -59,7 +59,11 @@ public class AuthenticationFilter extends OncePerRequestFilter {
"/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify", "/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify",
"/notification","/api/account/activateNewEmail","/api/third/party/auth/google_callback","/api/third/party/parseGoogleCredential","/api/third/party/receiveDesignResults","/api/third/party/parseWeChatCode","/api/third/party/receiveDesignParams" "/notification","/api/account/activateNewEmail","/api/third/party/auth/google_callback","/api/third/party/parseGoogleCredential","/api/third/party/receiveDesignResults","/api/third/party/parseWeChatCode","/api/third/party/receiveDesignParams"
, "/api/account/schoolLogin", "/api/account/enterpriseLogin", "/api/account/organizationNameSearch", , "/api/account/schoolLogin", "/api/account/enterpriseLogin", "/api/account/organizationNameSearch",
"/api/llm/stream" "/api/llm/stream",
//GlobalAwardController
"/api/global-award/uploads/pdf/init", "/api/global-award/uploads/pdf/chunk", "/api/global-award/uploads/pdf/complete", "/api/global-award/uploads/pdf/status",
"/api/global-award/uploads/video/init", "/api/global-award/uploads/video/chunk", "/api/global-award/uploads/video/complete", "/api/global-award/uploads/video/status",
"/api/global-award/contestants/save", "/api/global-award/contestants/by-email", "/api/global-award/checkEmail", "/api/global-award/checkCode"
); );
@Override @Override

View File

@@ -40,8 +40,8 @@ public class AccountTask {
accountService.extendValidityForCC(); accountService.extendValidityForCC();
} }
// 每天凌晨0点执行一次 // 每天凌晨0点执行一次 目前已没有角色类型为4的用户
@Scheduled(cron = "0 0 0 * * ?") /*@Scheduled(cron = "0 0 0 * * ?")
public void cancelActivityBenefits() { public void cancelActivityBenefits() {
// 1、查询当前所有参与了活动且过期的用户 // 1、查询当前所有参与了活动且过期的用户
List<Account> accountList = accountService.getExpiredUserBySystemUser(4); List<Account> accountList = accountService.getExpiredUserBySystemUser(4);
@@ -51,7 +51,7 @@ public class AccountTask {
log.info("参与活动的用户{} : {} 于 {} 账号有效期到期,置为游客", account.getId(), account.getUserEmail(), account.getValidEndTime()); log.info("参与活动的用户{} : {} 于 {} 账号有效期到期,置为游客", account.getId(), account.getUserEmail(), account.getValidEndTime());
accountService.toVisitor(account); accountService.toVisitor(account);
} }
} }*/
// 每天检测正式用户到期情况每天凌晨0点执行 // 每天检测正式用户到期情况每天凌晨0点执行
@Scheduled(cron = "0 0 0 * * ?") @Scheduled(cron = "0 0 0 * * ?")

View File

@@ -41,6 +41,13 @@ public class MinioUtil {
@Autowired @Autowired
private MinioClient minioClient; private MinioClient minioClient;
/**
* 获取MinIO客户端实例
*/
public MinioClient getMinioClient() {
return minioClient;
}
/** /**
* description: 判断bucket是否存在不存在则创建 * description: 判断bucket是否存在不存在则创建
* *

View File

@@ -103,7 +103,7 @@ public class ConvenientInquiryController {
@GetMapping("/recentNewUserChart") @GetMapping("/recentNewUserChart")
public Response<Map<String, Object>> recentNewUserChart(@Parameter(description = "startTime") @RequestParam @Nullable String startTime, public Response<Map<String, Object>> recentNewUserChart(@Parameter(description = "startTime") @RequestParam @Nullable String startTime,
@Parameter(description = "endTime") @RequestParam @Nullable String endTime, @Parameter(description = "endTime") @RequestParam @Nullable String endTime,
@Parameter(description = "userType") @RequestParam Integer userType) { @Parameter(description = "userType") @RequestParam @Nullable Integer userType) {
return Response.success(convenientInquiryService.recentNewUserChart(startTime, endTime, userType)); return Response.success(convenientInquiryService.recentNewUserChart(startTime, endTime, userType));
} }

View File

@@ -0,0 +1,168 @@
package com.ai.da.controller;
import com.ai.da.common.response.Response;
import com.ai.da.model.dto.*;
import com.ai.da.model.dto.ContestantDTO;
import com.ai.da.model.vo.CheckOTPVO;
import com.ai.da.service.GlobalAwardService;
import com.ai.da.service.upload.UploadService;
import com.ai.da.service.upload.UploadTask;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
@RestController
@RequestMapping("/api/global-award")
@Api(tags = "全球奖项API", description = "全球奖项大赛管理和文件上传")
public class GlobalAwardController {
@Resource
private GlobalAwardService globalAwardService;
@Resource
private UploadService uploadService;
// @PostMapping("/uploads/pdf")
// public Response<String> uploadPdf(@RequestParam("file") MultipartFile file,
// @RequestParam(value = "email", required = false) String email) throws Exception {
// return Response.success(globalAwardService.uploadPdf(file, email));
// }
//
// @PostMapping("/uploads/video")
// public Response<String> uploadVideo(@RequestParam("file") MultipartFile file,
// @RequestParam(value = "email", required = false) String email) throws Exception {
// return Response.success(globalAwardService.uploadVideo(file, email));
// }
// ===== 新增分片上传接口 =====
// ===== PDF分片上传接口 =====
/** 初始化PDF上传任务 */
@PostMapping("/uploads/pdf/init")
@ApiOperation(value = "初始化PDF上传", notes = "创建新的PDF上传任务并返回上传参数")
public Response<UploadInitResponse> initPdfUpload(@ApiParam(value = "PDF上传初始化请求", required = true) @RequestBody UploadInitRequest request) {
UploadTask uploadTask = uploadService.initPdfUpload(request);
return Response.success(UploadInitResponse.builder()
.uploadId(uploadTask.getUploadId())
.chunkSize(uploadTask.getChunkSize())
.totalChunks(uploadTask.getTotalChunks())
.expiresAt(uploadTask.getExpiresAt())
.build());
}
/** 上传PDF分片 */
@PostMapping("/uploads/pdf/chunk")
@ApiOperation(value = "上传PDF分片", notes = "上传PDF文件的单个分片")
public Response<UploadChunkResponse> uploadPdfChunk(
@ApiParam(value = "PDF文件分片", required = true) @RequestParam("chunk") MultipartFile chunk,
@ApiParam(value = "上传任务ID", required = true) @RequestParam("uploadId") String uploadId,
@ApiParam(value = "分片索引(从0开始)", required = true) @RequestParam("chunkIndex") int chunkIndex,
@ApiParam(value = "分片总数", required = true) @RequestParam("totalChunks") int totalChunks) {
UploadChunkResponse uploadChunkResponse = uploadService.uploadPdfChunk(uploadId, chunk, chunkIndex, totalChunks);
return Response.success(uploadChunkResponse);
}
/** 完成PDF上传 */
@PostMapping("/uploads/pdf/complete")
@ApiOperation(value = "完成PDF上传", notes = "完成PDF上传并合并所有分片")
public Response<UploadCompleteResponse> completePdfUpload(@ApiParam(value = "PDF上传完成请求", required = true) @RequestBody UploadCompleteRequest request) {
UploadCompleteResponse uploadCompleteResponse = uploadService.completePdfUpload(
request.getUploadId(),
request.getFileName(),
request.getTotalSize(),
request.getEmail(),
request.getSecureToken());
return Response.success(uploadCompleteResponse);
}
/** 查询PDF上传状态 */
@GetMapping("/uploads/pdf/status/{uploadId}")
@ApiOperation(value = "查询PDF上传状态", notes = "获取PDF上传任务的当前状态")
public Response<UploadStatusResponse> getPdfUploadStatus(@ApiParam(value = "上传任务ID", required = true) @PathVariable String uploadId) {
UploadStatusResponse pdfUploadStatus = uploadService.getPdfUploadStatus(uploadId);
return Response.success(pdfUploadStatus);
}
// ===== 视频分片上传接口 =====
/** 初始化视频上传任务 */
@PostMapping("/uploads/video/init")
@ApiOperation(value = "初始化视频上传", notes = "创建新的视频上传任务并返回上传参数")
public Response<UploadInitResponse> initVideoUpload(@ApiParam(value = "视频上传初始化请求", required = true) @RequestBody UploadInitRequest request) {
UploadTask uploadTask = uploadService.initVideoUpload(request);
return Response.success(UploadInitResponse.builder()
.uploadId(uploadTask.getUploadId())
.chunkSize(uploadTask.getChunkSize())
.totalChunks(uploadTask.getTotalChunks())
.expiresAt(uploadTask.getExpiresAt())
.build());
}
/** 上传视频分片 */
@PostMapping("/uploads/video/chunk")
@ApiOperation(value = "上传视频分片", notes = "上传视频文件的单个分片")
public Response<UploadChunkResponse> uploadVideoChunk(
@ApiParam(value = "视频文件分片", required = true) @RequestParam("chunk") MultipartFile chunk,
@ApiParam(value = "上传任务ID", required = true) @RequestParam("uploadId") String uploadId,
@ApiParam(value = "分片索引(从0开始)", required = true) @RequestParam("chunkIndex") int chunkIndex,
@ApiParam(value = "分片总数", required = true) @RequestParam("totalChunks") int totalChunks) {
UploadChunkResponse uploadChunkResponse = uploadService.uploadVideoChunk(uploadId, chunk, chunkIndex, totalChunks);
return Response.success(uploadChunkResponse);
}
/** 完成视频上传 */
@PostMapping("/uploads/video/complete")
@ApiOperation(value = "完成视频上传", notes = "完成视频上传并合并所有分片")
public Response<UploadCompleteResponse> completeVideoUpload(@ApiParam(value = "视频上传完成请求", required = true) @RequestBody UploadCompleteRequest request) {
UploadCompleteResponse uploadCompleteResponse = uploadService.completeVideoUpload(
request.getUploadId(),
request.getFileName(),
request.getTotalSize(),
request.getEmail(),
request.getSecureToken());
return Response.success(uploadCompleteResponse);
}
/** 查询视频上传状态 */
@GetMapping("/uploads/video/status/{uploadId}")
@ApiOperation(value = "查询视频上传状态", notes = "获取视频上传任务的当前状态")
public Response<UploadStatusResponse> getVideoUploadStatus(@ApiParam(value = "上传任务ID", required = true) @PathVariable String uploadId) {
UploadStatusResponse videoUploadStatus = uploadService.getVideoUploadStatus(uploadId);
return Response.success(videoUploadStatus);
}
@PostMapping("/contestants/save")
@ApiOperation(value = "保存参赛者信息", notes = "保存或更新参赛者信息及已上传的文件")
public Response<Map<String,Object>> submit(@ApiParam(value = "参赛者信息", required = true) @RequestBody ContestantDTO request) {
return Response.success(globalAwardService.saveContestant(request));
}
@GetMapping("/contestants/{id}")
@ApiOperation(value = "根据id查询参赛者", notes = "根据id获取参赛者信息")
public Response<ContestantDTO> getContestantByID(@ApiParam(value = "参赛者id", required = true) @PathVariable("id") String id) {
ContestantDTO dto = globalAwardService.getContestantByID(id);
return Response.success(dto);
}
@GetMapping("/checkEmail")
public Response<String> checkEmail(@RequestParam("email") String email) {
globalAwardService.checkEmail(email);
return Response.success();
}
@GetMapping("/checkCode")
public Response<CheckOTPVO> checkCode(@RequestParam("email") String email, @RequestParam("code") String code) {
return Response.success(globalAwardService.checkCode(email, code));
}
}

View File

@@ -125,11 +125,11 @@ public class PythonController {
@CrossOrigin @CrossOrigin
@Operation(summary = "Seg Anything 转发接口") @Operation(summary = "Seg Anything 转发接口")
@PostMapping("/segAnything") @PostMapping("/segAnything")
public Response<JSONObject> segAnything(@RequestBody Map<String, Object> payload) { public Response<String> segAnything(@RequestBody Map<String, Object> payload) {
// 将前端传来的 Map 转为 fastjson JSONObject 并转发给 python 服务 // 将前端传来的 Map 转为 fastjson JSONObject 并转发给 python 服务
JSONObject requestJson = (JSONObject) JSON.toJSON(payload); JSONObject requestJson = (JSONObject) JSON.toJSON(payload);
JSONObject result = pythonService.segAnything(requestJson); String url = pythonService.segAnything(requestJson);
return Response.success(result); return Response.success(url);
} }
} }

View File

@@ -0,0 +1,12 @@
package com.ai.da.mapper.primary;
import com.ai.da.mapper.primary.entity.Contestant;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ContestantMapper extends BaseMapper<Contestant> {
}

View File

@@ -0,0 +1,67 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* submissions 表对应实体 — 参赛选手信息 (Contestant)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@TableName("contestants")
public class Contestant {
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
private String email;
@TableField("first_name")
private String firstName;
@TableField("last_name")
private String lastName;
private String gender;
private String occupation;
private Integer age;
@TableField("country_region_city")
private String countryRegionCity;
@TableField("phone_number")
private String phoneNumber;
@TableField("design_title")
private String designTitle;
@TableField("design_description")
private String designDescription;
@TableField("pdf_path")
private String pdfPath;
@TableField("video_path")
private String videoPath;
@TableField("created_at")
private LocalDateTime createdAt;
@TableField("updated_at")
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,63 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* Contestant request DTO for Global Award
*/
@Data
@ApiModel(value = "参赛者信息", description = "全球奖项大赛参赛者信息数据传输对象")
public class ContestantDTO {
@ApiModelProperty(value = "邮箱地址", required = true, example = "user@example.com")
private String email;
@ApiModelProperty(value = "名字", required = true, example = "John")
private String firstName;
@ApiModelProperty(value = "姓氏", required = true, example = "Doe")
private String lastName;
@ApiModelProperty(value = "性别", required = true, example = "Male", allowableValues = "Male,Female,Other")
private String gender;
@ApiModelProperty(value = "职业", required = true, example = "Designer")
private String occupation;
@ApiModelProperty(value = "年龄", required = true, example = "25")
private Integer age;
@ApiModelProperty(value = "国家/地区/城市", required = true, example = "China/Shanghai/Shanghai")
private String countryRegionCity;
@ApiModelProperty(value = "电话号码", required = true, example = "+86 138 0000 0000")
private String phoneNumber;
@ApiModelProperty(value = "作品集链接", required = false, example = "https://portfolio.example.com")
private String portfolioUrl;
@ApiModelProperty(value = "设计作品标题", required = true, example = "Modern Office Building Design")
private String designTitle;
@ApiModelProperty(value = "设计作品描述", required = true, example = "A modern office building design featuring sustainable materials...")
private String designDescription;
@ApiModelProperty(value = "PDF文件路径", required = false, example = "contestants/user@example.com/2024/01/design_1234567890.pdf")
private String pdfPath;
@ApiModelProperty(value = "视频文件路径", required = false, example = "contestants/user@example.com/2024/01/video_1234567890.mp4")
private String videoPath;
// /**
// * 是否确认覆盖已存在记录false 表示发现已有记录时仅返回 existingRecord不覆盖
// */
// @ApiModelProperty(value = "是否确认覆盖已存在记录", required = false, example = "false")
// private Boolean confirm = false;
@NotBlank
private String secureToken;
}

View File

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

View File

@@ -1,5 +1,6 @@
package com.ai.da.model.dto; package com.ai.da.model.dto;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import com.ai.da.mapper.primary.entity.Gradient; import com.ai.da.mapper.primary.entity.Gradient;
@@ -73,4 +74,12 @@ public class DesignSingleItemDTO implements Serializable {
@Schema(description = "45") @Schema(description = "45")
private double rotate; private double rotate;
@Hidden
@Schema(description = "带overall印花未分割图片")
private String undividedLayerBase64;
@Hidden
@Schema(description = "带overall/single印花未分割图片")
private String undividedLayerWithSinglePrintBase64;
} }

View File

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

View File

@@ -0,0 +1,33 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
/**
* 分片上传响应DTO
*/
@Data
@Builder
@ApiModel(value = "分片上传响应", description = "单个文件分片上传成功的响应数据")
public class UploadChunkResponse {
/**
* 分片索引
*/
@ApiModelProperty(value = "分片索引(从0开始)", required = true, example = "0")
private Integer chunkIndex;
/**
* 是否上传成功
*/
@ApiModelProperty(value = "是否上传成功", required = true, example = "true")
private Boolean uploaded;
/**
* 分片大小(字节)
*/
@ApiModelProperty(value = "分片大小(字节)", required = true, example = "1048576")
private Long size;
}

View File

@@ -0,0 +1,53 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
/**
* 完成上传请求DTO
*/
@Data
@ApiModel(value = "完成上传请求", description = "文件上传完成时使用的请求参数")
public class UploadCompleteRequest {
/**
* 上传任务ID
*/
@NotBlank(message = "上传任务ID不能为空")
@ApiModelProperty(value = "上传任务唯一标识", required = true, example = "550e8400-e29b-41d4-a716-446655440000")
private String uploadId;
/**
* 文件名
*/
@NotBlank(message = "文件名不能为空")
@ApiModelProperty(value = "原始文件名", required = true, example = "design.pdf")
private String fileName;
/**
* 文件总大小(字节)
*/
@NotNull(message = "文件大小不能为空")
@Positive(message = "文件大小必须大于0")
@ApiModelProperty(value = "文件总大小(字节)", required = true, example = "10485760")
private Long totalSize;
/**
* 用户邮箱
*/
@NotBlank(message = "用户邮箱不能为空")
@ApiModelProperty(value = "用户邮箱", required = true, example = "user@example.com")
private String email;
/**
* 安全令牌(邮箱验证令牌)
*/
@NotBlank(message = "安全令牌不能为空")
@ApiModelProperty(value = "安全令牌", required = true, example = "abc123def456")
private String secureToken;
}

View File

@@ -0,0 +1,33 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
/**
* 完成上传响应DTO
*/
@Data
@Builder
@ApiModel(value = "完成上传响应", description = "文件上传完成并合并成功的响应数据")
public class UploadCompleteResponse {
/**
* 文件在MinIO中的路径
*/
@ApiModelProperty(value = "文件在MinIO中的存储路径", required = true, example = "contestants/user@example.com/2024/01/design_1234567890.pdf")
private String filePath;
/**
* 文件的完整URL
*/
@ApiModelProperty(value = "文件的完整访问URL", required = true, example = "https://minio.example.com/contestants/user@example.com/2024/01/design_1234567890.pdf")
private String fileUrl;
/**
* 文件大小(字节)
*/
@ApiModelProperty(value = "文件大小(字节)", required = true, example = "10485760")
private Long fileSize;
}

View File

@@ -0,0 +1,53 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
/**
* 初始化上传请求DTO
*/
@Data
@ApiModel(value = "初始化上传请求", description = "文件上传初始化时使用的请求参数")
public class UploadInitRequest {
/**
* 文件名
*/
@NotBlank(message = "文件名不能为空")
@ApiModelProperty(value = "文件名", required = true, example = "design.pdf")
private String fileName;
/**
* 文件大小(字节)
*/
@NotNull(message = "文件大小不能为空")
@Positive(message = "文件大小必须大于0")
@ApiModelProperty(value = "文件大小(字节)", required = true, example = "10485760")
private Long fileSize;
/**
* 文件类型MIME类型
*/
@NotBlank(message = "文件类型不能为空")
@ApiModelProperty(value = "文件类型(MIME类型)", required = true, example = "application/pdf")
private String fileType;
/**
* 用户邮箱
*/
@ApiModelProperty(value = "用户邮箱", required = true, example = "user@example.com")
private String email;
/**
* 安全令牌(邮箱验证令牌)
*/
@NotBlank(message = "安全令牌不能为空")
@ApiModelProperty(value = "安全令牌", required = true, example = "abc123def456")
private String secureToken;
}

View File

@@ -0,0 +1,41 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 初始化上传响应DTO
*/
@Data
@Builder
@ApiModel(value = "初始化上传响应", description = "文件上传初始化成功的响应数据")
public class UploadInitResponse {
/**
* 上传任务ID
*/
@ApiModelProperty(value = "上传任务唯一标识", required = true, example = "550e8400-e29b-41d4-a716-446655440000")
private String uploadId;
/**
* 分片大小(字节)
*/
@ApiModelProperty(value = "每个分片的大小(字节)", required = true, example = "1048576")
private Integer chunkSize;
/**
* 总分片数
*/
@ApiModelProperty(value = "文件被分成多少个分片", required = true, example = "10")
private Integer totalChunks;
/**
* 任务过期时间
*/
@ApiModelProperty(value = "上传任务过期时间", required = true, example = "2024-01-20T10:30:00")
private LocalDateTime expiresAt;
}

View File

@@ -0,0 +1,59 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import java.util.Set;
/**
* 上传状态响应DTO
*/
@Data
@Builder
@ApiModel(value = "上传状态响应", description = "查询上传任务当前状态的响应数据")
public class UploadStatusResponse {
/**
* 上传任务ID
*/
@ApiModelProperty(value = "上传任务唯一标识", required = true, example = "550e8400-e29b-41d4-a716-446655440000")
private String uploadId;
/**
* 上传状态
*/
@ApiModelProperty(value = "上传任务状态", required = true, example = "uploading", allowableValues = "initiated,uploading,completed,failed,expired")
private String status;
/**
* 上传进度百分比 (0-100)
*/
@ApiModelProperty(value = "上传进度百分比(0-100)", required = true, example = "60.0")
private Double progress;
/**
* 已上传分片索引集合
*/
@ApiModelProperty(value = "已上传分片的索引集合", required = true, example = "[0,1,2,3,4]")
private Set<Integer> uploadedChunks;
/**
* 总分片数
*/
@ApiModelProperty(value = "文件被分成多少个分片", required = true, example = "10")
private Integer totalChunks;
/**
* 文件总大小(字节)
*/
@ApiModelProperty(value = "文件总大小(字节)", required = true, example = "10485760")
private Long totalSize;
/**
* 已上传大小(字节)
*/
@ApiModelProperty(value = "已上传的数据大小(字节)", required = true, example = "6291456")
private Long uploadedSize;
}

View File

@@ -0,0 +1,16 @@
package com.ai.da.model.vo;
import com.ai.da.model.dto.ContestantDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CheckOTPVO {
private String secureToken;
private ContestantDTO contestantDTO;
}

View File

@@ -62,11 +62,11 @@ public class DesignItemClothesDetailVO {
@Schema(description = "渐变色信息") @Schema(description = "渐变色信息")
private Gradient gradient; private Gradient gradient;
@Schema(description = "未分割的图层") /* @Schema(description = "未分割的图层")
private String undividedLayer; private String undividedLayer;
@Schema(description = "添加single印花的未分割的图层") @Schema(description = "添加single印花的未分割的图层")
private String undividedLayerWithSinglePrint; private String undividedLayerWithSinglePrint;*/
@Schema(description = "局部design") @Schema(description = "局部design")
private PartialDesignDTO partialDesign; private PartialDesignDTO partialDesign;

View File

@@ -2789,7 +2789,7 @@ public class PythonService {
DesignPythonObject pythonObject = new DesignPythonObject(); DesignPythonObject pythonObject = new DesignPythonObject();
designPythonObjects.setProcess_id(designSingleDTO.getProcessId()); designPythonObjects.setProcess_id(designSingleDTO.getProcessId());
pythonObject.setItems(coverToDesignSinglePythonItem(designSingleDTO, designLibraryModelPoint, singleOverall)); pythonObject.setItems(coverToDesignSinglePythonItem(designSingleDTO, designLibraryModelPoint, singleOverall));
pythonObject.setBasic(coverToSingleBasic(singleOverall, switchCategory, designLibraryModelPoint, previewOrSubmit)); pythonObject.setBasic(coverToSingleBasic(singleOverall, switchCategory, designLibraryModelPoint, designSingleDTO.getDesignType()));
objects.add(pythonObject); objects.add(pythonObject);
return designPythonObjects; return designPythonObjects;
} }
@@ -2855,6 +2855,9 @@ public class PythonService {
designSingleItem.getPartialDesign().getPartialDesignMinioPath()); designSingleItem.getPartialDesign().getPartialDesignMinioPath());
resolveDesignElement(designSingleItem.getTrims(), printToPython); resolveDesignElement(designSingleItem.getTrims(), printToPython);
log.info("组装参数【服装:{}的maskUrl: {}】",designSingleItem.getType(), designSingleItem.getMaskUrl()); log.info("组装参数【服装:{}的maskUrl: {}】",designSingleItem.getType(), designSingleItem.getMaskUrl());
String mergeImagePath = !StringUtil.isNullOrEmpty(printToPython.getPartial())
? printToPython.getPartial() : designSingleItem.getPath();
response.add(new DesignPythonItem( response.add(new DesignPythonItem(
designSingleItem.getType(), designSingleItem.getType(),
designSingleItem.getPath(), designSingleItem.getPath(),
@@ -2871,7 +2874,8 @@ public class PythonService {
gradientString, gradientString,
designSingleItem.getMaskUrl(), designSingleItem.getMaskUrl(),
designSingleItem.getTranspose(), designSingleItem.getTranspose(),
designSingleItem.getRotate() designSingleItem.getRotate(),
mergeImagePath
)); ));
}); });
@@ -3049,7 +3053,7 @@ public class PythonService {
*/ */
private DesignPythonBasic coverToSingleBasic(String singleOverall, String switchCategory, private DesignPythonBasic coverToSingleBasic(String singleOverall, String switchCategory,
DesignLibraryModelPointVO designLibraryModelPoint, DesignLibraryModelPointVO designLibraryModelPoint,
String previewOrSubmit) { String designType) {
DesignPythonBasic basic = new DesignPythonBasic(); DesignPythonBasic basic = new DesignPythonBasic();
basic.setSingle_overall(singleOverall); basic.setSingle_overall(singleOverall);
basic.setSwitch_category(switchCategory); basic.setSwitch_category(switchCategory);
@@ -3061,7 +3065,8 @@ public class PythonService {
basic.setScale_earrings(0.16); basic.setScale_earrings(0.16);
basic.setBody_point_test(getMap(designLibraryModelPoint)); basic.setBody_point_test(getMap(designLibraryModelPoint));
basic.setLayer_order(Boolean.TRUE); basic.setLayer_order(Boolean.TRUE);
basic.setPreview_submit(previewOrSubmit); // basic.setPreview_submit(previewOrSubmit);
basic.setDesign_type(designType);
return basic; return basic;
} }
@@ -4109,7 +4114,7 @@ public class PythonService {
* 转发 seg_anything 请求到 python 服务 * 转发 seg_anything 请求到 python 服务
* 请求 body 由调用方组装(包含 user_id, image_path, type, points, labels, box 等) * 请求 body 由调用方组装(包含 user_id, image_path, type, points, labels, box 等)
*/ */
public JSONObject segAnything(JSONObject content) { public String segAnything(JSONObject content) {
OkHttpClient client = new OkHttpClient().newBuilder() OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS)
.pingInterval(5, TimeUnit.SECONDS) .pingInterval(5, TimeUnit.SECONDS)
@@ -4142,10 +4147,33 @@ public class PythonService {
String responseBody = response.body().string(); String responseBody = response.body().string();
JSONObject responseObject = JSON.parseObject(responseBody); JSONObject responseObject = JSON.parseObject(responseBody);
log.info("PythonService##segAnything response###{}", responseObject); log.info("PythonService##segAnything response###{}", responseObject);
return responseObject; // Extract nested output path and convert to presigned MinIO URL for frontend, return as String
try {
if (responseObject != null) {
JSONObject dataObj = responseObject.getJSONObject("data");
if (dataObj != null) {
String output = dataObj.getString("output");
if (!StringUtil.isNullOrEmpty(output)) {
try {
String presigned = minioUtil.getPreSignedUrl(output, 24 * 60);
return presigned;
} catch (Exception e) {
log.error("Failed to generate presigned url for {}: {}", output, e.getMessage());
throw new BusinessException("segAnything.presign.failed");
}
}
}
}
} catch (Exception ex) {
log.error("PythonService##segAnything post-process error###{}", ex.getMessage());
throw new BusinessException("segAnything.interface.exception");
}
throw new BusinessException("segAnything.missing.output");
} }
throw new BusinessException("segAnything.interface.exception"); throw new BusinessException("segAnything.interface.exception");
} catch (IOException | JSONException e) { } catch (IOException |JSONException e) {
log.error("PythonService##segAnything异常###{}", e.getMessage()); log.error("PythonService##segAnything异常###{}", e.getMessage());
throw new BusinessException("segAnything.interface.exception"); throw new BusinessException("segAnything.interface.exception");
} }
@@ -4281,7 +4309,7 @@ public class PythonService {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
map.put("prompt", prompt); map.put("prompt", prompt);
AuthPrincipalVo userHolder = UserContext.getUserHolder(); AuthPrincipalVo userHolder = UserContext.getUserHolder();
map.put("user_id", userHolder.getId()); map.put("user_id", userHolder.getId().toString());
log.info("brandDNAGenerate请求python 参数:####{}", map); log.info("brandDNAGenerate请求python 参数:####{}", map);
String param = JSON.toJSONString(map, SerializerFeature.WriteNullStringAsEmpty); String param = JSON.toJSONString(map, SerializerFeature.WriteNullStringAsEmpty);
log.info(param); log.info(param);

View File

@@ -16,7 +16,7 @@ public class DesignPythonBasic {
private String single_overall; private String single_overall;
private String preview_submit; // private String preview_submit;
private String switch_category; private String switch_category;
/** /**
@@ -40,4 +40,7 @@ public class DesignPythonBasic {
private Boolean layer_order = Boolean.FALSE; 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; 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(), public static List<String> OUTWEAR_DRESS_BLOUSE = Arrays.asList(CollectionLevel2TypeEnum.OUTWEAR.getRealName(),
CollectionLevel2TypeEnum.DRESS.getRealName(), CollectionLevel2TypeEnum.BLOUSE.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, 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, 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.type = type;
this.path = path; this.path = path;
this.color = color; this.color = color;
@@ -169,6 +176,7 @@ public class DesignPythonItem {
this.seg_mask_url = seg_mask_url; this.seg_mask_url = seg_mask_url;
this.transpose = transpose; this.transpose = transpose;
this.rotate = rotate; 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) { public DesignPythonItem(String type, String path, String color, PrintToPython print, String icon, Long businessId, Long image_id) {

View File

@@ -246,4 +246,6 @@ public interface AccountService extends IService<Account> {
void setEduAdminToExpire(Account adminAccount); void setEduAdminToExpire(Account adminAccount);
String getOrganizationTypeByRole(Integer roleNum); String getOrganizationTypeByRole(Integer roleNum);
void validateUserValidaExpire(Account account);
} }

View File

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

View File

@@ -0,0 +1,25 @@
package com.ai.da.service;
import com.ai.da.model.dto.ContestantDTO;
import com.ai.da.model.vo.CheckOTPVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
public interface GlobalAwardService {
String uploadPdf(MultipartFile file, String email) throws Exception;
String uploadVideo(MultipartFile file, String email) throws Exception;
Map<String, Object> saveContestant(ContestantDTO request);
com.ai.da.model.dto.ContestantDTO getContestantByID(String email);
void checkEmail(String email);
CheckOTPVO checkCode(String email, String otp);
void checkSecurityToken(String email, String securityToken);
}

View File

@@ -39,9 +39,10 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -83,6 +84,9 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService { public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
@Resource
private ApplicationContext applicationContext;
@Resource @Resource
private AccountMapper accountMapper; private AccountMapper accountMapper;
@@ -279,7 +283,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
// 定义常量(临时) // 定义常量(临时)
private static final Integer SYSTEM_USER_TYPE_EDU_ADMIN = 7; private static final Integer SYSTEM_USER_TYPE_EDU_ADMIN = 7;
private void validateUserValidaExpire(Account account) { public void validateUserValidaExpire(Account account) {
Long currentTime = new Date().getTime(); Long currentTime = new Date().getTime();
if (account.getSystemUser().equals(0)) { if (account.getSystemUser().equals(0)) {
return; return;
@@ -297,7 +301,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
if (isEduAdmin) { if (isEduAdmin) {
setEduAdminToExpire(account); setEduAdminToExpire(account);
} else { } else {
toVisitor(account); // 这里调用代理的 toVisitor 方法
AccountService proxy = applicationContext.getBean(AccountService.class);
proxy.toVisitor(account);
// return; // return;
throw new BusinessException("account.expired", ResultEnum.PROMPT.getCode()); throw new BusinessException("account.expired", ResultEnum.PROMPT.getCode());
} }
@@ -1949,6 +1955,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
return baseMapper.selectList(queryWrapper); return baseMapper.selectList(queryWrapper);
} }
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void toVisitor(Account account) { public void toVisitor(Account account) {
accountMapper.toVisitor(account.getId()); accountMapper.toVisitor(account.getId());
} }
@@ -2507,7 +2514,7 @@ 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.setValidEndTime(adminAcc.getValidEndTime());
} }

View File

@@ -363,9 +363,9 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
public List<TDesignPythonOutfitDetail> saveDesignSingleItemDetailAndLayers(DesignPythonObjects pythonObjects public List<TDesignPythonOutfitDetail> saveDesignSingleItemDetailAndLayers(DesignPythonObjects pythonObjects
, Long designId, Long designItemId, Long userId , Long designId, Long designItemId, Long userId
, JSONObject outfit, String timeZone, List<DesignSingleItemDTO> designSingleItemDTOList , JSONObject outfit, String timeZone, List<DesignSingleItemDTO> designSingleItemDTOList
, Map<String, List<String>> priorityAndUndividedLayer /*, Map<String, List<String>> priorityAndUndividedLayer*/
, boolean changeModelFlag , boolean changeModelFlag
, Long modelId, String modelType, boolean isSingleCollectionFlag) { , Long modelId, String modelType, boolean isSingleCollectionFlag, String designType) {
DesignItem designItem = new DesignItem(); DesignItem designItem = new DesignItem();
// String url = pythonObjects.getObjects().get(0).getBasic().getSave_name(); // String url = pythonObjects.getObjects().get(0).getBasic().getSave_name();
@@ -424,11 +424,15 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
designItemDetail.setColor(detail.getColor()); designItemDetail.setColor(detail.getColor());
designItemDetail.setIconPath(detail.getIcon()); designItemDetail.setIconPath(detail.getIcon());
// designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getType().toLowerCase())); // designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getType().toLowerCase()));
/*// 取消存储UndividedLayer和UndividedLayerWithSinglePrint字段
if (!detail.getType().equals("Body")) { if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(0)); if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(0))) {
designItemDetail.setUndividedLayerWithSinglePrint(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1)); 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表中 这里还要存吗? // 印花存储在design_item_detail_print表中 这里还要存吗?
// DesignPythonItemPrint printObject = detail.getPrintToPython(); // DesignPythonItemPrint printObject = detail.getPrintToPython();
// designItemDetail.setPrintPath(Objects.isNull(printObject) ? "" : printObject.getPath()); // designItemDetail.setPrintPath(Objects.isNull(printObject) ? "" : printObject.getPath());
@@ -436,7 +440,18 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
// designItemDetail.setPrintJson(JSON.toJSONString(printObject)); // designItemDetail.setPrintJson(JSON.toJSONString(printObject));
designItemDetail.setPartialDesign(Objects.isNull(detail.getPrint()) ? null : detail.getPrint().getPartial()); designItemDetail.setPartialDesign(Objects.isNull(detail.getPrint()) ? null : detail.getPrint().getPartial());
designItemDetail.setGradientString(detail.getGradientString());
designItemDetails.add(designItemDetail); designItemDetails.add(designItemDetail);
// 处理gradientString为null的情况强制更新为null
if (Objects.isNull(detail.getGradientString()) && Objects.nonNull(designItemDetail.getId())) {
UpdateWrapper<DesignItemDetail> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", designItemDetail.getId());
updateWrapper.set("gradient_string", null);
updateWrapper.set("update_date", DateUtil.getByTimeZone(timeZone));
designItemDetailService.update(null, updateWrapper);
}
}); });
// 逻辑删除未复用的旧记录 // 逻辑删除未复用的旧记录
@@ -481,6 +496,7 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
priorityOffset = designSingleItemDTOList.stream() priorityOffset = designSingleItemDTOList.stream()
.collect(Collectors.toMap(DesignSingleItemDTO::getPriority, DesignSingleItemDTO::getOffset)); .collect(Collectors.toMap(DesignSingleItemDTO::getPriority, DesignSingleItemDTO::getOffset));
} }
List<TDesignPythonOutfitDetail> list = new ArrayList<>(); List<TDesignPythonOutfitDetail> list = new ArrayList<>();
for (int i = 0; i < layers.size(); i++) { for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i); JSONObject jsonObject = layers.getJSONObject(i);
@@ -685,6 +701,9 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public DesignSingleVO designSingleIncludeLayers(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO) { public DesignSingleVO designSingleIncludeLayers(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO) {
if (!designSingleIncludeLayersDTO.getDesignType().equals("merge") && !designSingleIncludeLayersDTO.getDesignType().equals("default")) {
throw new BusinessException("The value of DesignType can only be 'default' or 'merge' ");
}
// 记录入参 base64数据太长所以这里去掉 // 记录入参 base64数据太长所以这里去掉
DesignSingleIncludeLayersDTO clone = SerializationUtils.clone(designSingleIncludeLayersDTO); DesignSingleIncludeLayersDTO clone = SerializationUtils.clone(designSingleIncludeLayersDTO);
clone.getDesignSingleItemDTOList().forEach(i -> { clone.getDesignSingleItemDTOList().forEach(i -> {
@@ -708,6 +727,17 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
log.info("set partialDesignBase64为空便于日志打印"); log.info("set partialDesignBase64为空便于日志打印");
i.getPartialDesign().setPartialDesignBase64(null); 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)); log.info("designSingle request入参 ==> " + JSONObject.toJSONString(clone));
@@ -802,7 +832,7 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
maskBase64ToPath(designSingleIncludeLayersDTO, setNull); maskBase64ToPath(designSingleIncludeLayersDTO, setNull);
// maskBase64ToPath(designSingleIncludeLayersDTO, Boolean.TRUE); // maskBase64ToPath(designSingleIncludeLayersDTO, Boolean.TRUE);
partialDesignBase64ToImage(designSingleIncludeLayersDTO, userId, designSingleIncludeLayersDTO.getIsPreview()); partialDesignBase64ToImage(designSingleIncludeLayersDTO, userId, designSingleIncludeLayersDTO.getIsPreview(), designSingleIncludeLayersDTO.getDesignType());
// 组装入参 // 组装入参
DesignPythonObjects objects = pythonService.covertDesignSingleParam( DesignPythonObjects objects = pythonService.covertDesignSingleParam(
@@ -820,13 +850,13 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
JSONObject outfit = data.getJSONObject("0"); JSONObject outfit = data.getJSONObject("0");
JSONArray layers = outfit.getJSONArray("layers"); JSONArray layers = outfit.getJSONArray("layers");
Map<String, List<String>> priorityAndUndividedLayer = setPriorityAndUndividedLayer(layers); // Map<String, List<String>> priorityAndUndividedLayer = setPriorityAndUndividedLayer(layers, designSingleIncludeLayersDTO);
if (!designSingleIncludeLayersDTO.getIsPreview()) { if (!designSingleIncludeLayersDTO.getIsPreview()) {
// 更新及保存图层信息 // 更新及保存图层信息
tDesignPythonOutfitDetails = saveDesignSingleItemDetailAndLayers(objects, design.getId(), designSingleIncludeLayersDTO.getDesignItemId() tDesignPythonOutfitDetails = saveDesignSingleItemDetailAndLayers(objects, design.getId(), designSingleIncludeLayersDTO.getDesignItemId()
, userId, outfit, designSingleIncludeLayersDTO.getTimeZone() , userId, outfit, designSingleIncludeLayersDTO.getTimeZone()
, designSingleIncludeLayersDTO.getDesignSingleItemDTOList() , designSingleIncludeLayersDTO.getDesignSingleItemDTOList()
, priorityAndUndividedLayer, changeModelFlag, modelId, modelType, isSingleCollectionFlag); /*, priorityAndUndividedLayer*/, changeModelFlag, modelId, modelType, isSingleCollectionFlag, designSingleIncludeLayersDTO.getDesignType());
saveCollectionElement(designSingleIncludeLayersDTO); saveCollectionElement(designSingleIncludeLayersDTO);
} else { } else {
@@ -860,8 +890,8 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
outfit.getString("synthesis_url"), outfit.getString("synthesis_url"),
designSingleIncludeLayersDTO.getDesignSingleItemDTOList(), designSingleIncludeLayersDTO.getDesignSingleItemDTOList(),
detailsVO, detailsVO,
design.getSingleOverall(), design.getSingleOverall()/*,
priorityAndUndividedLayer); priorityAndUndividedLayer*/);
} }
// 方法1仅查询无事务 // 方法1仅查询无事务
@@ -921,12 +951,12 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
item.setMaskUrl(path); 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 -> { designSingleIncludeLayersDTO.getDesignSingleItemDTOList().forEach(item -> {
PartialDesignDTO partialDesignDTO = item.getPartialDesign(); PartialDesignDTO partialDesignDTO = item.getPartialDesign();
if (!Objects.isNull(item.getPartialDesign()) if (!Objects.isNull(item.getPartialDesign())
@@ -948,21 +978,90 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
item.getPartialDesign().setPartialDesignMinioPath(newPath); item.getPartialDesign().setPartialDesignMinioPath(newPath);
} else if (Objects.isNull(item.getPartialDesign()) } else if (Objects.isNull(item.getPartialDesign())
|| StringUtil.isNullOrEmpty(item.getPartialDesign().getPartialDesignMinioPath())) { || StringUtil.isNullOrEmpty(item.getPartialDesign().getPartialDesignMinioPath())) {
item.setPartialDesign(new PartialDesignDTO(null)); if (designType.equals("merge")) {
// 先去数据库进行查找,如果数据库中也是空,则提示需要提供,否则无法生成
DesignItemDetail designItemDetail = designItemDetailService.getById(item.getId());
if (Objects.isNull(designItemDetail)){
log.error("未知designItemDetailId: {}", item.getId());
throw new BusinessException("designItemDetails.not.found");
} else if (StringUtil.isNullOrEmpty(designItemDetail.getPartialDesign())) {
item.setPartialDesign(new PartialDesignDTO(designItemDetail.getUndividedLayer()));
/*log.error("merge模式下必须提供partialDesign");
throw new BusinessException("required.partialDesign");*/
} else {
item.setPartialDesign(new PartialDesignDTO(designItemDetail.getPartialDesign()));
}
} else {
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 @Override
public Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers) { public Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers, DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO) {
HashMap<String, List<String>> priorityAndLayer = new HashMap<>(); String designType = "default";
for (int i = 0; i < layers.size(); i++) { if (Objects.nonNull(designSingleIncludeLayersDTO)) {
JSONObject jsonObject = layers.getJSONObject(i); designType = designSingleIncludeLayersDTO.getDesignType();
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")));
} }
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; return priorityAndLayer;
} }
@@ -1077,8 +1176,8 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
String currentFullBodyView, String currentFullBodyView,
List<DesignSingleItemDTO> designSingleItemDTOList, List<DesignSingleItemDTO> designSingleItemDTOList,
List<DesignPythonOutfitVO> layersObject, List<DesignPythonOutfitVO> layersObject,
String singleOrOverall, String singleOrOverall/*,
Map<String, List<String>> priorityAndUndividedLayer) { Map<String, List<String>> priorityAndUndividedLayer*/) {
DesignSingleVO designSingleVO = new DesignSingleVO(); DesignSingleVO designSingleVO = new DesignSingleVO();
ArrayList<DesignItemClothesDetailVO> clothes = new ArrayList<>(); ArrayList<DesignItemClothesDetailVO> clothes = new ArrayList<>();
@@ -1118,10 +1217,15 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
String preSignedUrl = StringUtil.isNullOrEmpty(partialDesignMinioPath) ? null : minioUtil.getPreSignedUrl(partialDesignMinioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true); String preSignedUrl = StringUtil.isNullOrEmpty(partialDesignMinioPath) ? null : minioUtil.getPreSignedUrl(partialDesignMinioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true);
designItemClothesDetailVO.setPartialDesign(new PartialDesignDTO(partialDesignMinioPath, preSignedUrl)); designItemClothesDetailVO.setPartialDesign(new PartialDesignDTO(partialDesignMinioPath, preSignedUrl));
/*// 取消存储/返回UndividedLayer和UndividedLayerWithSinglePrint字段
if (priorityAndUndividedLayer.containsKey(singleItem.getPriority().toString())) { if (priorityAndUndividedLayer.containsKey(singleItem.getPriority().toString())) {
designItemClothesDetailVO.setUndividedLayer(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(0), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true)); if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(0))) {
designItemClothesDetailVO.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true)); 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())); body.setLayersObject(layersObject.stream().filter(layers -> layers.getImageCategory().equals("body")).collect(Collectors.toList()));
clothes.add(designItemClothesDetailVO); clothes.add(designItemClothesDetailVO);
@@ -1297,6 +1401,9 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
} }
} }
if (Objects.isNull(designSingleItem.getPrintObject()) || Objects.isNull(designSingleItem.getPrintObject().getPrints())) {
return;
}
// 添加print到library // 添加print到library
designSingleItem.getPrintObject().getPrints().forEach(print -> { designSingleItem.getPrintObject().getPrints().forEach(print -> {
if (!StringUtil.isNullOrEmpty(print.getDesignType()) && print.getDesignType().equals("Collection")) { if (!StringUtil.isNullOrEmpty(print.getDesignType()) && print.getDesignType().equals("Collection")) {

View File

@@ -715,7 +715,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
(existing, replacement) -> replacement)); (existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers); Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
log.info("all typeLayers Map:{}", typeAndUndividedLayer); 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()) { for (DesignPythonItem detail : item.getItems()) {
if (null == detail) { if (null == detail) {
continue; continue;
@@ -853,7 +853,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
Map<String, Integer> typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0], Map<String, Integer> typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0],
d -> Math.abs(d.getPriority()), d -> Math.abs(d.getPriority()),
(existing, replacement) -> replacement)); (existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers); // Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
for (DesignPythonItem detail : item.getItems()) { for (DesignPythonItem detail : item.getItems()) {
if (null == detail) { if (null == detail) {
continue; continue;
@@ -864,9 +864,9 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
designItemDetail.setDesignItemId(designItemId); designItemDetail.setDesignItemId(designItemId);
designItemDetail.setCollectionElementId(detail.getElementId()); designItemDetail.setCollectionElementId(detail.getElementId());
designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone)); designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone));
if (!detail.getType().equals("Body")) { /*if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType())); designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType()));
} }*/
if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) { if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) {
designItemDetail.setPath(detail.getBody_path()); designItemDetail.setPath(detail.getBody_path());
@@ -1140,12 +1140,12 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
userPreference.setPath(designItemDetail.getPath()); userPreference.setPath(designItemDetail.getPath());
SysFile sysFile = sysFileService.getOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUrl, designItemDetail.getPath())); SysFile sysFile = sysFileService.getOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUrl, designItemDetail.getPath()));
userPreference.setAccountId(userHolder.getId()); userPreference.setAccountId(userHolder.getId());
if (sysFile != null){ if (sysFile != null) {
userPreference.setCategory(sysFile.getLevel3Type().toLowerCase()+"_"+sysFile.getLevel2Type().toLowerCase()); userPreference.setCategory(sysFile.getLevel3Type().toLowerCase() + "_" + sysFile.getLevel2Type().toLowerCase());
userPreference.setStyle(sysFile.getStyle()); userPreference.setStyle(sysFile.getStyle());
}else { } else {
log.error("sysFile not found:{}",designItemDetail.getPath()); log.error("sysFile not found:{}", designItemDetail.getPath());
SendEmailUtil.commonExceptionReminder("url在sysFile里找不到"+designItemDetail.getPath(), new String[]{"litianxiangxtt@163.com"}); SendEmailUtil.commonExceptionReminder("url在sysFile里找不到" + designItemDetail.getPath(), new String[]{"litianxiangxtt@163.com"});
continue; continue;
} }
@@ -1154,7 +1154,12 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
.toLocalDateTime()); .toLocalDateTime());
userPreference.setDesignItemId(designItem.getId()); userPreference.setDesignItemId(designItem.getId());
userPreference.setProjectId(projectId); userPreference.setProjectId(projectId);
userPreference.setWorkspaceRelStyleId(workspaceRelStyles.get(0).getStyleId()); if (workspaceRelStyles == null||workspaceRelStyles.isEmpty()) {
//查不到记录style应该是all
userPreference.setWorkspaceRelStyleId(0L);
} else {
userPreference.setWorkspaceRelStyleId(workspaceRelStyles.get(0).getStyleId());
}
userPreferenceMapper.insert(userPreference); userPreferenceMapper.insert(userPreference);
} }
} }
@@ -1326,12 +1331,13 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
d.setScope(o.getPath().startsWith("aida-sys-image") ? "sys" : "user"); d.setScope(o.getPath().startsWith("aida-sys-image") ? "sys" : "user");
d.setLevel1Type(converTypeToLevel1(o.getType())); d.setLevel1Type(converTypeToLevel1(o.getType()));
d.setGradient(JSONObject.parseObject(o.getGradientString(), Gradient.class)); d.setGradient(JSONObject.parseObject(o.getGradientString(), Gradient.class));
/*// 取消存储/返回UndividedLayer和UndividedLayerWithSinglePrint字段
if (!StringUtil.isNullOrEmpty(o.getUndividedLayer())) { if (!StringUtil.isNullOrEmpty(o.getUndividedLayer())) {
d.setUndividedLayer(minioUtil.getPreSignedUrl(o.getUndividedLayer(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); d.setUndividedLayer(minioUtil.getPreSignedUrl(o.getUndividedLayer(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
} }
if (!StringUtil.isNullOrEmpty(o.getUndividedLayerWithSinglePrint())) { if (!StringUtil.isNullOrEmpty(o.getUndividedLayerWithSinglePrint())) {
d.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(o.getUndividedLayerWithSinglePrint(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); d.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(o.getUndividedLayerWithSinglePrint(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
} }*/
// 根据designItemDetailId获取印花 // 根据designItemDetailId获取印花
List<DesignItemDetailPrint> prints = designItemDetailPrintService.getByDesignItemDetailId(o.getId(), "print"); List<DesignItemDetailPrint> prints = designItemDetailPrintService.getByDesignItemDetailId(o.getId(), "print");
prints.removeIf(print -> !minioUtil.doesObjectExist(print.getPath())); prints.removeIf(print -> !minioUtil.doesObjectExist(print.getPath()));
@@ -2539,7 +2545,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
Map<String, Integer> typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0], Map<String, Integer> typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0],
d -> Math.abs(d.getPriority()), d -> Math.abs(d.getPriority()),
(existing, replacement) -> replacement)); (existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers); // Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
for (DesignPythonItem detail : item.getItems()) { for (DesignPythonItem detail : item.getItems()) {
if (null == detail) { if (null == detail) {
continue; continue;
@@ -2550,9 +2556,9 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
designItemDetail.setDesignItemId(designItemId); designItemDetail.setDesignItemId(designItemId);
designItemDetail.setCollectionElementId(detail.getElementId()); designItemDetail.setCollectionElementId(detail.getElementId());
designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone)); designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone));
if (!detail.getType().equals("Body")) { /*if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType())); designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType()));
} }*/
if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) { if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) {
designItemDetail.setPath(detail.getBody_path()); designItemDetail.setPath(detail.getBody_path());
//BODY不关联businessId //BODY不关联businessId

View File

@@ -0,0 +1,300 @@
package com.ai.da.service.impl;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.enums.AuthenticationOperationTypeEnum;
import com.ai.da.common.utils.*;
import com.ai.da.mapper.primary.AccountMapper;
import com.ai.da.mapper.primary.ContestantMapper;
import com.ai.da.mapper.primary.NotificationMapper;
import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.mapper.primary.entity.Contestant;
import com.ai.da.mapper.primary.entity.Notification;
import com.ai.da.model.dto.ContestantDTO;
import com.ai.da.model.dto.PublishSysNotificationDTO;
import com.ai.da.model.vo.CheckOTPVO;
import com.ai.da.service.GlobalAwardService;
import com.ai.da.service.MessageCenterService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service
@Slf4j
@RequiredArgsConstructor
public class GlobalAwardServiceImpl implements GlobalAwardService {
@Resource
private ContestantMapper contestantMapper;
private final AccountMapper accountMapper;
private final MessageCenterService messageCenterService;
private final NotificationMapper notificationMapper;
private final RedisUtil redisUtil;
@Value("${file.upload.dir:uploads}")
private String uploadDir;
private static final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyy/MM");
private static final String tokenCacheKey = AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + ":";
@Value("${minio.bucket:contestants}")
private String minioBucket;
@Value("${global.award.link}")
private String link;
@Resource
private MinioUtil minioUtil;
@Override
public String uploadPdf(MultipartFile file, String email) throws Exception {
validatePdf(file);
String path = storeFile(file, email, "pdf");
return path;
}
@Override
public String uploadVideo(MultipartFile file, String email) throws Exception {
validateVideo(file);
String path = storeFile(file, email, "video");
return path;
}
private void validatePdf(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BusinessException("File is empty.");
}
String ct = file.getContentType();
if (ct == null || !ct.toLowerCase().contains("pdf")) {
throw new BusinessException("Only PDF files are allowed.");
}
// size limit example 20MB
if (file.getSize() > 20L * 1024 * 1024) {
throw new BusinessException("PDF file size exceeds the limit.");
}
}
private void validateVideo(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BusinessException("File is empty.");
}
String ct = file.getContentType();
if (ct == null || !(ct.toLowerCase().contains("mp4") || ct.toLowerCase().contains("video") )) {
throw new BusinessException("Invalid video file type.");
}
// size limit example 100MB
if (file.getSize() > 100L * 1024 * 1024) {
throw new BusinessException("Video file size exceeds the limit.");
}
}
private String normalizeEmail(String email) {
if (email == null) {
return "anonymous";
}
return email.replaceAll("[^a-zA-Z0-9]", "_");
}
private String storeFile(MultipartFile file, String email, String kind) throws IOException {
String normalized = normalizeEmail(email);
String datePart = LocalDateTime.now().format(YYYY_MM_DD);
String ext = "";
String original = file.getOriginalFilename();
if (original != null && original.contains(".")) {
ext = original.substring(original.lastIndexOf('.'));
}
String filename = System.currentTimeMillis() + "_" + UUID.randomUUID().toString() + ext;
String relativePath = "contestants/" + normalized + "/" + datePart + "/" + filename;
String uploadedPath = minioUtil.upload(minioBucket, relativePath, file, null);
log.info("uploaded via MinioUtil: {}", uploadedPath);
return uploadedPath;
}
@Override
public Map<String, Object> saveContestant(ContestantDTO request) {
Map<String,Object> resp = new HashMap<>();
if (request.getEmail() == null) {
throw new BusinessException("Email is required.");
}
checkSecurityToken(request.getEmail(), request.getSecureToken());
QueryWrapper<Contestant> qw = new QueryWrapper<>();
qw.eq("email", request.getEmail());
Contestant existing = contestantMapper.selectOne(qw);
LocalDateTime now = LocalDateTime.now();
if (existing == null) {
Contestant toInsert = Contestant.builder()
.email(request.getEmail())
.firstName(request.getFirstName())
.lastName(request.getLastName())
.gender(request.getGender())
.occupation(request.getOccupation())
.age(request.getAge())
.countryRegionCity(request.getCountryRegionCity())
.phoneNumber(request.getPhoneNumber())
.designTitle(request.getDesignTitle())
.designDescription(request.getDesignDescription())
.pdfPath(request.getPdfPath())
.videoPath(request.getVideoPath())
.createdAt(now)
.updatedAt(now)
.build();
contestantMapper.insert(toInsert);
resp.put("success", true);
sendSiteMsg(toInsert.getId(), toInsert.getEmail());
return resp;
} else {
// update existing contestant
existing.setFirstName(request.getFirstName());
existing.setLastName(request.getLastName());
existing.setGender(request.getGender());
existing.setOccupation(request.getOccupation());
existing.setAge(request.getAge());
existing.setCountryRegionCity(request.getCountryRegionCity());
existing.setPhoneNumber(request.getPhoneNumber());
existing.setDesignTitle(request.getDesignTitle());
existing.setDesignDescription(request.getDesignDescription());
existing.setPdfPath(request.getPdfPath());
existing.setVideoPath(request.getVideoPath());
existing.setUpdatedAt(now);
contestantMapper.updateById(existing);
resp.put("success", true);
return resp;
}
}
@Override
public ContestantDTO getContestantByID(String id) {
if (id == null) {
throw new BusinessException("id is required.");
}
Contestant existing = contestantMapper.selectById(id);
if (existing == null) {
return null;
}
ContestantDTO dto = new ContestantDTO();
// dto.setEmail(existing.getEmail());
dto.setFirstName(existing.getFirstName());
dto.setLastName(existing.getLastName());
dto.setGender(existing.getGender());
dto.setOccupation(existing.getOccupation());
dto.setAge(existing.getAge());
dto.setCountryRegionCity(existing.getCountryRegionCity());
dto.setPhoneNumber(existing.getPhoneNumber());
dto.setDesignTitle(existing.getDesignTitle());
dto.setDesignDescription(existing.getDesignDescription());
dto.setPdfPath(existing.getPdfPath());
dto.setVideoPath(existing.getVideoPath());
return dto;
}
/**
* 检查邮箱是否符合申请要求,发送验证码
* @param email AiDA邮箱
*/
public void checkEmail(String email) {
List<Integer> validRole = Arrays.asList(1, 2, 7, 8);
// 1. 验证邮箱在aida中有无账号
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Account::getUserEmail, email);
List<Account> accounts = accountMapper.selectList(queryWrapper);
if (accounts.isEmpty()) {
throw new BusinessException("Please register and subscribe to AiDA, then resubmit your application.");
}
// 2. 验证账号是否是付费用户如果首次提交是但是修改的时候已经不是了how?不允许修改吗)
if (validRole.contains(accounts.getFirst().getSystemUser())) {
String randomVerifyCode = RandomsUtil.generateVerifyCode(100000L, 999999L);
LocalCacheUtils.setVerifyCodeCache(
AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + "_" + email, randomVerifyCode);
SendEmailUtil.send(email, null,
SendEmailUtil.LOGIN_TEMPLATE_ID, randomVerifyCode);
} else {
throw new BusinessException("Please subscribe to AiDA, then resubmit your application.");
}
}
/**
* 验证验证码是否正确
* @param email 邮箱
* @param otp 一次性验证码
* @return 临时token和之前提交的表单内容
*/
public CheckOTPVO checkCode(String email, String otp) {
String otpCache = LocalCacheUtils.getVerifyCodeCache(AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + "_" + email);
assert otpCache != null;
if (otpCache.equals(otp)) {
// 1. 生成唯一token
String secureToken = UUID.randomUUID().toString().replace("-", "");
redisUtil.addToString(tokenCacheKey + email, secureToken, 3 * 24 * 60 * 60L);
return new CheckOTPVO(secureToken, getContestantByID(email));
} else {
throw new BusinessException("Verification code is incorrect. Please try again.");
}
}
public void checkSecurityToken(String email, String securityToken) {
String key = tokenCacheKey + email;
if (StringUtils.isBlank(securityToken)) {
log.error("security token 缺失");
throw new BusinessException("Please complete email verification first.");
}
String tokenCache = redisUtil.getFromString(key);
if (StringUtils.isBlank(tokenCache)) {
log.error("security token 过期");
throw new BusinessException("Email verification has expired. Please verify again.");
} else if (!tokenCache.equals(securityToken)){
log.error("security token 与缓存不符");
throw new BusinessException("Identity verification failed. Please complete email verification first.");
}
}
// 发送站内信
public void sendSiteMsg(String applicationId, String email) {
Long userId;
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Account::getUserEmail, email);
List<Account> accounts = accountMapper.selectList(queryWrapper);
if (accounts.isEmpty()) {
throw new BusinessException("Please register and subscribe to AiDA, then resubmit your application.");
}else {
userId = accounts.get(0).getId();
}
PublishSysNotificationDTO sysNotificationDTO = new PublishSysNotificationDTO();
Notification notification = new Notification();
notification.setType("system");
notification.setReceiverId(userId);
sysNotificationDTO.setTitle("System Notification 系统通知");
// todo
sysNotificationDTO.setContent(link + applicationId);
notification.setContent(JSON.toJSONString(sysNotificationDTO));
notification.setIsRead(0);
notification.setCreateTime(LocalDateTime.now());
notificationMapper.insert(notification);
// 这里推送消息是在接受到视频生成结束后发生的所以UserContext中没有用户信息
messageCenterService.pushMessage("system", userId);
}
}

View File

@@ -194,6 +194,8 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
if (!type.equals("system")) { if (!type.equals("system")) {
// 个人未读消息 // 个人未读消息
count = getUnreadCountByType(type, receiverId); count = getUnreadCountByType(type, receiverId);
} else if (Objects.isNull(receiverId)) {
count = 1L;
} else { } else {
// 系统未读消息 // 系统未读消息
count = getUnreadSystemNotification(receiverId); count = getUnreadSystemNotification(receiverId);
@@ -253,12 +255,14 @@ public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, No
// 计算总的系统通知数量 // 计算总的系统通知数量
QueryWrapper<Notification> queryWrapper = new QueryWrapper<>(); QueryWrapper<Notification> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Notification::getType, "system") queryWrapper.lambda().eq(Notification::getType, "system")
.gt(Notification::getCreateTime, account.getCreateDate())
.and(wrapper -> wrapper .and(wrapper -> wrapper
.isNull(Notification::getReceiverId) .isNull(Notification::getReceiverId)
.or() .or()
.eq(Notification::getReceiverId, receiverId) .eq(Notification::getReceiverId, receiverId)
); );
if (Objects.nonNull(account)) {
queryWrapper.lambda().gt(Notification::getCreateTime, account.getCreateDate());
}
Long totalSysCount = baseMapper.selectCount(queryWrapper); Long totalSysCount = baseMapper.selectCount(queryWrapper);
// 计算单个用户读了多少条系统数据 // 计算单个用户读了多少条系统数据

View File

@@ -258,6 +258,11 @@ public class PanToneServiceImpl extends ServiceImpl<PanToneMapper, PanTone> impl
d.setH(getRgbByHsvBatchDTO.getH()); d.setH(getRgbByHsvBatchDTO.getH());
d.setS(getRgbByHsvBatchDTO.getS()); d.setS(getRgbByHsvBatchDTO.getS());
d.setV(getRgbByHsvBatchDTO.getV()); d.setV(getRgbByHsvBatchDTO.getV());
// 不使用数据库中存储的RGB值使用通过hsv计算得到的RGB值
int[] rgb = PantoneUtils.hsvToRgb(d.getH(), d.getS(), d.getV());
d.setR(rgb[0]);
d.setG(rgb[1]);
d.setB(rgb[2]);
} }
}); });
Map<Integer, PantoneVO> valueToPantoneVo = templateResposne.stream().collect(Collectors.toMap( Map<Integer, PantoneVO> valueToPantoneVo = templateResposne.stream().collect(Collectors.toMap(

View File

@@ -0,0 +1,93 @@
package com.ai.da.service.upload;
import com.ai.da.model.dto.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 分片上传服务接口
* 提供PDF和视频文件的分片上传、断点续传功能
*/
public interface UploadService {
// ===== PDF上传相关 =====
/**
* 初始化PDF上传任务
* @param request 初始化请求
* @return 上传任务信息
*/
UploadTask initPdfUpload(UploadInitRequest request);
/**
* 上传PDF分片
* @param uploadId 上传任务ID
* @param chunk 分片文件
* @param chunkIndex 分片索引
* @param totalChunks 总分片数
* @return 分片上传结果
*/
UploadChunkResponse uploadPdfChunk(String uploadId, MultipartFile chunk,
int chunkIndex, int totalChunks);
/**
* 完成PDF上传异步合并并上传到MinIO
* @param uploadId 上传任务ID
* @param fileName 文件名
* @param totalSize 文件总大小
* @return 完成上传结果
*/
UploadCompleteResponse completePdfUpload(String uploadId, String fileName, long totalSize, String email, String secureToken);
/**
* 查询PDF上传状态
* @param uploadId 上传任务ID
* @return 上传状态信息
*/
UploadStatusResponse getPdfUploadStatus(String uploadId);
// ===== 视频上传相关 =====
/**
* 初始化视频上传任务
* @param request 初始化请求
* @return 上传任务信息
*/
UploadTask initVideoUpload(UploadInitRequest request);
/**
* 上传视频分片
* @param uploadId 上传任务ID
* @param chunk 分片文件
* @param chunkIndex 分片索引
* @param totalChunks 总分片数
* @return 分片上传结果
*/
UploadChunkResponse uploadVideoChunk(String uploadId, MultipartFile chunk,
int chunkIndex, int totalChunks);
/**
* 完成视频上传异步合并并上传到MinIO
* @param uploadId 上传任务ID
* @param fileName 文件名
* @param totalSize 文件总大小
* @return 完成上传结果
*/
UploadCompleteResponse completeVideoUpload(String uploadId, String fileName, long totalSize, String email, String secureToken);
/**
* 查询视频上传状态
* @param uploadId 上传任务ID
* @return 上传状态信息
*/
UploadStatusResponse getVideoUploadStatus(String uploadId);
// ===== 通用功能 =====
/**
* 清理过期上传任务
*/
void cleanupExpiredUploads();
}

View File

@@ -0,0 +1,111 @@
package com.ai.da.service.upload;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Set;
/**
* 上传任务实体类
* 用于管理分片上传的状态和元数据
*/
@Data
@Builder
public class UploadTask {
/**
* 上传任务唯一标识
*/
private String uploadId;
/**
* 文件名
*/
private String fileName;
/**
* 文件类型 (pdf/video)
*/
private String fileType;
/**
* 文件总大小(字节)
*/
private Long fileSize;
/**
* 用户邮箱
*/
private String email;
/**
* 总分片数
*/
private Integer totalChunks;
/**
* 分片大小(字节)
*/
private Integer chunkSize;
/**
* 已上传分片索引集合
*/
private Set<Integer> uploadedChunks;
/**
* 上传状态
*/
private UploadStatus status;
/**
* 任务创建时间
*/
private LocalDateTime createdAt;
/**
* 任务过期时间
*/
private LocalDateTime expiresAt;
/**
* 最终文件在MinIO中的路径
*/
private String finalPath;
/**
* 上传状态枚举
*/
public enum UploadStatus {
/**
* 已初始化,等待上传分片
*/
INITIATED,
/**
* 正在上传分片
*/
UPLOADING,
/**
* 正在处理文件(合并分片、上传到存储)
*/
PROCESSING,
/**
* 上传完成
*/
COMPLETED,
/**
* 上传失败
*/
FAILED,
/**
* 任务过期
*/
EXPIRED
}
}

View File

@@ -0,0 +1,598 @@
package com.ai.da.service.upload.impl;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.utils.MinioUtil;
import com.ai.da.model.dto.*;
import com.ai.da.service.upload.UploadService;
import com.ai.da.service.upload.UploadTask;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 分片上传服务实现类
*/
@Service
@Slf4j
public class UploadServiceImpl implements UploadService {
// ===== 配置参数 =====
@Value("${file.upload.temp.dir:temp/uploads}")
private String tempDir;
// PDF分片大小1MB
@Value("${file.upload.chunk.size.pdf:1048576}")
private int pdfChunkSize;
// 视频分片大小2MB
@Value("${file.upload.chunk.size.video:2097152}")
private int videoChunkSize;
// 文件大小限制
@Value("${file.upload.max.size.pdf:20971520}") // PDF: 20MB
private long maxPdfSize;
@Value("${file.upload.max.size.video:104857600}") // 视频: 100MB
private long maxVideoSize;
@Resource
private MinioUtil minioUtil;
@Value("${minio.bucketName.globalAward:global-award}")
private String minioBucket;
@Resource
private com.ai.da.service.GlobalAwardService globalAwardService;
// 内存存储上传任务状态
private final ConcurrentHashMap<String, UploadTask> uploadTasks = new ConcurrentHashMap<>();
// JSON序列化工具
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 应用启动时加载现有上传任务
*/
@PostConstruct
public void loadExistingTasks() {
try {
Path tempPath = Paths.get(tempDir);
if (!Files.exists(tempPath)) {
return;
}
Files.list(tempPath)
.filter(Files::isDirectory)
.forEach(uploadDir -> {
try {
String uploadId = uploadDir.getFileName().toString();
Path metadataPath = uploadDir.resolve("metadata.json");
if (Files.exists(metadataPath)) {
String json = Files.readString(metadataPath);
UploadTask task = objectMapper.readValue(json, UploadTask.class);
// 检查任务是否已过期
if (task.getExpiresAt().isAfter(LocalDateTime.now())) {
uploadTasks.put(uploadId, task);
log.info("加载现有上传任务: uploadId={}, status={}", uploadId, task.getStatus());
} else {
// 清理过期任务
cleanupTempFiles(uploadId);
log.info("清理过期上传任务: uploadId={}", uploadId);
}
}
} catch (Exception e) {
log.warn("加载上传任务失败: {}", uploadDir.getFileName(), e);
}
});
log.info("成功加载 {} 个现有上传任务", uploadTasks.size());
} catch (Exception e) {
log.error("加载现有上传任务时发生错误", e);
}
}
// ===== PDF上传实现 =====
@Override
public UploadTask initPdfUpload(UploadInitRequest request) {
// 验证安全令牌
globalAwardService.checkSecurityToken(request.getEmail(), request.getSecureToken());
// 验证PDF文件
validatePdfFile(request);
// 创建上传任务
String uploadId = UUID.randomUUID().toString();
int totalChunks = (int) Math.ceil((double) request.getFileSize() / pdfChunkSize);
UploadTask task = createUploadTask(request, uploadId, totalChunks, pdfChunkSize, "pdf");
// 创建临时目录并保存任务状态
createTempDirectory(uploadId);
uploadTasks.put(uploadId, task);
saveTaskMetadata(task);
log.info("PDF上传任务初始化完成: uploadId={}, totalChunks={}, fileSize={}",
uploadId, totalChunks, request.getFileSize());
return task;
}
@Override
public UploadChunkResponse uploadPdfChunk(String uploadId, MultipartFile chunk,
int chunkIndex, int totalChunks) {
// 验证任务状态
UploadTask task = validateAndGetTask(uploadId, "pdf");
// 保存分片到本地
saveChunkToLocal(chunk, uploadId, chunkIndex);
// 更新任务进度
updateTaskProgress(task, chunkIndex);
log.debug("PDF分片上传完成: uploadId={}, chunkIndex={}, size={}",
uploadId, chunkIndex, chunk.getSize());
return UploadChunkResponse.builder()
.chunkIndex(chunkIndex)
.uploaded(true)
.size(chunk.getSize())
.build();
}
@Override
public UploadCompleteResponse completePdfUpload(String uploadId, String fileName, long totalSize, String email, String secureToken) {
// 验证安全令牌
globalAwardService.checkSecurityToken(email, secureToken);
UploadTask task = validateAndGetTask(uploadId, "pdf");
log.info("开始PDF文件合并: uploadId={}, fileName={}", uploadId, fileName);
try {
// 1. 合并所有分片
Path mergedFile = mergeChunks(task);
// 2. 上传到MinIO
String finalPath = uploadToMinio(task, mergedFile, "pdf");
// 3. 更新任务状态并清理
completeTask(task, finalPath);
cleanupTempFiles(uploadId);
log.info("PDF上传完成: uploadId={}, finalPath={}", uploadId, finalPath);
return buildCompleteResponse(task, finalPath, totalSize);
} catch (Exception e) {
log.error("PDF上传失败: uploadId={}", uploadId, e);
task.setStatus(UploadTask.UploadStatus.FAILED);
saveTaskMetadata(task);
throw new BusinessException("File merge failed. Please try again.");
}
}
@Override
public UploadStatusResponse getPdfUploadStatus(String uploadId) {
UploadTask task = uploadTasks.get(uploadId);
if (task == null) {
throw new BusinessException("Upload task not found.");
}
if (!"pdf".equals(task.getFileType())) {
throw new BusinessException("Task type mismatch.");
}
// 计算上传进度
double progress = task.getTotalChunks() > 0 ?
(double) task.getUploadedChunks().size() / task.getTotalChunks() * 100 : 0;
long uploadedSize = task.getUploadedChunks().size() * task.getChunkSize();
return UploadStatusResponse.builder()
.uploadId(uploadId)
.status(task.getStatus().name().toLowerCase())
.progress(Math.min(progress, 100.0))
.uploadedChunks(new HashSet<>(task.getUploadedChunks()))
.totalChunks(task.getTotalChunks())
.totalSize(task.getFileSize())
.uploadedSize(Math.min(uploadedSize, task.getFileSize()))
.build();
}
// ===== 视频上传实现 =====
@Override
public UploadTask initVideoUpload(UploadInitRequest request) {
// 验证安全令牌
globalAwardService.checkSecurityToken(request.getEmail(), request.getSecureToken());
// 验证视频文件
validateVideoFile(request);
// 创建上传任务
String uploadId = UUID.randomUUID().toString();
int totalChunks = (int) Math.ceil((double) request.getFileSize() / videoChunkSize);
UploadTask task = createUploadTask(request, uploadId, totalChunks, videoChunkSize, "video");
// 创建临时目录并保存任务状态
createTempDirectory(uploadId);
uploadTasks.put(uploadId, task);
saveTaskMetadata(task);
log.info("视频上传任务初始化完成: uploadId={}, totalChunks={}, fileSize={}",
uploadId, totalChunks, request.getFileSize());
return task;
}
@Override
public UploadChunkResponse uploadVideoChunk(String uploadId, MultipartFile chunk,
int chunkIndex, int totalChunks) {
// 验证任务状态
UploadTask task = validateAndGetTask(uploadId, "video");
// 保存分片到本地
saveChunkToLocal(chunk, uploadId, chunkIndex);
// 更新任务进度
updateTaskProgress(task, chunkIndex);
log.debug("视频分片上传完成: uploadId={}, chunkIndex={}, size={}",
uploadId, chunkIndex, chunk.getSize());
return UploadChunkResponse.builder()
.chunkIndex(chunkIndex)
.uploaded(true)
.size(chunk.getSize())
.build();
}
@Override
public UploadCompleteResponse completeVideoUpload(String uploadId, String fileName, long totalSize, String email, String secureToken) {
// 验证安全令牌
globalAwardService.checkSecurityToken(email, secureToken);
UploadTask task = validateAndGetTask(uploadId, "video");
log.info("开始视频文件合并: uploadId={}, fileName={}", uploadId, fileName);
try {
// 1. 合并所有分片
Path mergedFile = mergeChunks(task);
// 2. 上传到MinIO
String finalPath = uploadToMinio(task, mergedFile, "video");
// 3. 更新任务状态并清理
completeTask(task, finalPath);
cleanupTempFiles(uploadId);
log.info("视频上传完成: uploadId={}, finalPath={}", uploadId, finalPath);
return buildCompleteResponse(task, finalPath, totalSize);
} catch (Exception e) {
log.error("视频上传失败: uploadId={}", uploadId, e);
task.setStatus(UploadTask.UploadStatus.FAILED);
saveTaskMetadata(task);
throw new BusinessException("File merge failed. Please try again.");
}
}
@Override
public UploadStatusResponse getVideoUploadStatus(String uploadId) {
UploadTask task = uploadTasks.get(uploadId);
if (task == null) {
throw new BusinessException("Upload task not found.");
}
if (!"video".equals(task.getFileType())) {
throw new BusinessException("Task type mismatch.");
}
// 计算上传进度
double progress = task.getTotalChunks() > 0 ?
(double) task.getUploadedChunks().size() / task.getTotalChunks() * 100 : 0;
long uploadedSize = task.getUploadedChunks().size() * task.getChunkSize();
return UploadStatusResponse.builder()
.uploadId(uploadId)
.status(task.getStatus().name().toLowerCase())
.progress(Math.min(progress, 100.0))
.uploadedChunks(new HashSet<>(task.getUploadedChunks()))
.totalChunks(task.getTotalChunks())
.totalSize(task.getFileSize())
.uploadedSize(Math.min(uploadedSize, task.getFileSize()))
.build();
}
// ===== 核心辅助方法 =====
/**
* 验证PDF文件
*/
private void validatePdfFile(UploadInitRequest request) {
if (!"application/pdf".equals(request.getFileType())) {
throw new BusinessException("Only PDF files are allowed.");
}
if (request.getFileSize() > maxPdfSize) {
throw new BusinessException("PDF file size cannot exceed " + (maxPdfSize / 1024 / 1024) + "MB.");
}
}
/**
* 验证视频文件
*/
private void validateVideoFile(UploadInitRequest request) {
if (request.getFileType() == null ||
(!request.getFileType().contains("mp4") &&
!request.getFileType().contains("video") &&
!request.getFileType().contains("avi"))) {
throw new BusinessException("Unsupported video format.");
}
if (request.getFileSize() > maxVideoSize) {
throw new BusinessException("Video file size cannot exceed " + (maxVideoSize / 1024 / 1024) + "MB.");
}
}
/**
* 创建上传任务
*/
private UploadTask createUploadTask(UploadInitRequest request, String uploadId,
int totalChunks, int chunkSize, String fileType) {
return UploadTask.builder()
.uploadId(uploadId)
.fileName(request.getFileName())
.fileType(fileType)
.fileSize(request.getFileSize())
.email(request.getEmail())
.totalChunks(totalChunks)
.chunkSize(chunkSize)
.uploadedChunks(new HashSet<>())
.status(UploadTask.UploadStatus.INITIATED)
.createdAt(LocalDateTime.now())
.expiresAt(LocalDateTime.now().plusHours(24))
.build();
}
/**
* 创建临时目录
*/
private void createTempDirectory(String uploadId) {
try {
Path uploadDir = Paths.get(tempDir, uploadId, "chunks");
Files.createDirectories(uploadDir);
} catch (IOException e) {
throw new BusinessException("Failed to create temporary directory.");
}
}
/**
* 验证并获取上传任务
*/
private UploadTask validateAndGetTask(String uploadId, String expectedType) {
UploadTask task = uploadTasks.get(uploadId);
if (task == null) {
throw new BusinessException("Upload task not found.");
}
if (task.getExpiresAt().isBefore(LocalDateTime.now())) {
task.setStatus(UploadTask.UploadStatus.EXPIRED);
throw new BusinessException("Upload task has expired.");
}
if (!expectedType.equals(task.getFileType())) {
throw new BusinessException("Task type mismatch.");
}
if (task.getStatus() == UploadTask.UploadStatus.COMPLETED ||
task.getStatus() == UploadTask.UploadStatus.FAILED) {
throw new BusinessException("Task has already been completed or failed.");
}
return task;
}
/**
* 保存分片到本地
*/
private void saveChunkToLocal(MultipartFile chunk, String uploadId, int chunkIndex) {
try {
Path chunkPath = Paths.get(tempDir, uploadId, "chunks", "chunk_" + chunkIndex);
Files.copy(chunk.getInputStream(), chunkPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new BusinessException("Failed to save file chunk.");
}
}
/**
* 更新任务进度
*/
private void updateTaskProgress(UploadTask task, int chunkIndex) {
task.getUploadedChunks().add(chunkIndex);
task.setStatus(UploadTask.UploadStatus.UPLOADING);
saveTaskMetadata(task);
}
/**
* 合并分片文件(零拷贝方式)
*/
private Path mergeChunks(UploadTask task) throws IOException {
Path mergedFile = Files.createTempFile("merge_", "_" + task.getFileName());
try (FileChannel outputChannel = FileChannel.open(mergedFile, StandardOpenOption.WRITE)) {
for (int i = 0; i < task.getTotalChunks(); i++) {
Path chunkPath = Paths.get(tempDir, task.getUploadId(), "chunks", "chunk_" + i);
try (FileChannel inputChannel = FileChannel.open(chunkPath, StandardOpenOption.READ)) {
// 零拷贝传输,提升性能
inputChannel.transferTo(0, inputChannel.size(), outputChannel);
}
}
}
return mergedFile;
}
/**
* 上传文件到MinIO
*/
private String uploadToMinio(UploadTask task, Path mergedFile, String fileType) throws Exception {
// 生成MinIO路径: contestants/{email}/{date}/{filename}
String normalizedEmail = normalizeEmail(task.getEmail());
String datePart = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
String ext = getFileExtension(task.getFileName());
String filename = System.currentTimeMillis() + "_" + UUID.randomUUID().toString() + ext;
String relativePath = "contestants/" + normalizedEmail + "/" + datePart + "/" + filename;
// 上传文件
try (FileInputStream fis = new FileInputStream(mergedFile.toFile())) {
minioUtil.getMinioClient().putObject(
PutObjectArgs.builder()
.bucket(minioBucket)
.object(relativePath)
.stream(fis, mergedFile.toFile().length(), -1)
.contentType(getContentType(fileType))
.build()
);
}
return relativePath;
}
/**
* 完成任务
*/
private void completeTask(UploadTask task, String finalPath) {
task.setStatus(UploadTask.UploadStatus.COMPLETED);
task.setFinalPath(finalPath);
saveTaskMetadata(task);
}
/**
* 构建完成响应
*/
private UploadCompleteResponse buildCompleteResponse(UploadTask task, String finalPath, long totalSize) {
// todo:URL是逻辑url
String fileUrl = minioBucket + "/" + finalPath;
return UploadCompleteResponse.builder()
.filePath(finalPath)
.fileUrl(fileUrl)
.fileSize(totalSize)
.build();
}
/**
* 保存任务元数据
*/
private void saveTaskMetadata(UploadTask task) {
try {
Path metadataPath = Paths.get(tempDir, task.getUploadId(), "metadata.json");
String json = objectMapper.writeValueAsString(task);
Files.writeString(metadataPath, json);
} catch (IOException e) {
log.warn("保存任务元数据失败: uploadId={}", task.getUploadId());
}
}
/**
* 清理临时文件
*/
private void cleanupTempFiles(String uploadId) {
try {
Path uploadDir = Paths.get(tempDir, uploadId);
if (Files.exists(uploadDir)) {
Files.walk(uploadDir)
.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
log.warn("删除临时文件失败: {}", path);
}
});
}
} catch (Exception e) {
log.warn("清理临时文件失败: uploadId={}", uploadId);
}
}
/**
* 清理过期上传任务(每小时执行一次)
*/
@Scheduled(fixedDelay = 3600000) // 1小时
public void cleanupExpiredUploads() {
LocalDateTime now = LocalDateTime.now();
uploadTasks.entrySet().removeIf(entry -> {
UploadTask task = entry.getValue();
if (task.getExpiresAt().isBefore(now)) {
cleanupTempFiles(task.getUploadId());
return true;
}
return false;
});
}
// ===== 工具方法 =====
/**
* 标准化邮箱地址
*/
private String normalizeEmail(String email) {
if (email == null) {
return "anonymous";
}
return email.replaceAll("[^a-zA-Z0-9]", "_");
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String fileName) {
if (fileName != null && fileName.contains(".")) {
return fileName.substring(fileName.lastIndexOf('.'));
}
return "";
}
/**
* 获取文件MIME类型
*/
private String getContentType(String fileType) {
switch (fileType.toLowerCase()) {
case "pdf":
return "application/pdf";
case "video":
return "video/mp4";
default:
return "application/octet-stream";
}
}
}

View File

@@ -22,7 +22,7 @@ spring.security.jwtExpiration=8640000000
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\ 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/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/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/subscription_plan/** /api/stripe/**,/api/message/**,/api/tags/**,/notification/**,/api/affiliate/**,/api/project/**,/api/llm/**, /api/subscription_plan/**,/api/global-award/**
spring.security.authApi=/auth/login spring.security.authApi=/auth/login
@@ -63,6 +63,7 @@ minio.bucketName.gradient=aida-gradient
minio.bucketName.modifiedSketch=aida-modified-sketch minio.bucketName.modifiedSketch=aida-modified-sketch
minio.bucketName.slogan=aida-slogan minio.bucketName.slogan=aida-slogan
minio.bucketName.partialDesign=aida-partial-design minio.bucketName.partialDesign=aida-partial-design
minio.bucketName.globalAward=global-award
redirect_url=http://18.167.251.121:7788 redirect_url=http://18.167.251.121:7788
spring.rabbitmq.host=18.167.251.121 spring.rabbitmq.host=18.167.251.121
@@ -159,3 +160,25 @@ 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 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
# ===== 分片上传配置 =====
# 临时文件目录
file.upload.temp.dir=temp/uploads
# 分片大小配置
# PDF分片大小1MB
file.upload.chunk.size.pdf=1048576
# 视频分片大小2MB
file.upload.chunk.size.video=2097152
# 文件大小限制
# PDF最大文件大小20MB
file.upload.max.size.pdf=20971520
# 视频最大文件大小100MB
file.upload.max.size.video=104857600
# 上传任务过期时间(小时)
file.upload.task.expiry.hours=24
global.award.link=https://develop.aida.com.hk/award/contestants?id=

View File

@@ -22,7 +22,7 @@ spring.security.jwtExpiration=604800000
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\ 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/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/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/subscription_plan/** /api/stripe/**,/api/message/**,/api/tags/**,/notification/**,/api/affiliate/**,/api/project/**,/api/llm/**, /api/subscription_plan/**,/api/global-award/**
spring.security.authApi=/auth/login spring.security.authApi=/auth/login
@@ -63,6 +63,7 @@ minio.bucketName.gradient=aida-gradient
minio.bucketName.modifiedSketch=aida-modified-sketch minio.bucketName.modifiedSketch=aida-modified-sketch
minio.bucketName.slogan=aida-slogan minio.bucketName.slogan=aida-slogan
minio.bucketName.partialDesign=aida-partial-design minio.bucketName.partialDesign=aida-partial-design
minio.bucketName.globalAward=global-award
redirect_url=http://18.167.251.121:7788 redirect_url=http://18.167.251.121:7788
spring.rabbitmq.host=18.167.251.121 spring.rabbitmq.host=18.167.251.121
@@ -157,3 +158,25 @@ google.client.id=29310152396-nnsd3h533fld665oguu8ovrt1nukmt46.apps.googleusercon
google.client.secret=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 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 design.callback.url=https://api.aida.com.hk/api/third/party/receiveDesignResults
# ===== 分片上传配置 =====
# 临时文件目录
file.upload.temp.dir=temp/uploads
# 分片大小配置
# PDF分片大小1MB
file.upload.chunk.size.pdf=1048576
# 视频分片大小2MB
file.upload.chunk.size.video=2097152
# 文件大小限制
# PDF最大文件大小20MB
file.upload.max.size.pdf=20971520
# 视频最大文件大小100MB
file.upload.max.size.video=104857600
# 上传任务过期时间(小时)
file.upload.task.expiry.hours=24
global.award.link=https://www.aida.com.hk/award/contestants?id=

View File

@@ -214,6 +214,7 @@ the.subscription.end.date.can.be.extended.only.not.reduced=The subscription end
total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts=Total sub-account quota cannot be lower than existing sub-accounts. total.sub-account.quota.cannot.be.lower.than.existing.sub-accounts=Total sub-account quota cannot be lower than existing sub-accounts.
the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used=The credit limit set cannot be lower than the amount of credits already used. the.credit.limit.set.cannot.be.lower.than.the.amount.of.credits.already.used=The credit limit set cannot be lower than the amount of credits already used.
administrator.user.is.already.bound.to.different.organization=This administrator user is already bound to a subscription plan of a different organization. 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: # Informative:

View File

@@ -210,6 +210,7 @@ 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=该管理员用户已与其他组织的订阅计划绑定 administrator.user.is.already.bound.to.different.organization=该管理员用户已与其他组织的订阅计划绑定
required.partialDesign=修改单套搭配必须提供'partialDesign'(base64 或 path)
# 可能会报异常 # 可能会报异常
# Informative: # Informative:

View File

@@ -27,20 +27,20 @@ paypal.webhook_id=1D107312EX592781K
##### Stripe ##### Stripe
# developer # developer
#stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2 stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
# dev 端点 # dev 端点
#stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
# local 端点 # local 端点
#stripe.webhook-sign-secret=whsec_TJcMSnAkh4uktrNY1M6Iy8XaVze4Rzqm #stripe.webhook-sign-secret=whsec_TJcMSnAkh4uktrNY1M6Iy8XaVze4Rzqm
# kim - test # kim - test
stripe.private-key=sk_test_51LwPrxH7nPZ8bkrNj67TFD7sxucaTANs1lf0KGSu1QSJfxYXcnigq2wTaZyZzST7y0fMbhhvaJZ4LjjFhr95M83a00eXrmOTL0 #stripe.private-key=sk_test_51LwPrxH7nPZ8bkrNj67TFD7sxucaTANs1lf0KGSu1QSJfxYXcnigq2wTaZyZzST7y0fMbhhvaJZ4LjjFhr95M83a00eXrmOTL0
# prod 端点 # prod 端点
#stripe.webhook-sign-secret=whsec_GoyVEAaBtuGD5Rt55z83JnPnLDAZTN3u #stripe.webhook-sign-secret=whsec_GoyVEAaBtuGD5Rt55z83JnPnLDAZTN3u
# local 端点 # local 端点
#stripe.webhook-sign-secret=whsec_NvwM3hDQiN5GXclYOYekE9IKHLjmROF8 #stripe.webhook-sign-secret=whsec_NvwM3hDQiN5GXclYOYekE9IKHLjmROF8
# dev 端点 # dev 端点
stripe.webhook-sign-secret=whsec_pX0pPMQm85PaUSWnFMEzoccb3MGNkjoL #stripe.webhook-sign-secret=whsec_pX0pPMQm85PaUSWnFMEzoccb3MGNkjoL
# kim - live # kim - live
#stripe.private-key=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 #stripe.private-key=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