203 Commits

Author SHA1 Message Date
litianxiang
d700f94f9d flux test 2026-05-14 16:36:55 +08:00
litianxiang
b277479e73 豆包模型更换 2026-05-13 20:52:28 +08:00
litianxiang
83cbd57dea 登录鉴权按照Source判断id来自于何处 2026-05-13 09:40:30 +08:00
litianxiang
4d3b22de82 买家端需要的获取商家主页和模糊搜索接口 2026-05-11 16:40:44 +08:00
litianxiang
6b5c2cfec0 日志
订单表字段改名
视频返回新增字段
2026-05-06 16:58:46 +08:00
litianxiang
b676de054a 新工作流 2026-05-06 14:37:42 +08:00
litianxiang
4c169ef67e seller产品图处加入视频 2026-05-06 13:30:21 +08:00
litianxiang
f2bce066b6 seller产品图编辑 2026-05-06 10:46:14 +08:00
litianxiang
6af442eb15 服务端口号与宿主机统一,方便本地调试不需要修改bootstrap 2026-05-04 14:21:35 +08:00
litianxiang
768df55309 服务端口号与宿主机统一,方便本地调试不需要修改bootstrap 2026-05-04 14:19:37 +08:00
litianxiang
f351277b73 nacos注册配置 2026-05-04 10:27:20 +08:00
litianxiang
a799162ea4 工作流 2026-04-28 16:40:56 +08:00
litianxiang
c035eb9d7d bootstrap配置 2026-04-28 14:38:00 +08:00
litianxiang
906a54b3c8 工作流配置 2026-04-28 13:58:46 +08:00
litianxiang
643799546b 工作流配置 2026-04-28 13:42:24 +08:00
litianxiang
f582464cd3 工作流配置 2026-04-28 13:37:47 +08:00
litianxiang
b864b393bc 1 2026-04-28 13:21:12 +08:00
litianxiang
c03a8762e7 商品bug 2026-04-28 09:39:26 +08:00
litianxiang
cb87ad1099 host配置 2026-04-27 16:43:18 +08:00
litianxiang
fb229764f8 host配置 2026-04-27 16:29:54 +08:00
litianxiang
8bec1f842d 工作流配置 2026-04-27 16:10:01 +08:00
litianxiang
b54bd04cff 端口号冲突 2026-04-27 15:00:28 +08:00
litianxiang
b4ccad6242 服务名修改 2026-04-27 14:30:29 +08:00
litianxiang
6068bf7d7d 微服务改造 2026-04-27 13:54:03 +08:00
litianxiang
d36baf747f 微服务改造 2026-04-27 11:47:15 +08:00
7c8f1bee6a 添加 .gitea/workflows/develop_3.1_MS_build_manual.yaml 2026-04-24 13:40:03 +08:00
litianxiang
62bd145e2c 微服务改造 2026-04-23 10:52:49 +08:00
litianxiang
23716984cc 微服务改造 2026-04-22 15:54:42 +08:00
litianxiang
d0b8b8d674 微服务改造 2026-04-22 11:16:03 +08:00
litianxiang
92e7dbf258 微服务改造 2026-04-22 11:15:34 +08:00
litianxiang
32a228485b token保持一致 2026-04-20 10:26:04 +08:00
litianxiang
560e47747a 微服务改造 2026-04-16 15:51:05 +08:00
litianxiang
c8dc38575a Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-04-15 14:52:35 +08:00
litianxiang
c00d906083 portfolioUrl 漏加fix 2026-04-15 14:52:18 +08:00
4df3f9cc53 BUGFIX:design印花scale与getDetail获取到的印花scale不一致 2026-04-15 14:43:59 +08:00
litianxiang
b0343be544 配置过滤器 2026-04-15 14:05:57 +08:00
litianxiang
d33cb9f0bf 配置过滤器 2026-04-15 13:47:28 +08:00
litianxiang
40f2735831 配置过滤器 2026-04-15 13:26:15 +08:00
01d3806d5f Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-04-13 17:52:35 +08:00
107e4e9771 BUGFIX:design 没有使用printboard中的元素 2026-04-13 17:51:15 +08:00
litianxiang
716d720782 GlobalAward返回最大的参赛者编号 2026-04-13 14:38:02 +08:00
litianxiang
6b5bacc49b GlobalAward返回最大的参赛者编号 2026-04-13 14:34:05 +08:00
litianxiang
409bc7b1fd 过滤器配置 2026-04-13 13:09:12 +08:00
litianxiang
ec6a5df8af TO DEV 2026-04-13 11:55:17 +08:00
litianxiang
029b96ae99 GlobalAward下载到浏览器 2026-04-13 11:47:20 +08:00
litianxiang
14002e7331 GlobalAward下载补充和数量接口 2026-04-13 10:22:43 +08:00
14dfe2806c merge dev 2026-04-10 23:27:37 +08:00
798c7b0592 Merge branch 'release/3.1' into dev/3.1_release_merge 2026-04-10 23:09:33 +08:00
9bd10581f4 BUGFIX:获取relight结果时删除了排序记录 2026-04-10 23:03:15 +08:00
1f288fe5e3 BUGFIX 2026-04-10 22:55:44 +08:00
72602eb245 BUGFIX 2026-04-10 22:51:00 +08:00
983d53268d DEBUG 2026-04-10 22:32:55 +08:00
f3aeeb3584 DEBUG 2026-04-10 22:21:56 +08:00
5d3692a204 BUGFIX:支付失败后的邮件通知类型错误(临时处理) 2026-04-08 13:52:38 +08:00
f2a074b2f6 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-03-31 15:19:56 +08:00
6a7a37dcec BUGFIX:只有Printboard时,首次design没有使用元素 2026-03-31 14:12:43 +08:00
litianxiang
c4d2780f0e TO DEV 2026-03-31 13:55:32 +08:00
litianxiang
1da6b7728c Merge remote-tracking branch 'origin/dev/3.1_release_merge' into release/3.1 2026-03-30 17:05:43 +08:00
litianxiang
0faf77899b fix:PRINTBOARD_HIGH_I2I与PRINTBOARD_ADVANCED_I2I使用模型一致导致积分扣除错误 2026-03-27 16:49:10 +08:00
litianxiang
e4940019bf 框选适配py 2026-03-27 15:19:38 +08:00
litianxiang
0da66ff210 print t2i模型替换 2026-03-27 15:16:33 +08:00
litianxiang
5dd862ff79 MOOD_BOARD high 去掉翻译 2026-03-26 16:13:43 +08:00
litianxiang
edaec9884d TO PROD 2026-03-25 22:28:44 +08:00
litianxiang
76eeb2be53 moodboard基础模型修改 2026-03-25 10:39:22 +08:00
litianxiang
cb6f94d2d4 py api fix 2026-03-25 10:19:06 +08:00
litianxiang
28656c44c8 FIX FLUX2 2026-03-24 16:24:43 +08:00
litianxiang
6757a89d04 Pattern模式参数fix 2026-03-24 15:54:53 +08:00
litianxiang
9be1a1e307 加锁解决不同线程读取前还未保存的问题 2026-03-24 15:49:16 +08:00
litianxiang
2168978f61 print pattern也改为flux2 2026-03-24 15:32:06 +08:00
litianxiang
54466b935d debug 2026-03-24 15:23:33 +08:00
litianxiang
c970ebe691 debug 2026-03-24 15:15:59 +08:00
litianxiang
1c5a3a12b9 debug 2026-03-24 15:04:40 +08:00
litianxiang
6e06000083 debug 2026-03-24 14:46:01 +08:00
litianxiang
dea2b3be42 debug 2026-03-24 14:29:08 +08:00
litianxiang
bcf51aea23 debug 2026-03-24 14:20:39 +08:00
litianxiang
0c9d5404c6 flux2失败状态判断 2026-03-24 14:05:27 +08:00
litianxiang
93429839c0 本地flux改为flux2 2026-03-24 13:39:38 +08:00
litianxiang
27859c3e28 Merge remote-tracking branch 'origin/release/3.1' into dev/3.1_release_merge 2026-03-23 14:11:33 +08:00
litianxiang
f02c0930a6 日志切面(controller层报错打印) 2026-03-23 13:56:47 +08:00
litianxiang
d57bb83b25 Merge remote-tracking branch 'origin/release/3.1' into release/3.1 2026-03-23 13:50:44 +08:00
731e34f133 TO DEV 2026-03-23 13:38:10 +08:00
75eca8d6ba Merge branch 'release/3.1' into dev/3.1_release_merge
# Conflicts:
#	src/main/java/com/ai/da/python/PythonService.java
2026-03-23 13:22:18 +08:00
3e53401f76 TASK:返回符合查询条件的金额总计 2026-03-23 11:55:07 +08:00
litianxiang
b6a068ebcd SKETCHBOARD传入的text改为获取第一个,为分割获取style的方式 2026-03-23 11:50:24 +08:00
litianxiang
dc291ea086 加入非空校验 2026-03-14 01:50:39 +08:00
litianxiang
2e846e671a romantic风格缺少下装fix 2026-03-13 10:06:26 +08:00
litianxiang
a5093311f9 当style为洛丽塔时无sketch和谷歌风控问题FIX 2026-03-12 19:06:54 +08:00
litianxiang
aed338a6d7 当style为洛丽塔时无sketch和谷歌风控问题FIX 2026-03-12 18:59:17 +08:00
litianxiang
8bdb49d25c 当style为洛丽塔时无sketch和谷歌风控问题FIX 2026-03-12 18:49:57 +08:00
litianxiang
5d53a8cd42 当style为洛丽塔时无sketch和谷歌风控问题FIX 2026-03-12 18:49:28 +08:00
litianxiang
61b7f3072f 当style为洛丽塔时无sketch和谷歌风控问题FIX 2026-03-12 18:36:21 +08:00
litianxiang
a1f489f3a1 比赛url修改 2026-03-12 17:39:05 +08:00
litianxiang
fc3fd877a8 transpose和rotate获取位置修改 2026-03-05 16:58:17 +08:00
litianxiang
fc72d2c430 transpose和rotate获取位置修改 2026-03-05 13:29:14 +08:00
litianxiang
1ac01dd090 测试token恢复 2026-02-25 16:36:06 +08:00
litianxiang
3bbdf7c672 fix:按编号导出参赛选手文件 2026-02-09 10:33:25 +08:00
litianxiang
0646484fba 按编号导出参赛选手文件 2026-02-09 10:21:40 +08:00
litianxiang
96b8613741 映射temp到服务器 2026-02-06 17:29:42 +08:00
litianxiang
cf30226a51 映射temp到服务器 2026-02-06 17:24:43 +08:00
litianxiang
3c15a3ff68 映射temp到服务器 2026-02-06 17:21:07 +08:00
litianxiang
0c904be227 测试临时token 2026-02-06 11:42:34 +08:00
litianxiang
7759b56123 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-02-05 17:39:15 +08:00
litianxiang
d5bfaa8822 fix:允许图生图不带提示词 2026-02-05 17:38:38 +08:00
967c0cbc01 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-02-05 16:55:24 +08:00
417e34b41a BUGFIX:1.首次design没有使用library和生成的印花2.design overall印花太小 2026-02-05 16:54:45 +08:00
litianxiang
d51aa84647 fix:参赛者根据链接返回文件参数 2026-02-05 09:41:10 +08:00
litianxiang
5895bc6ab6 Revert "fix:参赛者根据链接返回文件参数"
This reverts commit 3301869f20.
2026-02-05 09:40:35 +08:00
litianxiang
3301869f20 fix:参赛者根据链接返回文件参数 2026-02-05 09:40:15 +08:00
litianxiang
1ec42f4ad5 fix:参赛者id逻辑更改 2026-02-04 17:20:22 +08:00
cc506ff7e9 Merge branch 'dev/3.1_release_merge' into release/3.1 2026-02-04 17:05:59 +08:00
litianxiang
f2d43f06f4 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev/3.1_release_merge 2026-02-04 15:28:08 +08:00
litianxiang
9251df49f8 比赛新增文件大小和视频时长 2026-02-04 15:27:51 +08:00
430156f4e8 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-02-04 14:48:38 +08:00
litianxiang
d1123aedcc fix:导出为页面下载 2026-02-04 14:45:11 +08:00
8c007077a3 BUGFIX: detail中的merge模式下没有存储partialDesign的图片 2026-02-04 14:43:29 +08:00
litianxiang
d63b4b4e63 fix:参赛选手加入编号 2026-02-04 14:03:30 +08:00
litianxiang
b826f0bf39 参赛选手加入编号,增加导出功能 2026-02-04 13:41:16 +08:00
litianxiang
1decd8e258 参赛选手加入编号,增加导出功能 2026-02-04 13:41:09 +08:00
litianxiang
1286e84488 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev-ltx 2026-02-04 13:21:19 +08:00
a252fdf7f9 BUGFIX: detail中的default模式报错 2026-02-03 16:56:15 +08:00
807d802178 TASK:motion 参数数据类型变更 2026-02-02 15:30:04 +08:00
53f1b548be CONFIG 2026-02-02 15:28:54 +08:00
45dd78032a BUGFIX: 1.token过期,重新登录无法解决 2.motion生成参数数据类型变更 2026-02-02 15:04:27 +08:00
c160da5132 BUGFIX: token过期,重新登录无法解决 2026-02-02 14:57:32 +08:00
b23faeeee2 Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-02-02 13:32:55 +08:00
67789abca4 TASK:getAllPose id的数据类型改为整型 2026-02-02 13:32:27 +08:00
1c78d66aab Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-30 17:14:50 +08:00
528bc69923 BUGFIX: design single merge模式下取消传递print及elements等元素 2026-01-30 17:10:37 +08:00
litianxiang
22880d128d Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev-ltx 2026-01-30 15:40:25 +08:00
9c56a102cc Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-29 14:38:20 +08:00
2f59fe074f BUGFIX: 管理员修改用户身份为游客 2026-01-29 14:37:54 +08:00
9c61b1c8fe Merge branch 'dev/dev_xp' into dev/3.1_release_merge 2026-01-29 10:12:47 +08:00
e30fdf7401 BUGFIX:模特上传,事务管理不统一导致library出现孤儿数据 2026-01-29 10:10:21 +08:00
ba2d10afbc paymentMethodConfiguration切换 2026-01-28 15:41:52 +08:00
6146112d04 TO PROD 2026-01-27 16:39:39 +08:00
412550df27 BUGFIX:管理员查design频次与chart中获取到的数量有区别 2026-01-27 14:59:39 +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
3e334d7956 Merge remote-tracking branch 'origin/dev/3.1_release_merge' into dev-ltx 2026-01-26 11:16:46 +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
107 changed files with 13335 additions and 10630 deletions

View File

@@ -0,0 +1,111 @@
name: 手动 AiDA back-java 开发分支构建部署
on:
workflow_dispatch:
jobs:
build_and_deploy:
runs-on: java21
outputs:
build_status: ${{ job.status }}
build_url: ${{ gitea.server_url }}/${{ gitea.repository.owner.name }}/${{ gitea.repository.name }}/actions/runs/${{ gitea.run_id }}
permissions:
contents: read
packages: write
env:
REMOTE_DEPLOY_PATH: /workspace/workspace_aida/DevelopVersion/develop-MS-version-aida-back
steps:
- name: 0.记录开始时间
id: build_start_time
run: echo "current_time=$(TZ='Asia/Hong_Kong' date '+%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_OUTPUT
- name: 1.检出代码
uses: actions/checkout@v4
with:
ref: dev/3.1_release_merge_MS
- name: 3.缓存 Maven 依赖
uses: actions/cache@v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: 4.构建项目
run: |
java -version
mvn -v
mvn clean package -DskipTests
- name: 5.生成Dockerfile
run: |
echo "===== 生成Dockerfile ====="
cat > Dockerfile << 'EOF'
FROM openjdk:21-ea-21-jdk-slim
VOLUME /tmp
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone
ADD ./target/aida-0.0.1-SNAPSHOT.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
EOF
echo "Dockerfile内容:"
cat Dockerfile
- name: 6.生成docker-compose.yml
run: |
echo "===== 生成docker-compose.yml ====="
cat > docker-compose.yml << 'EOF'
version: '3'
services:
aida_back:
container_name: develop-aida-ms
build: .
volumes:
# 数据挂载
- ./log:/log
- ./temp:/temp
- ./uploads:/temp/uploads
ports:
- '10092:10092'
restart: always
EOF
# 验证docker-compose.yml生成
echo "docker-compose.yml内容:"
cat docker-compose.yml
- name: 7.上传jar到远程服务器
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
port: 22
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
source: "target/*.jar,Dockerfile,docker-compose.yml"
target: ${{ env.REMOTE_DEPLOY_PATH }}
preserve_host_directory_structure: false
- name: 8. 重启 Docker 服务
uses: appleboy/ssh-action@master # 👈 专门执行命令的 action
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
key_base64: true
script: |
echo "========= 进入部署目录 ========="
cd ${{ env.REMOTE_DEPLOY_PATH }}
ls -l
echo "========= 停止旧服务 ========="
docker compose down
echo "========= 启动新服务 ========="
docker compose up -d --build
echo "========= 查看运行状态 ========="
docker compose ps

View File

@@ -99,9 +99,17 @@ jobs:
volumes:
# 数据挂载
- ./log:/log
- ./temp:/temp
- ./uploads:/temp/uploads
ports:
- '10090:5567'
networks:
- aida_java_net
restart: always
networks:
aida_java_net:
external: true
name: aida_java_net
EOF
# 验证docker-compose.yml生成
echo "docker-compose.yml内容:"

70
pom.xml
View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aida</groupId>
@@ -15,7 +15,7 @@
<description>ai da</description>
<properties>
<java.version>21</java.version>
<mybatis.plus.version>3.5.5</mybatis.plus.version>
<mybatis.plus.version>3.5.7</mybatis.plus.version>
<hutool.version>5.8.23</hutool.version>
<wx.java.version>4.2.7.B</wx.java.version>
<fastjson.version>2.0.43</fastjson.version>
@@ -28,6 +28,11 @@
<javacv.version>1.5.5</javacv.version>
<system.windowsx64>windows-x86_64</system.windowsx64>
<javacpp.platform.linux-x86_64>linux-x86_64</javacpp.platform.linux-x86_64>
<!-- Spring Cloud Alibaba 版本 -->
<spring-cloud-alibaba.version>2023.0.3.4</spring-cloud-alibaba.version>
<!-- Spring Cloud 版本 -->
<spring-cloud.version>2023.0.4</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -38,6 +43,22 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud 依赖版本管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba 依赖版本管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
@@ -74,9 +95,14 @@
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -237,6 +263,12 @@
<version>2.15.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
@@ -427,6 +459,38 @@
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- ==================== Spring Cloud Alibaba 微服务相关 ==================== -->
<!-- 启用 bootstrap.yml 加载 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- Nacos 服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- OpenFeign 服务调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud LoadBalancer 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -1,20 +1,24 @@
package com.ai.da;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class AiDaApplication {
public static void main(String[] args) {
SpringApplication.run(AiDaApplication.class, args);
log.info("AiDaApplication 启动完成!");
}
}
package com.ai.da;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@EnableScheduling
@EnableAsync
@EnableFeignClients
@EnableDiscoveryClient
public class AiDaApplication {
public static void main(String[] args) {
SpringApplication.run(AiDaApplication.class, args);
log.info("AiDaApplication 启动完成!");
}
}

View File

@@ -559,83 +559,83 @@ public class GenerateConsumer {
log.info("============ProcessPoseTransformResult End listening==========");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer1(Message msg, Channel channel) {
generate(msg, channel, "consumer 1");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer2(Message msg, Channel channel) {
generate(msg, channel, "consumer 2");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer3(Message msg, Channel channel) {
generate(msg, channel, "consumer 3");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer4(Message msg, Channel channel) {
generate(msg, channel, "consumer 4");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer5(Message msg, Channel channel) {
generate(msg, channel, "consumer 5");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer6(Message msg, Channel channel) {
generate(msg, channel, "consumer 6");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer7(Message msg, Channel channel) {
generate(msg, channel, "consumer 7");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer8(Message msg, Channel channel) {
generate(msg, channel, "consumer 8");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
@RabbitHandler
public void generateConsumer9(Message msg, Channel channel) {
generate(msg, channel, "consumer 9");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.generateResult}")
@RabbitHandler
public void getGenerateResult(Message msg, Channel channel) {
processGenerateResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.toProductImageResult}")
@RabbitHandler
public void getToProductImageResult(Message msg, Channel channel) {
processToProductImageResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.relightResult}")
@RabbitHandler
public void getRelightResult(Message msg, Channel channel) {
processRelightResult(msg, channel);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.poseTransform}")
@RabbitHandler
public void getPoseTransformationResult(Message msg, Channel channel) {
processPoseTransformResult(msg, channel);
}
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer1(Message msg, Channel channel) {
// generate(msg, channel, "consumer 1");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer2(Message msg, Channel channel) {
// generate(msg, channel, "consumer 2");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer3(Message msg, Channel channel) {
// generate(msg, channel, "consumer 3");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer4(Message msg, Channel channel) {
// generate(msg, channel, "consumer 4");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer5(Message msg, Channel channel) {
// generate(msg, channel, "consumer 5");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer6(Message msg, Channel channel) {
// generate(msg, channel, "consumer 6");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer7(Message msg, Channel channel) {
// generate(msg, channel, "consumer 7");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer8(Message msg, Channel channel) {
// generate(msg, channel, "consumer 8");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generate}")
// @RabbitHandler
// public void generateConsumer9(Message msg, Channel channel) {
// generate(msg, channel, "consumer 9");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.generateResult}")
// @RabbitHandler
// public void getGenerateResult(Message msg, Channel channel) {
// processGenerateResult(msg, channel);
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.toProductImageResult}")
// @RabbitHandler
// public void getToProductImageResult(Message msg, Channel channel) {
// processToProductImageResult(msg, channel);
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.relightResult}")
// @RabbitHandler
// public void getRelightResult(Message msg, Channel channel) {
// processRelightResult(msg, channel);
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.poseTransform}")
// @RabbitHandler
// public void getPoseTransformationResult(Message msg, Channel channel) {
// processPoseTransformResult(msg, channel);
// }
// @RabbitListener(queues = "#{rabbitMQProperties.queues.designBatch}")
// @RabbitHandler
// public void getDesignBatchResult(Message msg, Channel channel) {

View File

@@ -28,6 +28,11 @@ public class MQPublisher {
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getSr(), mm);
}
public void sendGenerateResultMessage(String mm) {
log.info("send generate result message: {}", mm);
amqpTemplate.convertAndSend(rabbitMQProperties.getQueues().getGenerateResult(), mm);
}
/**
*
* @param mailParams 含有的字段

View File

@@ -222,16 +222,16 @@ public class SRConsumer {
taskListService.updateTaskStatusOrOutputRedis(uniqueId, "fail", null);
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.sr}")
@RabbitHandler
public void SRConsumer1(Message msg, Channel channel) {
superResolution(msg, channel, "consumer 1");
}
@RabbitListener(queues = "#{rabbitMQProperties.queues.srResult}")
@RabbitHandler
public void SRResultConsumer1(Message msg, Channel channel) {
getSRResult(msg, channel, "consumer 1");
}
// @RabbitListener(queues = "#{rabbitMQProperties.queues.sr}")
// @RabbitHandler
// public void SRConsumer1(Message msg, Channel channel) {
// superResolution(msg, channel, "consumer 1");
// }
//
// @RabbitListener(queues = "#{rabbitMQProperties.queues.srResult}")
// @RabbitHandler
// public void SRResultConsumer1(Message msg, Channel channel) {
// getSRResult(msg, channel, "consumer 1");
// }
}

View File

@@ -0,0 +1,170 @@
package com.ai.da.common.aspect;
import com.ai.da.common.context.UserContext;
import com.ai.da.model.vo.AuthPrincipalVo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
/**
* Controller日志切面
* 记录所有Controller接口的请求参数和用户信息
*/
@Aspect
@Component
public class ControllerLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(ControllerLoggingAspect.class);
/**
* 定义切点所有Controller方法
*/
@Pointcut("execution(* com.ai.da.controller..*(..))")
public void controllerMethods() {
}
/**
* Controller方法执行前记录日志
*/
// @Before("controllerMethods()")
public void logControllerBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 获取当前用户ID
Long userId = null;
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
if (authPrincipalVo != null) {
userId = authPrincipalVo.getId();
}
// 获取请求参数
Map<String, Object> params = getRequestParams(joinPoint, request);
logger.info("=== 请求开始 ===");
logger.info("用户ID: {}", userId);
logger.info("请求URL: {}", request.getRequestURL().toString());
logger.info("请求方法: {}", request.getMethod());
logger.info("请求IP: {}", getClientIpAddress(request));
logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.info("请求参数: {}", params);
}
}
/**
* 获取请求参数
*/
private Map<String, Object> getRequestParams(JoinPoint joinPoint, HttpServletRequest request) {
Map<String, Object> params = new HashMap<>();
// 1. 获取Query String参数
String queryString = request.getQueryString();
if (queryString != null && !queryString.isEmpty()) {
params.put("queryString", queryString);
}
// 2. 获取方法参数(包含 @PathVariable, @RequestParam, @RequestBody 等)
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
Map<String, Object> methodParams = new HashMap<>();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
// 过滤掉不可序列化的参数
if (arg != null) {
if (isIgnorable(arg)) {
// 对于可忽略的类型,记录类型名
methodParams.put("arg" + i, "[" + arg.getClass().getSimpleName() + "]");
} else {
try {
methodParams.put("arg" + i, arg);
} catch (Exception e) {
methodParams.put("arg" + i, arg.toString());
}
}
}
}
if (!methodParams.isEmpty()) {
params.put("methodParams", methodParams);
}
}
return params;
}
/**
* 判断是否需要过滤的参数类型
*/
private boolean isIgnorable(Object obj) {
return obj instanceof HttpServletRequest
|| obj instanceof HttpServletResponse
|| obj instanceof MultipartFile
|| obj instanceof MultipartFile[];
}
/**
* Controller方法抛出异常时记录日志
*/
@AfterThrowing(pointcut = "controllerMethods()", throwing = "exception")
public void logControllerAfterThrowing(JoinPoint joinPoint, Throwable exception) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Long userId = null;
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
if (authPrincipalVo != null) {
userId = authPrincipalVo.getId();
}
// 获取请求参数
Map<String, Object> params = new HashMap<>();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
params = getRequestParams(joinPoint, request);
}
logger.error("=== 请求异常 ===");
logger.error("用户ID: {}", userId);
logger.error("调用方法: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.error("请求参数: {}", params);
logger.error("异常信息: ", exception);
logger.error("=== 异常结束 ===");
}
/**
* 获取客户端真实IP地址
*/
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
return xForwardedFor.split(",")[0];
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
return xRealIp;
}
String proxyClientIp = request.getHeader("Proxy-Client-IP");
if (proxyClientIp != null && !proxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(proxyClientIp)) {
return proxyClientIp;
}
String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
if (wlProxyClientIp != null && !wlProxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
return wlProxyClientIp;
}
return request.getRemoteAddr();
}
}

View File

@@ -202,7 +202,7 @@ public class MyTaskScheduler {
}
}
// @Scheduled(cron = "0 0 9 * * ?")
// @Scheduled(cron = "0 0 9 * * ?")
public void sendTrialOrderExcelToManagements() {
// 获取前一天日期
LocalDate yesterday = LocalDate.now().minusDays(1);

View File

@@ -0,0 +1,34 @@
package com.ai.da.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
/**
* Spring Security 配置。
* 由于鉴权逻辑已迁移至 GatewayGlobalAuthWebFilter
* 后端服务 (aida-back) 默认放行所有请求,仅依赖网关传递的用户信息。
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF微服务通常不需要
.csrf(AbstractHttpConfigurer::disable)
// 允许所有请求,具体鉴权在网关层完成
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
// 禁用默认的表单登录和 HTTP Basic 认证,防止 302 重定向
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable);
return http.build();
}
}

View File

@@ -1,13 +1,16 @@
package com.ai.da.common.config;
import com.ai.da.common.interceptor.UserContextInterceptor;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import jakarta.annotation.Resource;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
@@ -17,11 +20,20 @@ public class WebConfig implements WebMvcConfigurer {
static final String ORIGINS[] = new String[]{"GET", "POST", "PUT", "DELETE"};
@Resource
private UserContextInterceptor userContextInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOriginPatterns("*").allowCredentials(true).allowedMethods(ORIGINS).maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userContextInterceptor)
.addPathPatterns("/**");
}
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)

View File

@@ -1,89 +1,99 @@
package com.ai.da.common.config.exception;
import com.ai.da.common.response.Response;
import com.google.common.collect.ImmutableMap;
import com.ai.da.common.response.ResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author: dangweijian
* @description: 全局异常捕获
* @create: 2019-12-03 10:24
**/
@Slf4j
@ControllerAdvice
public class ExceptionCatch {
/**
* 线程安全,且构建后不可更改
*/
private static ImmutableMap<Class<? extends Throwable>, ResultEnum> EXCEPTIONS;
/**
* 用于构建ImmutableMap
*/
private static ImmutableMap.Builder<Class<? extends Throwable>, ResultEnum> builder = ImmutableMap.builder();
@ResponseBody
@ExceptionHandler(BusinessException.class)
public Response<String> businessExceptionCatch(BusinessException e) {
log.error("发生业务异常,code:[{}],msg:[{}]", e.getCode(), e.getMsg(), e);
return Response.error(e.getCode(), e.getMsg());
}
@ResponseBody
@ExceptionHandler(Exception.class)
public Response<String> exceptionCatch(Exception e) {
log.error("发生系统异常,message:[{}]", e.getMessage(), e);
//如果ImmutableMap集合为空,构建ImmutableMap
if (EXCEPTIONS == null || EXCEPTIONS.size() == 0) {
EXCEPTIONS = builder.build();
}
//获取不可预知异常自定义错误码
if (EXCEPTIONS != null) {
ResultEnum resultEnum = EXCEPTIONS.get(e.getClass());
if (resultEnum != null) {
return Response.error(resultEnum.getCode(), resultEnum.getMsg());
}
}
return Response.error(ResultEnum.ERROR.getCode(), e.getMessage() == null ? ResultEnum.ERROR.getMsg() : e.getMessage());
}
/**
* 处理参数校验异常
*
* @param e
* @return ResponseData
*/
@ResponseBody
@ExceptionHandler(BindException.class)
public Response<String> bindExceptionHandler(BindException e) {
log.error("参数错误bind{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
BusinessException businessException = new BusinessException(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return Response.error(businessException.getCode(), businessException.getMsg());
}
/**
* 处理参数校验异常
*
* @param e
* @return ResponseData
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response<String> handleValidationException(MethodArgumentNotValidException e) {
log.error("参数错误bind{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
BusinessException businessException = new BusinessException(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return Response.error(businessException.getCode(), businessException.getMsg());
}
//初始化,不可预知异常自定义错误编码
static {
// builder.put(FileNotFoundException.class, ResultEnum.FILE_NOT_EXIST);
}
}
package com.ai.da.common.config.exception;
import com.ai.da.common.response.Response;
import com.google.common.collect.ImmutableMap;
import com.ai.da.common.response.ResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author: dangweijian
* @description: 全局异常捕获
* @create: 2019-12-03 10:24
**/
@Slf4j
@ControllerAdvice
public class ExceptionCatch {
/**
* 线程安全,且构建后不可更改
*/
private static ImmutableMap<Class<? extends Throwable>, ResultEnum> EXCEPTIONS;
/**
* 用于构建ImmutableMap
*/
private static ImmutableMap.Builder<Class<? extends Throwable>, ResultEnum> builder = ImmutableMap.builder();
@ResponseBody
@ExceptionHandler(BusinessException.class)
public Response<String> businessExceptionCatch(BusinessException e) {
log.error("发生业务异常,code:[{}],msg:[{}]", e.getCode(), e.getMsg(), e);
return Response.error(e.getCode(), e.getMsg());
}
@ResponseBody
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnauthorizedException.class)
public Response<String> unauthorizedExceptionCatch(UnauthorizedException e) {
log.error("Unauthorized: {}", e.getMessage());
return Response.error(401, e.getMessage());
}
@ResponseBody
@ExceptionHandler(Exception.class)
public Response<String> exceptionCatch(Exception e) {
log.error("发生系统异常,message:[{}]", e.getMessage(), e);
//如果ImmutableMap集合为空,构建ImmutableMap
if (EXCEPTIONS == null || EXCEPTIONS.size() == 0) {
EXCEPTIONS = builder.build();
}
//获取不可预知异常自定义错误码
if (EXCEPTIONS != null) {
ResultEnum resultEnum = EXCEPTIONS.get(e.getClass());
if (resultEnum != null) {
return Response.error(resultEnum.getCode(), resultEnum.getMsg());
}
}
return Response.error(ResultEnum.ERROR.getCode(), e.getMessage() == null ? ResultEnum.ERROR.getMsg() : e.getMessage());
}
/**
* 处理参数校验异常
*
* @param e
* @return ResponseData
*/
@ResponseBody
@ExceptionHandler(BindException.class)
public Response<String> bindExceptionHandler(BindException e) {
log.error("参数错误bind{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
BusinessException businessException = new BusinessException(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return Response.error(businessException.getCode(), businessException.getMsg());
}
/**
* 处理参数校验异常
*
* @param e
* @return ResponseData
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response<String> handleValidationException(MethodArgumentNotValidException e) {
log.error("参数错误bind{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
BusinessException businessException = new BusinessException(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return Response.error(businessException.getCode(), businessException.getMsg());
}
//初始化,不可预知异常自定义错误编码
static {
// builder.put(FileNotFoundException.class, ResultEnum.FILE_NOT_EXIST);
}
}

View File

@@ -1,12 +0,0 @@
package com.ai.da.common.config.exception;
public class TokenMissingOrExpiredException extends RuntimeException {
public TokenMissingOrExpiredException(String message) {
super(message);
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}

View File

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

View File

@@ -10,7 +10,7 @@ public class CommonConstant {
// 单位 秒 两天过期
public static final Long CREDITS_EXPIRE_TIME = 2 * 24 * 60 * 60L;
// 单位 分钟
public static final Integer MINIO_IMAGE_EXPIRE_TIME = 24 * 60;
public static final Integer MINIO_IMAGE_EXPIRE_TIME = 24 * 60 * 7;
// 单位 秒 一天过期 in redis
public static final Long GENERATE_RESULT_EXPIRE_TIME = 24 * 60 * 60L;
// 单位 秒 7天过期
@@ -23,6 +23,7 @@ public class CommonConstant {
}
public static final String GENERATE_PATH = "/api/generate_image";
public static final String GENERATE_PATH_FLUX2_KLEIN = "/api/generate_image_flux2_klein";
public static final String GENERATE_SINGLE_LOGO = "/api/generate_single_logo";

View File

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

View File

@@ -1,14 +0,0 @@
package com.ai.da.common.constant;
/**
* @author yanglei
* 异常类常量
*/
public class TokenConstant {
/**
* 固定session
*/
public static final String FIX_SESSION = "qrLS_003af9d8c1363fc4_6c97e932665c4460a1fdbfbf47ce3490";
public static final String PERMISSIONS = "9672233956";
}

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
package com.ai.da.common.httpdata.token;
public enum TokenApis {
/**
* token
*/
GET_TOKEN("POST", "/api/openApi/v2/Weixin/QrCodeLoginCheck?session="),
GENERATE_USER("POST", "/api/openApi/v2/Welink/TopicGetjson");
private String method;
private String url;
TokenApis(String method, String url) {
this.method = method;
this.url = url;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@@ -1,42 +0,0 @@
package com.ai.da.common.httpdata.token;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class TokenQuery {
private static final String GET_TOKEN_DOMAIN = "https://www.szsige.com";
private static final String GENERATE_USER_DOMAIN = "https://www.szsige.com";
public static JSONObject getToken(String session) {
String url = GET_TOKEN_DOMAIN + TokenApis.GET_TOKEN.getUrl() + session;
log.info("获取用户token接口请求url:" + url);
HttpResponse httpResponse = HttpUtil.createPost(url).execute();
log.info("获取用户token接口响应" + httpResponse);
if (httpResponse.isOk() && StrUtil.isNotEmpty(httpResponse.body())) {
return JSONObject.parseObject(httpResponse.body());
}
return null;
}
public static JSONObject generateUser(Map<String, Object> param, String token) {
HttpResponse httpResponse = HttpUtil.createPost(GENERATE_USER_DOMAIN + TokenApis.GENERATE_USER.getUrl())
.body(JSONObject.toJSONString(param != null ? param : new HashMap<>()))
.header("Authorization", "Bearer " + token)
.header("X-Promiss", "9672233956")
.execute();
log.info("生成用户信息接口响应:" + httpResponse);
if (httpResponse.isOk() && StrUtil.isNotEmpty(httpResponse.body())) {
return JSONObject.parseObject(httpResponse.body());
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
package com.ai.da.common.interceptor;
import com.ai.da.common.context.UserContext;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 从 Gateway 转发的请求头中读取已鉴权的用户身份,写入 UserContext。
* <p>
* Gateway 验证 JWT 后将 X-User-Id 和 X-User-Info 写入请求头,
* 此拦截器负责将信息填充到 ThreadLocal供业务代码使用。
* 不需要 Gateway 鉴权路径(如 login、静态资源不会有这两个头
* 此时 UserContext 保持为空,业务代码应自行处理。
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class UserContextInterceptor implements HandlerInterceptor {
private static final String USER_ID_HEADER = "X-User-Id";
private static final String USER_INFO_HEADER = "X-User-Info";
private final ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String userInfoJson = request.getHeader(USER_INFO_HEADER);
if (userInfoJson != null && !userInfoJson.isBlank()) {
try {
AuthPrincipalVo principal = objectMapper.readValue(userInfoJson, AuthPrincipalVo.class);
UserContext.setUserHolder(principal);
} catch (Exception e) {
log.warn("Failed to parse X-User-Info header: {}", e.getMessage());
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
UserContext.delete();
}
}

View File

@@ -1,27 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.Response;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName UserAuthAccessDeniedHandler
* @Description 无权限处理类
* @Author dwjian
* @Date 2020/7/9 20:30
*/
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException exception) throws IOException {
Response<String> response = Response.error(ResultEnum.NO_PERMISSION.getCode(), ResultEnum.NO_PERMISSION.getMsg());
JSONResponseUtils.build(httpServletResponse, response);
}
}

View File

@@ -1,29 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.Response;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName UserAuthenticationEntryPointHandler
* @Description 未登录处理类
* @Author dwjian
* @Date 2020/7/9 20:13
*/
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Response<String> response = Response.error(ResultEnum.NO_LOGIN.getCode(), ResultEnum.NO_LOGIN.getMsg());
httpServletResponse.setStatus(401);
JSONResponseUtils.build(httpServletResponse, response);
}
}

View File

@@ -1,25 +0,0 @@
package com.ai.da.common.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
/**
* @author: dangweijian
* @description: 认证管理器
* @create: 2020-07-10 15:58
**/
@Component
public class UserAuthenticationManager implements AuthenticationManager {
@Resource
private UserAuthenticationProvider userAuthenticationProvider;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return userAuthenticationProvider.authenticate(authentication);
}
}

View File

@@ -1,59 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.config.RsaProperties;
import com.ai.da.common.utils.RsaDecryptUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
/**
* @author: dangweijian
* @description: 登录校验处理类
* @create: 2020-07-09 14:39
**/
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
try {
password = RsaDecryptUtils.decrypt(password, RsaProperties.privateKey);
} catch (Exception e) {
throw new BadCredentialsException("用户名或密码错误");
}
// User user = userService.getByUsername(userName);
// if (user == null) {
// throw new UsernameNotFoundException("用户名或密码错误");
// }
// //账号已冻结
// if(user.getStatus() == 1){
// throw new LockedException("账号已冻结");
// }
// if(!passwordEncoder.matches(password, user.getPassword())){
// throw new BadCredentialsException("用户名或密码错误");
// }
// //超级管理员
// Set<SimpleGrantedAuthority> authorities = new HashSet<>();
// if(user.getIsAdmin()) {
// authorities.add(new SimpleGrantedAuthority("admin"));
// return new UsernamePasswordAuthenticationToken(user, password, authorities);
// }else {
// List<RoleMenuDto> userMenus = menuService.getRoleMenusByUserId(user.getId(), null);
// if(CollUtil.isNotEmpty(userMenus)){
// authorities.addAll(userMenus.stream().map(RoleMenuDto::getPermission).filter(StringUtils::isNotEmpty).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
// }
// return new UsernamePasswordAuthenticationToken(user, password, authorities);
// }
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
}

View File

@@ -1,48 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.Response;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName UserLoginFailureHandler
* @Description 登录失败处理类
* @Author dwjian
* @Date 2020/7/9 20:17
*/
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Response<String> response;
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
response = Response.fail(e.getMessage());
} else if (e instanceof LockedException) {
response = Response.fail(ResultEnum.ACCOUNT_LOCK);
} else if (e instanceof CredentialsExpiredException) {
response = Response.fail("证书过期,请联系管理员!");
} else if (e instanceof AccountExpiredException) {
response = Response.fail("账户过期,请联系管理员!");
} else if (e instanceof DisabledException) {
response = Response.fail("账户被禁用,请联系管理员!");
} else if (e instanceof AuthenticationServiceException) {
response = Response.fail(e.getMessage());
} else {
log.error("登录失败:", e);
response = Response.fail("登录失败!");
}
JSONResponseUtils.build(httpServletResponse, response);
}
}

View File

@@ -1,51 +0,0 @@
package com.ai.da.common.security;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.JSONResponseUtils;
import com.ai.da.common.response.Response;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: dangweijian
* @description: 登录成功处理类
* @create: 2020-07-09 14:58
**/
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JWTTokenHelper jwtTokenHelper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// User user = (User) authentication.getPrincipal();
// AuthPrincipalVo principal = new AuthPrincipalVo();
// BeanUtils.copyProperties(user, principal);
// // 获取用户角色
// List<UserRoleDto> userRoles = roleService.getUserRoles(user.getId(), 0);
// principal.setRoles(userRoles);
// // 获取角色部门
// if(CollUtil.isNotEmpty(userRoles)){
// principal.setDepts(deptService.getDeptByRoleIds(userRoles.stream().map(UserRoleDto::getRoleId).collect(Collectors.toList())));
// }
// // 用户角色权限
// List<String> authorities = new ArrayList<>(authentication.getAuthorities()).stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// principal.setAuthorities(authorities);
// AuthVo authVo = new AuthVo();
// authVo.setAuthorities(authorities);
// authVo.setToken(jwtTokenHelper.createToken(principal));
// authVo.setPrincipal(principal);
// user.setLastLoginTime(new Date());
// userService.updateById(user);
JSONResponseUtils.build(response, Response.success(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMsg(), null));
}
}

View File

@@ -1,34 +0,0 @@
package com.ai.da.common.security;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* @ClassName UserPermissionEvaluator
* @Description 权限校验处理器
* @Author dwjian
* @Date 2020/7/12 10:16
*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
System.out.println(authentication);
System.out.println(targetUrl);
System.out.println(permission);
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
System.out.println(authentication);
System.out.println(targetId);
System.out.println(targetType);
System.out.println(permission);
return false;
}
}

View File

@@ -1,107 +0,0 @@
package com.ai.da.common.security.config;
import com.ai.da.common.security.*;
import com.ai.da.common.security.filter.AuthenticationFilter;
import com.ai.da.common.security.filter.UserAuthenticationProcessingFilter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import jakarta.annotation.Resource;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityConfig {
@Resource
private SecurityProperties securityProperties;
@Resource
private UserLoginSuccessHandler userLoginSuccessHandler;
@Resource
private UserLoginFailureHandler userLoginFailureHandler;
@Resource
private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
@Resource
private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
@Resource
private UserAuthenticationManager userAuthenticationManager;
@Resource
private UserAuthenticationProcessingFilter userAuthenticationProcessingFilter;
/**
* 不通过注入spring管理 让Security来管理 这样自定义的Filter就不会走,.permitAll()才能起作用
*/
@Resource
private AuthenticationFilter authenticationFilter;
@Resource
private UserPermissionEvaluator userPermissionEvaluator;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return this.userAuthenticationManager;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers(
new AntPathRequestMatcher("/doc.html"),
new AntPathRequestMatcher("/swagger-ui.html"),
new AntPathRequestMatcher("/swagger-ui/**"),
new AntPathRequestMatcher("/swagger-resources/**"),
new AntPathRequestMatcher("/v2/api-docs"),
new AntPathRequestMatcher("/v3/api-docs/**"),
new AntPathRequestMatcher("/webjars/**")
).permitAll()
.requestMatchers(securityProperties.getIgnorePaths()).permitAll()
.anyRequest().authenticated()
)
.headers(headers -> headers
.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)
.cacheControl(cache -> cache.disable())
)
.exceptionHandling(exception -> exception
.authenticationEntryPoint(userAuthenticationEntryPointHandler)
.accessDeniedHandler(userAuthAccessDeniedHandler)
)
.formLogin(form -> form
.loginProcessingUrl(securityProperties.getAuthApi())
.successHandler(userLoginSuccessHandler)
.failureHandler(userLoginFailureHandler)
)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
//自定义过滤器在登录时认证用户名、密码
httpSecurity.addFilterAt(userAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFilter, BasicAuthenticationFilter.class);
return httpSecurity.build();
}
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(userPermissionEvaluator);
return handler;
}
}

View File

@@ -1,26 +0,0 @@
package com.ai.da.common.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: dangweijian
* @description: JWT配置类
* @create: 2020-07-09 09:38
**/
@Data
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
private String jwtSecret;
private String jwtTokenHeader;
private String jwtTokenPrefix;
private long jwtExpiration;
private String[] ignorePaths;
private String authApi;
}

View File

@@ -1,160 +0,0 @@
package com.ai.da.common.security.filter;
import cn.hutool.core.util.StrUtil;
import com.ai.da.common.config.exception.TokenMissingOrExpiredException;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.LocalCacheUtils;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.MultiReadHttpServletRequest;
import com.ai.da.common.utils.MultiReadHttpServletResponse;
import com.ai.da.common.utils.RequestInfoUtil;
import com.ai.da.model.vo.AuthPrincipalVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* @author: dangweijian
* @description: 认证拦截器
* @create: 2020-07-10 16:50
**/
@Slf4j
@Configuration
public class AuthenticationFilter extends OncePerRequestFilter {
@Resource
private JWTTokenHelper jwtTokenHelper;
@Resource
private SecurityProperties properties;
@Resource
private RedisUtil redisUtil;
private static final List<String> FILTER_URL =
Arrays.asList("/favicon.ico", "/doc.html", "/swagger-ui.html",
"/swagger-resources", "/swagger-resources/", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security",
"/webjars/", "/v2/api-docs", "/v3/api-docs", "/v3/api-docs/swagger-config",
"/api/account/login", "/api/account/preLogin", "api/account/sendEmail","api/account/noLoginRequired",
"/api/account/resetPwd",
"/api/python/saveGeneratePicture", "/api/python/getLibraryByUserId",
"/api/third/party/addUser","/api/third/party/addTrialUser", "/api/third/party/editUser", "/api/element/initDefaultSysFile",
"/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew","/api/third/party/updateNoLoginRequiredNew",
"/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl",
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back","/api/alipay-hk/trade/notify",
"/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease",
"/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"
, "/api/account/schoolLogin", "/api/account/enterpriseLogin", "/api/account/organizationNameSearch",
"/api/llm/stream"
);
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException {
String requestURI = httpServletRequest.getRequestURI();
if (calculateUrl(requestURI)/* || hasAuthorizationToken(httpServletRequest)*/) {
StopWatch stopWatch = new StopWatch();
HttpServletRequest wrappedRequest = httpServletRequest;
HttpServletResponse wrappedResponse = httpServletResponse;
try {
stopWatch.start();
if ((httpServletRequest.getContentType() == null && httpServletRequest.getContentLength() > 0) || (httpServletRequest.getContentType() != null && !httpServletRequest.getContentType().contains("application/json"))) {
extracted(wrappedRequest);
filterChain.doFilter(wrappedRequest, wrappedResponse);
} else {
wrappedRequest = new MultiReadHttpServletRequest(httpServletRequest);
wrappedResponse = new MultiReadHttpServletResponse(httpServletResponse);
extracted(wrappedRequest);
// excel导出使用原始response,不对响应做包装
if (requestURI.equals("/api/account/exportAccountsToExcel")) {
filterChain.doFilter(httpServletRequest, httpServletResponse); // 不包装
} else {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
throw e;
} finally {
stopWatch.stop();
}
} else {
//先清空当前线程变量,防止上一个线程遗留
UserContext.delete();
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
private Boolean calculateUrl(String requestURI) {
String filterUrl = FILTER_URL.stream().filter(url -> requestURI.contains(url)).findFirst().orElse(null);
return null == filterUrl ? Boolean.TRUE : Boolean.FALSE;
}
private boolean hasAuthorizationToken(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
return authorizationHeader != null && authorizationHeader.startsWith("Bearer");
}
private void extracted(HttpServletRequest request) {
String jwtToken = request.getHeader(properties.getJwtTokenHeader());
// log.debug("后台检查令牌:{}", jwtToken);
if (StrUtil.isBlank(jwtToken)) {
String ipAddress = RequestInfoUtil.getIpAddress(request);
log.info("本次请求的ip为 " + ipAddress);
// throw new RuntimeException("请传入token");
throw new TokenMissingOrExpiredException("请传入token");
}
if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
//写死 暂时放行
return;
}
// 检查token
boolean validate = jwtTokenHelper.validateToken(jwtToken);
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(jwtToken);
if (principal == null) {
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(token without userInfo)");
}
//先清空当前线程变量,防止上一个线程遗留
UserContext.delete();
//存取用户信息到缓存
UserContext.setUserHolder(principal);
// 校验 token先查本地缓存再查 Redis保证服务重启后仍然有效
String userIdStr = String.valueOf(principal.getId());
String cacheToken = LocalCacheUtils.getTokenCache(userIdStr);
if (StringUtils.isEmpty(cacheToken)) {
// 本地缓存为空时,尝试从 Redis 读取
cacheToken = redisUtil.getLoginToken(principal.getId());
if (StringUtils.isEmpty(cacheToken)) {
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(cache & redis empty)");
}
// 将 Redis 中的 token 回填到本地缓存,减少后续 Redis 访问
LocalCacheUtils.setTokenCache(userIdStr, cacheToken);
}
if(!cacheToken.equals(jwtToken) ){
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(token not match local cache)");
}
// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(null, null);
// SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}

View File

@@ -1,69 +0,0 @@
package com.ai.da.common.security.filter;
import cn.hutool.core.util.StrUtil;
import com.ai.da.common.security.UserLoginSuccessHandler;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.common.utils.RedisCacheUtils;
import com.alibaba.fastjson.JSONObject;
import com.ai.da.common.security.UserAuthenticationManager;
import com.ai.da.common.security.UserLoginFailureHandler;
import com.ai.da.common.utils.MultiReadHttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* @author: dangweijian
* @description: 用户认证过滤器
* @create: 2020-07-10 15:58
**/
@Slf4j
@Component
public class UserAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
/**
* @param securityProperties 配置从配置中读取登录url
* @param authenticationManager 认证管理器
* @param adminAuthenticationSuccessHandler 认证成功处理器
* @param adminAuthenticationFailureHandler 认证失败处理器
*/
public UserAuthenticationProcessingFilter(SecurityProperties securityProperties, UserAuthenticationManager authenticationManager, UserLoginSuccessHandler adminAuthenticationSuccessHandler, UserLoginFailureHandler adminAuthenticationFailureHandler) {
super(new AntPathRequestMatcher(securityProperties.getAuthApi(), HttpMethod.POST.name()));
this.setAuthenticationManager(authenticationManager);
this.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler);
this.setAuthenticationFailureHandler(adminAuthenticationFailureHandler);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType() == null || !request.getContentType().contains("application/json")) {
throw new AuthenticationServiceException("请求头类型不支持: " + request.getContentType());
}
UsernamePasswordAuthenticationToken authRequest;
try {
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
// 将前端传递的数据转换成jsonBean数据格式
JSONObject jsonObject = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest));
String code = jsonObject.getString("code");
String uuid = jsonObject.getString("uuid");
if (StrUtil.isEmpty(code) || StrUtil.isEmpty(uuid) || !code.equals(RedisCacheUtils.get("code-key-" + uuid, String.class))) {
throw new AuthenticationServiceException("验证码错误");
}
RedisCacheUtils.delete("code-key-" + uuid);
authRequest = new UsernamePasswordAuthenticationToken(jsonObject.get("username"), jsonObject.get("password"), null);
authRequest.setDetails(authenticationDetailsSource.buildDetails(wrappedRequest));
} catch (Exception e) {
throw new AuthenticationServiceException(e.getMessage());
}
return this.getAuthenticationManager().authenticate(authRequest);
}
}

View File

@@ -1,108 +0,0 @@
package com.ai.da.common.security.jwt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
/**
* @author: dangweijian
* @description: JWT工具
* @create: 2020-07-09 09:27
**/
@Slf4j
@Component
public class JWTTokenHelper {
@Resource
private SecurityProperties securityProperties;
private static final String ISSUER = "DWJ";
private static final String AUTHORITIES = "authorities";
private static final String CHANGE_MAILBOX = "changeMailbox";
public String createToken(AuthPrincipalVo principal) {
SecretKey key = buildSigningKey();
String token = Jwts.builder()
.id(String.valueOf(principal.getId()))
.subject(JSONObject.toJSONString(principal))
.issuedAt(new Date())
.issuer(ISSUER)
.claim(AUTHORITIES, JSON.toJSONString(new ArrayList<>()))//自定义属性 权限
.expiration(new Date(System.currentTimeMillis() + securityProperties.getJwtExpiration()))
.signWith(key)
.compact();
token = securityProperties.getJwtTokenPrefix() + token;
return token;
}
public boolean validateToken(String token) {
Claims claims = parser(token);
if (MapUtil.isEmpty(claims)) {
return false;
}
return true;
}
public AuthPrincipalVo parserToUser(String token) {
String subject = parser(token).getSubject();
if (StrUtil.isNotEmpty(subject)) {
return JSONObject.parseObject(subject, AuthPrincipalVo.class);
}
return null;
}
public Claims parser(String token) {
token = token.replaceAll(securityProperties.getJwtTokenPrefix(), "");
SecretKey key = buildSigningKey();
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}
public String createToken(Long userId, String userEmail){
SecretKey key = buildSigningKey();
String token = Jwts.builder()
.id(String.valueOf(userId))
.subject(userEmail + "_" + userId)
.issuedAt(new Date())
.issuer(ISSUER)
.claim(CHANGE_MAILBOX, JSON.toJSONString(new ArrayList<>()))//自定义属性 权限
.expiration(new Date(System.currentTimeMillis() + CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY))
.signWith(key)
.compact();
return token;
}
public String parseToEmailAndId(String token) {
return parser(token).getSubject();
}
/**
* JWT 要求 HMAC-SHA 的密钥至少 256 bit这里统一扩展/哈希密钥长度避免 WeakKeyException。
*/
private SecretKey buildSigningKey() {
byte[] raw = securityProperties.getJwtSecret().getBytes(StandardCharsets.UTF_8);
if (raw.length < 32) {
raw = DigestUtil.sha256(raw);
}
return Keys.hmacShaKeyFor(raw);
}
}

View File

@@ -28,20 +28,20 @@ public class AccountTask {
* 每个月月初只刷新教育子账号的积分
*/
// @Scheduled(cron = "0 25 14 * * ?")
@Scheduled(cron = "0 0 0 1 * ?")
// @Scheduled(cron = "0 0 0 1 * ?")
public void refreshCreditsMonthly() {
log.info("每月1号0点 重置教育版子账号为默认积分");
accountService.refreshCreditsMonthly();
}
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void getPaidUser() {
// 获取code-create 表中 指定日期之后 订单状态为wc-processing的订单
accountService.extendValidityForCC();
}
// 每天凌晨0点执行一次
@Scheduled(cron = "0 0 0 * * ?")
// 每天凌晨0点执行一次 目前已没有角色类型为4的用户
/*@Scheduled(cron = "0 0 0 * * ?")
public void cancelActivityBenefits() {
// 1、查询当前所有参与了活动且过期的用户
List<Account> accountList = accountService.getExpiredUserBySystemUser(4);
@@ -51,10 +51,10 @@ public class AccountTask {
log.info("参与活动的用户{} : {} 于 {} 账号有效期到期,置为游客", account.getId(), account.getUserEmail(), account.getValidEndTime());
accountService.toVisitor(account);
}
}
}*/
// 每天检测正式用户到期情况每天凌晨0点执行
@Scheduled(cron = "0 0 0 * * ?")
// @Scheduled(cron = "0 0 0 * * ?")
public void paidUserToVisitor() {
// 1、查询当前已过期正式用户或试用用户
List<Account> accountList = accountService.getExpiredUserBySystemUser(1);
@@ -77,7 +77,7 @@ public class AccountTask {
accountService.registerUserToVisitor();
}
@Scheduled(cron = "0 0 0 1 * ?")
// @Scheduled(cron = "0 0 0 1 * ?")
// 每月初刷新所有用户用户名剩余修改次数
public void resetUsernameModifyTimes(){
log.info("重置所有用户的用户名修改次数");
@@ -85,17 +85,17 @@ public class AccountTask {
}
// @Scheduled(cron = "0 35 14 * * ?")
@Scheduled(cron = "0 5 0 * * ?")
// @Scheduled(cron = "0 5 0 * * ?")
public void checkEduAdminExpireStatus() {
accountService.checkEduAdminExpireStatus();
}
@Scheduled(cron = "0 5 0 * * ?")
// @Scheduled(cron = "0 5 0 * * ?")
public void activeSubscriptionPlan() {
subscriptionPlanService.activeSubscriptionPlan(null);
}
@Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void expireSubscription() {
subscriptionPlanService.expireSubscription();
}

View File

@@ -38,7 +38,7 @@ public class GenerateTask {
* 故这里通过定时任务做补偿
* flux五分钟查询一次万相1小时查询一次
*/
@Scheduled(cron = "0 */4 * * * ?")
// @Scheduled(cron = "0 */4 * * * ?")
public void fluxCompensationMechanism(){
// 1、查所有 任务还没成功、还没失败正在等待或者执行中的任务id有哪些
// 由于获取结果的polling_url在redis中只存一天大部分结果超过一天之后就无法再找到任务小部分可以通过公共路径查到结果
@@ -98,7 +98,7 @@ public class GenerateTask {
}
// 万相 -> pose transformation 补偿 当前任务执行完后5分钟再执行一次不会出现任务重叠的情况
@Scheduled(fixedDelay = 5 * 60 * 1000)
// @Scheduled(fixedDelay = 5 * 60 * 1000)
public void wxCompensationMechanism(){
List<APIGenerate> apiGenerates = apiGenerateService.getPendingTaskByStatus("wx");
if (apiGenerates != null && !apiGenerates.isEmpty()){

View File

@@ -45,7 +45,7 @@ public class PaymentTask {
@Resource
private PayPalCheckoutService payPalCheckoutService;
// @Scheduled(cron = "0/30 * * * * ?")
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirmForPaypal() throws SerializeException {
// log.info("PayPal orderConfirm 被执行......");
@@ -97,19 +97,19 @@ public class PaymentTask {
//
}
@Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void updateAffiliateInfoWithPayment(){
// log.info("佣金计算定时器");
affiliateService.updateAffiliateInfoWithPayment();
}
// 定时同步(每分钟一次)
@Scheduled(fixedRate = 60000)
// @Scheduled(fixedRate = 60000)
public void syncLinkViewCountToDB(){
affiliateService.syncLinkViewCountToDB();
}
// @Scheduled(cron = "0 0 8 28-31 * ?")
// @Scheduled(cron = "0 0 8 28-31 * ?")
public void commissionSummaryReminder(){
// 每个月末的最后一天的早上八点执行
LocalDate today = LocalDate.now();
@@ -120,7 +120,7 @@ public class PaymentTask {
}
}
@Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void calcCouponsCommission(){
// log.info("优惠券佣金计算定时器");
affiliateService.calcCouponsCommission();

View File

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

View File

@@ -1,178 +1,171 @@
package com.ai.da.common.utils;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.model.dto.BasicEmailParamDTO;
import com.alibaba.fastjson.JSONObject;
import com.sun.mail.smtp.SMTPTransport;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamSource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.*;
import java.util.List;
import java.util.Objects;
@Slf4j
@Component
public class MailUtil {
@Resource
private JavaMailSender javaMailSender;
@Resource
private TemplateEngine templateEngine;
/**
* 发送邮件 - 默认发件人
*
* @param basicEmailParamDTO 发送邮件所需参数
* @param inputStreamSource 附件(如果有)
*/
public int sendMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
MimeMessage mimeMessage = createSimpleMail(basicEmailParamDTO, fileName, inputStreamSource);
// 提取配置
String host;
String username;
String password;
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
host = ((JavaMailSenderImpl) javaMailSender).getHost();
} else {
host = basicEmailParamDTO.getServiceAddress();
}
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getSenderUser())) {
username = ((JavaMailSenderImpl) javaMailSender).getUsername();
} else {
username = basicEmailParamDTO.getSenderUser();
}
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
password = ((JavaMailSenderImpl) javaMailSender).getPassword();
} else {
password = basicEmailParamDTO.getPassword();
}
return sendMail(mimeMessage, host, username, password);
}
private int sendMail(MimeMessage mimeMessage, String host, String username, String password) throws MessagingException {
SMTPTransport transport = null;
try {
// 获取 SMTPTransport
transport = (SMTPTransport) mimeMessage.getSession().getTransport("smtp");
// 连接到 SMTP 服务器
transport.connect(host, username, password);
// 发送邮件
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
// 获取 SMTP 服务器的响应
String lastServerResponse = transport.getLastServerResponse();
int lastReturnCode = transport.getLastReturnCode();
log.info("SMTP 状态码: {}, SMTP 服务器响应: {}", lastReturnCode, lastServerResponse);
return lastReturnCode;
} catch (MailException | MessagingException e) {
// 记录日志或执行其他补偿逻辑
log.info("邮件发送失败:{}", e.getMessage());
} finally {
// 关闭连接
assert transport != null;
transport.close();
}
return 0;
}
/**
* 创建一封邮件
*
* @param basicEmailParamDTO 创建邮件需要的参数
* @param inputStreamSource 附件(如果有)
* @return 一封邮件
*/
private MimeMessage createSimpleMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
// 创建邮件对象
MimeMessage message = javaMailSender.createMimeMessage();
// 使用 MimeMessageHelper 简化邮件内容和附件的设置
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(message, true);
// 设置发件人
mimeMessageHelper.setFrom(new InternetAddress(basicEmailParamDTO.getSenderUserMail()));
// 设置收件人
mimeMessageHelper.setTo(basicEmailParamDTO.getMailTo());
// 设置抄送人
if (basicEmailParamDTO.getCc() != null && basicEmailParamDTO.getCc().length > 0) {
mimeMessageHelper.setCc(basicEmailParamDTO.getCc());
}
// 设置暗送人
if (basicEmailParamDTO.getBcc() != null && basicEmailParamDTO.getBcc().length > 0) {
mimeMessageHelper.setBcc(basicEmailParamDTO.getBcc());
}
// 设置邮件主题
mimeMessageHelper.setSubject(basicEmailParamDTO.getSubject());
// 设置邮件内容HTML 格式)
mimeMessageHelper.setText(basicEmailParamDTO.getContent(), true);
// 设置附件
if (inputStreamSource != null) {
mimeMessageHelper.addAttachment(fileName, inputStreamSource);
}
return message;
}
/**
* 设置实体参数
*
* @param mailTo 接收邮件的邮箱地址
* @param jsonObject 模板中变量的值
* @return 返回一个MailEntity
* @throws AddressException 邮箱地址值异常
*/
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, JSONObject jsonObject, String templatePath, String title) throws AddressException {
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
// basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
basicEmailParamDTO.setSenderUserMail(CommonConstant.senderEmail);
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
basicEmailParamDTO.setSubject(title);
// todo 邮件模板不存在的报错与重试机制
basicEmailParamDTO.setContent(setContent(jsonObject, templatePath));
return basicEmailParamDTO;
}
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, String title) throws AddressException {
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
basicEmailParamDTO.setSubject(title);
return basicEmailParamDTO;
}
/**
* 将地址转换为InternetAddress类型
*
* @param addressList 普通的地址字符串列表
* @return InternetAddress类型的地址列表
* @throws AddressException 地址异常
*/
public InternetAddress[] getInternetAddressList(List<String> addressList) throws AddressException {
InternetAddress[] toAddress = new InternetAddress[addressList.size()];
for (String address : addressList) {
toAddress[addressList.indexOf(address)] = new InternetAddress(address);
}
return toAddress;
}
public String setContent(JSONObject jsonObject, String templatePath) {
Context context = new Context();
if (Objects.nonNull(jsonObject)) {
for (String key : jsonObject.keySet()) {
context.setVariable(key, jsonObject.get(key));
}
}
return templateEngine.process(templatePath, context);
}
}
package com.ai.da.common.utils;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.model.dto.BasicEmailParamDTO;
import com.alibaba.fastjson.JSONObject;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamSource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.*;
import java.util.List;
import java.util.Objects;
@Slf4j
@Component
public class MailUtil {
@Resource
private JavaMailSender javaMailSender;
@Resource
private TemplateEngine templateEngine;
/**
* 发送邮件 - 默认发件人
*
* @param basicEmailParamDTO 发送邮件所需参数
* @param fileName 附件名(如果有)
* @param inputStreamSource 附件(如果有)
*/
public int sendMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
MimeMessage mimeMessage = createSimpleMail(basicEmailParamDTO, fileName, inputStreamSource);
// 提取配置
String host;
String username;
String password;
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
host = ((JavaMailSenderImpl) javaMailSender).getHost();
} else {
host = basicEmailParamDTO.getServiceAddress();
}
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getSenderUser())) {
username = ((JavaMailSenderImpl) javaMailSender).getUsername();
} else {
username = basicEmailParamDTO.getSenderUser();
}
if (StringUtil.isNullOrEmpty(basicEmailParamDTO.getServiceAddress())) {
password = ((JavaMailSenderImpl) javaMailSender).getPassword();
} else {
password = basicEmailParamDTO.getPassword();
}
return sendMail(mimeMessage, host, username, password);
}
private int sendMail(MimeMessage mimeMessage, String host, String username, String password) throws MessagingException {
try {
// 配置连接属性
java.util.Properties props = mimeMessage.getSession().getProperties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.host", host);
props.put("mail.user", username);
props.put("mail.password", password);
// 使用 JavaMailSender 发送邮件Spring Boot 3.x 标准方式)
javaMailSender.send(mimeMessage);
log.info("邮件发送成功至: {}", host);
return 1;
} catch (MailException e) {
log.info("邮件发送失败:{}", e.getMessage());
return 0;
}
}
/**
* 创建一封邮件
*
* @param basicEmailParamDTO 创建邮件需要的参数
* @param inputStreamSource 附件(如果有)
* @return 一封邮件
*/
private MimeMessage createSimpleMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
// 创建邮件对象
MimeMessage message = javaMailSender.createMimeMessage();
// 使用 MimeMessageHelper 简化邮件内容和附件的设置
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(message, true);
// 设置发件人
mimeMessageHelper.setFrom(new InternetAddress(basicEmailParamDTO.getSenderUserMail()));
// 设置收件人
mimeMessageHelper.setTo(basicEmailParamDTO.getMailTo());
// 设置抄送人
if (basicEmailParamDTO.getCc() != null && basicEmailParamDTO.getCc().length > 0) {
mimeMessageHelper.setCc(basicEmailParamDTO.getCc());
}
// 设置暗送人
if (basicEmailParamDTO.getBcc() != null && basicEmailParamDTO.getBcc().length > 0) {
mimeMessageHelper.setBcc(basicEmailParamDTO.getBcc());
}
// 设置邮件主题
mimeMessageHelper.setSubject(basicEmailParamDTO.getSubject());
// 设置邮件内容HTML 格式)
mimeMessageHelper.setText(basicEmailParamDTO.getContent(), true);
// 设置附件
if (inputStreamSource != null) {
mimeMessageHelper.addAttachment(fileName, inputStreamSource);
}
return message;
}
/**
* 设置实体参数
*
* @param mailTo 接收邮件的邮箱地址
* @param jsonObject 模板中变量的值
* @return 返回一个MailEntity
* @throws AddressException 邮箱地址值异常
*/
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, JSONObject jsonObject, String templatePath, String title) throws AddressException {
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
// basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
basicEmailParamDTO.setSenderUserMail(CommonConstant.senderEmail);
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
basicEmailParamDTO.setSubject(title);
// todo 邮件模板不存在的报错与重试机制
basicEmailParamDTO.setContent(setContent(jsonObject, templatePath));
return basicEmailParamDTO;
}
public BasicEmailParamDTO setBasicEmailParams(List<String> mailTo, String title) throws AddressException {
BasicEmailParamDTO basicEmailParamDTO = new BasicEmailParamDTO();
basicEmailParamDTO.setSenderUserMail("info@aida.com.hk");
basicEmailParamDTO.setMailTo(getInternetAddressList(mailTo));
basicEmailParamDTO.setSubject(title);
return basicEmailParamDTO;
}
/**
* 将地址转换为InternetAddress类型
*
* @param addressList 普通的地址字符串列表
* @return InternetAddress类型的地址列表
* @throws AddressException 地址异常
*/
public InternetAddress[] getInternetAddressList(List<String> addressList) throws AddressException {
InternetAddress[] toAddress = new InternetAddress[addressList.size()];
for (String address : addressList) {
toAddress[addressList.indexOf(address)] = new InternetAddress(address);
}
return toAddress;
}
public String setContent(JSONObject jsonObject, String templatePath) {
Context context = new Context();
if (Objects.nonNull(jsonObject)) {
for (String key : jsonObject.keySet()) {
context.setVariable(key, jsonObject.get(key));
}
}
return templateEngine.process(templatePath, context);
}
}

View File

@@ -14,6 +14,7 @@ import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@@ -41,6 +42,16 @@ public class MinioUtil {
@Autowired
private MinioClient minioClient;
@Value("${minio.endpoint}")
private String endpoint;
/**
* 获取MinIO客户端实例
*/
public MinioClient getMinioClient() {
return minioClient;
}
/**
* description: 判断bucket是否存在不存在则创建
*
@@ -951,6 +962,166 @@ public class MinioUtil {
}
}
/**
* 检测字符串是否为预签名URL
* 通过检查URL中是否包含minio endpoint来判断
*
* @param str 待检测的字符串
* @return true表示是预签名URLfalse表示不是
*/
public boolean isPresignedUrl(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
// 检查字符串是否是一个有效的URL
URL url = new URL(str);
String host = url.getHost();
// 获取endpoint中的主机部分去掉http://或https://
String endpointHost = endpoint;
if (endpointHost.startsWith("http://")) {
endpointHost = endpointHost.substring(7);
} else if (endpointHost.startsWith("https://")) {
endpointHost = endpointHost.substring(8);
}
// 去掉端口号
if (endpointHost.contains(":")) {
endpointHost = endpointHost.substring(0, endpointHost.indexOf(":"));
}
// 检查URL的host是否与endpoint的host匹配
return host.equals(endpointHost);
} catch (Exception e) {
// 不是有效的URL
return false;
}
}
/**
* 检测字符串是否为MinIO逻辑路径bucketName/objectName格式
* 逻辑路径特点:
* 1. 包含 "/"(桶名和对象名之间的分隔符)
* 2. 不是完整的URL不以http://或https://开头)
* 3. 路径中没有查询参数
*
* @param str 待检测的字符串
* @return true表示是MinIO逻辑路径false表示不是
*/
public boolean isMinioLogicalPath(String str) {
if (str == null || str.isEmpty()) {
return false;
}
// 必须是字符串
if (!(str instanceof String)) {
return false;
}
String trimStr = str.trim();
// 不应该以http://或https://开头
if (trimStr.startsWith("http://") || trimStr.startsWith("https://")) {
return false;
}
// 应该包含 "/"bucket/object格式
if (!trimStr.contains("/")) {
return false;
}
// 不应该包含空格或特殊字符
if (trimStr.contains(" ") || trimStr.contains("\n") || trimStr.contains("\t")) {
return false;
}
return true;
}
/**
* 将预签名URL转换为逻辑路径
*
* @param presignedUrl 预签名URL
* @return 逻辑路径格式bucketName/objectName
*/
public String getLogicalPathFromPresignedUrl(String presignedUrl) {
try {
// 解析URL
URL url = new URL(presignedUrl);
// 获取路径部分(去掉开头的/
String path = url.getPath();
if (path.startsWith("/")) {
path = path.substring(1);
}
// 路径格式为 bucketName/objectName
// Minio路径中可能包含多个/,需要正确分割
int firstSlashIndex = path.indexOf("/");
if (firstSlashIndex <= 0) {
throw new MinioException("预签名URL路径格式无效应包含桶名和对象名: " + presignedUrl);
}
String bucketName = path.substring(0, firstSlashIndex);
String objectName = path.substring(firstSlashIndex + 1);
// log.info("预签名URL转换成功桶名: {}, 对象名: {}", bucketName, objectName);
return bucketName + "/" + objectName;
} catch (Exception e) {
log.error("预签名URL解析失败: {}", e.getMessage(), e);
throw new BusinessException("system.error");
}
}
/**
* 处理MinIO资源预签名URL或逻辑路径统一生成预签名URL
*
* @param resource 预签名URL或逻辑路径
* @param expires 过期时间(秒)
* @return 新的预签名URL
*/
public String processMinioResource(String resource, int expires) {
try {
String logicalPath;
if (isPresignedUrl(resource)) {
// 是预签名URL解析为逻辑路径
logicalPath = getLogicalPathFromPresignedUrl(resource);
} else if (isMinioLogicalPath(resource)) {
// 本身就是逻辑路径
logicalPath = resource.trim();
} else {
// 不认识的内容,直接返回原始值
log.warn("未识别的MinIO资源格式: {}", resource);
return resource;
}
// 统一生成预签名URL
return getPreSignedUrl(logicalPath, expires);
} catch (Exception e) {
log.error("处理MinIO资源失败: {}, error: {}", resource, e.getMessage(), e);
// 如果失败,返回原始内容
return resource;
}
}
/**
* 将任意MinIO URL转换为逻辑路径
* 检测URL类型并转换为逻辑路径返回
*
* @param url 预签名URL或逻辑路径
* @return 逻辑路径格式bucketName/objectName
* @throws MinioException 如果不是有效的MinIO资源
*/
public String convertToLogicalPath(String url) {
if (url == null || url.isEmpty()) {
throw new BusinessException("url.cannot.be.empty");
}
if (isMinioLogicalPath(url)) {
// 本身就是逻辑路径,直接返回
return url.trim();
} else if (isPresignedUrl(url)) {
// 是预签名URL转换为逻辑路径
return getLogicalPathFromPresignedUrl(url);
} else {
// 不认识的内容,抛出异常
throw new BusinessException("无法识别的MinIO资源格式: " + url + "请提供有效的预签名URL或逻辑路径");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +0,0 @@
package com.ai.da.common.utils;
import com.ai.da.model.vo.AuthPrincipalVo;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityContextUtils {
public static AuthPrincipalVo getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() != null) {
return (AuthPrincipalVo) authentication.getPrincipal();
}
return null;
}
public static Long getCurrentUserId() {
AuthPrincipalVo currentUser = getCurrentUser();
if (currentUser != null) {
return currentUser.getId();
}
return null;
}
public static String getCurrentUsername() {
AuthPrincipalVo currentUser = getCurrentUser();
if (currentUser != null) {
return currentUser.getUsername();
}
return null;
}
}

View File

@@ -0,0 +1,131 @@
package com.ai.da.common.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* Token 生成工具类(仅负责生成,不负责鉴权)。
* 鉴权逻辑已迁移至 GatewayGlobalAuthWebFilter
*/
@Slf4j
@Component
public class TokenGenerateUtils {
private static final String ISSUER = "DWJ";
@Value("${spring.security.jwtSecret:JWTSECRET}")
private String jwtSecret;
@Value("${spring.security.jwtExpiration:8640000000}")
private long jwtExpiration;
@Value("${spring.security.jwtTokenPrefix:Bearer-}")
private String jwtTokenPrefix;
/**
* 生成 JWT Token。
* @param principal 用户信息
* @return 完整的 token含 prefix
*/
public String createToken(AuthPrincipalVo principal) {
SecretKey key = buildSigningKey();
String token = Jwts.builder()
.id(String.valueOf(principal.getId()))
.subject(JSONObject.toJSONString(principal))
.issuedAt(new Date())
.issuer(ISSUER)
.expiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(key)
.compact();
return jwtTokenPrefix + token;
}
/**
* 获取 Token 过期时间(毫秒)。
*/
public long getJwtExpiration() {
return jwtExpiration;
}
/**
* 生成用于邮箱变更的简化 Token。
* @param userId 用户 ID
* @param mailbox 新邮箱
* @return token不含 prefix
*/
public String createMailboxToken(Long userId, String mailbox) {
SecretKey key = buildSigningKey();
return Jwts.builder()
.id(String.valueOf(userId))
.subject(mailbox + "_" + userId)
.issuedAt(new Date())
.issuer(ISSUER)
.expiration(new Date(System.currentTimeMillis() + CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY))
.signWith(key)
.compact();
}
/**
* 验证 Token 是否有效(签名和有效期)。
*/
public boolean validateToken(String token) {
try {
Claims claims = parseTokenBody(token);
return claims != null;
} catch (Exception e) {
return false;
}
}
/**
* 从 Token 中解析用户信息。
*/
public AuthPrincipalVo parserToUser(String token) {
try {
String subject = parseTokenBody(token).getSubject();
if (StrUtil.isNotEmpty(subject)) {
return JSONObject.parseObject(subject, AuthPrincipalVo.class);
}
} catch (Exception e) {
log.error("JWT解析用户信息失败: {}", e.getMessage());
}
return null;
}
/**
* 解析邮箱变更 Token返回 "email_id" 格式字符串。
*/
public String parseMailboxToken(String token) {
return parseTokenBody(token).getSubject();
}
private Claims parseTokenBody(String token) {
SecretKey key = buildSigningKey();
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}
private SecretKey buildSigningKey() {
byte[] raw = jwtSecret.getBytes(StandardCharsets.UTF_8);
if (raw.length < 32) {
raw = DigestUtil.sha256(raw);
}
return Keys.hmacShaKeyFor(raw);
}
}

View File

@@ -103,7 +103,7 @@ public class ConvenientInquiryController {
@GetMapping("/recentNewUserChart")
public Response<Map<String, Object>> recentNewUserChart(@Parameter(description = "startTime") @RequestParam @Nullable String startTime,
@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));
}

View File

@@ -0,0 +1,203 @@
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.model.vo.ContestantCountVO;
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 jakarta.servlet.http.HttpServletResponse;
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));
}
@GetMapping("/contestants/export")
@ApiOperation(value = "导出参赛者列表为Excel", notes = "导出所有参赛者信息为xlsx并触发下载")
public void exportContestants(HttpServletResponse response) throws Exception {
byte[] data = globalAwardService.exportContestants();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=\"contestants.xlsx\"");
response.setContentLength(data.length);
response.getOutputStream().write(data);
response.getOutputStream().flush();
}
@PostMapping("/contestants/export/files")
@ApiOperation(value = "导出参赛者文件为ZIP", notes = "根据参赛者编号范围导出PDF、视频和信息文件为ZIP直接响应给浏览器")
public void exportContestantFiles(@ApiParam(value = "参赛者文件导出请求", required = true) @RequestBody ContestantExportRequest request, HttpServletResponse response) throws Exception {
byte[] zipData = globalAwardService.exportContestantFilesAsZip(request.getMinContestantNumber(), request.getMaxContestantNumber());
if (zipData.length == 0) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("No contestants found in the specified range.");
return;
}
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"contestants.zip\"");
response.setContentLength(zipData.length);
response.getOutputStream().write(zipData);
response.getOutputStream().flush();
}
@GetMapping("/contestants/count")
@ApiOperation(value = "查询参赛者总数", notes = "查询数据库中参赛者的总数量和最大参赛者编号")
public Response<ContestantCountVO> getContestantCount() {
return Response.success(globalAwardService.getContestantCount());
}
}

View File

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

View File

@@ -0,0 +1,21 @@
package com.ai.da.feign.gateway;
import com.ai.da.common.response.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 调用 Gateway 黑名单接口,将指定用户的 token 加入黑名单。
* 替代原来的 SellerFeignClient.clearTokenCache。
*/
@FeignClient(name = "aida-gateway", path = "/internal")
public interface GatewayFeignClient {
/**
* 将用户 token 加入黑名单。
* 后续 Gateway 会拒绝携带该用户 token 的请求。
*/
@PostMapping("/logout")
Response<Void> logout(@RequestParam("userId") Long userId);
}

View File

@@ -0,0 +1,20 @@
package com.ai.da.feign.seller;
import com.ai.da.common.response.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 调用 aida-seller 设计师相关接口的 Feign 客户端
*/
@FeignClient(name = "aida-seller", path = "/api/designer")
public interface SellerFeignClient {
@GetMapping("/check")
Response<Boolean> checkDesignerQualification(@RequestParam("userId") Long userId);
@PostMapping("/cache/clear")
Response<Void> clearTokenCache(@RequestParam("userId") Long userId);
}

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

@@ -19,7 +19,7 @@ public interface DesignMapper extends CommonMapper<Design> {
Long insertDesign(Design design);
List<UserDesignStatisticDTO> getDesignStatistic(String startTime, String endTime, List<Long> ids, String email,
String role, String organizationName);
String role, String organizationName, boolean filterBySecond);
List<Design> selectDeleteList();
}

View File

@@ -0,0 +1,82 @@
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("contestant_number")
private Integer contestantNumber;
@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("video_duration")
private Integer videoDuration;
@TableField("video_size")
private Long videoSize;
@TableField("pdf_size")
private Long pdfSize;
@TableField("portfolio_url")
private String portfolioUrl;
@TableField("created_at")
private LocalDateTime createdAt;
@TableField("updated_at")
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,74 @@
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;
@ApiModelProperty(value = "视频时长(秒)", required = false, example = "120")
private Integer videoDuration;
@ApiModelProperty(value = "视频大小(字节)", required = false, example = "10485760")
private Long videoSize;
@ApiModelProperty(value = "PDF 文件大小(字节)", required = false, example = "524288")
private Long pdfSize;
// /**
// * 是否确认覆盖已存在记录false 表示发现已有记录时仅返回 existingRecord不覆盖
// */
// @ApiModelProperty(value = "是否确认覆盖已存在记录", required = false, example = "false")
// private Boolean confirm = false;
@NotBlank
private String secureToken;
}

View File

@@ -0,0 +1,19 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 参赛者文件导出请求DTO
*/
@Data
@ApiModel(value = "参赛者文件导出请求", description = "用于导出指定范围的参赛者文件")
public class ContestantExportRequest {
@ApiModelProperty(value = "最小参赛者编号", required = true, example = "10000")
private Integer minContestantNumber;
@ApiModelProperty(value = "最大参赛者编号", required = true, example = "10010")
private Integer maxContestantNumber;
}

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
package com.ai.da.model.dto;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 图片处理请求体
*/
@Data
@Builder
public class ImageProcessRequest {
/**
* OSS桶名bucket_name
*/
private String bucket_name;
/**
* OSS对象名object_name
*/
private String object_name;
/**
* 输入图片路径列表input_image_paths
*/
private List<String> input_image_paths;
/**
* 图像宽度width
*/
private Integer width;
/**
* 图像高度height
*/
private Integer height;
/**
* 文本提示prompt
*/
private String prompt;
/**
* 推理步数steps
*/
private Integer steps;
/**
* 引导系数guidance
*/
private Double guidance;
}

View File

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

View File

@@ -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

@@ -0,0 +1,17 @@
package com.ai.da.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContestantCountVO {
private Long count;
private Integer maxContestantNumber;
}

View File

@@ -1,5 +1,5 @@
package com.ai.da.model.vo;
package com.ai.da.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import com.ai.da.mapper.primary.entity.Gradient;
@@ -62,11 +62,11 @@ public class DesignItemClothesDetailVO {
@Schema(description = "渐变色信息")
private Gradient gradient;
@Schema(description = "未分割的图层")
/* @Schema(description = "未分割的图层")
private String undividedLayer;
@Schema(description = "添加single印花的未分割的图层")
private String undividedLayerWithSinglePrint;
private String undividedLayerWithSinglePrint;*/
@Schema(description = "局部design")
private PartialDesignDTO partialDesign;

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
package com.ai.da.python.vo;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Data
public class DesignPythonItemPrint {
@@ -53,7 +53,7 @@ public class DesignPythonItemPrint {
if (ifDesign){
this.print_path_list = print_path_list;
this.location = Collections.singletonList(Arrays.asList(0.0f, 0.0f));
this.print_scale_list = Collections.singletonList(Arrays.asList(0.0f, 0.0f));
this.print_scale_list = Collections.singletonList(Arrays.asList(1.0f, 1.0f));
this.print_angle_list = Arrays.asList(0.0, 0.0);
}

View File

@@ -0,0 +1,37 @@
package com.ai.da.seller;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 设计URLs DTO
*/
@Data
@Schema(description = "设计URLs数据传输对象")
public class DesignUrlsDTO {
/**
* 设计项ID
*/
@Schema(description = "设计项ID", example = "1")
private Long designItemId;
/**
* TO_PRODUCT_IMAGE类型的URL列表
*/
@Schema(description = "TO_PRODUCT_IMAGE类型的URL列表")
private List<String> toProductImageUrls;
/**
* DesignItemDetail的path列表
*/
@Schema(description = "DesignItemDetail的path列表")
private List<String> clothes;
/**
* 姿势转换视频信息列表
*/
@Schema(description = "姿势转换视频信息列表")
private List<PoseTransformationVideoDTO> videos;
}

View File

@@ -0,0 +1,30 @@
package com.ai.da.seller;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 姿势转换视频信息DTO
*/
@Data
@Schema(description = "姿势转换视频信息数据传输对象")
public class PoseTransformationVideoDTO {
/**
* GIF第一帧截图URL
*/
@Schema(description = "GIF第一帧截图URL")
private String firstFrameUrl;
/**
* GIF视频URL
*/
@Schema(description = "GIF视频URL")
private String gifUrl;
/**
* 视频URL
*/
@Schema(description = "视频URL")
private String videoUrl;
}

View File

@@ -0,0 +1,44 @@
package com.ai.da.seller;
import com.ai.da.common.response.Response;
import com.ai.da.service.UserLikeGroupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* Seller Controller
*/
@RestController
@RequestMapping("/api/seller")
@RequiredArgsConstructor
@Tag(name = "Seller", description = "Seller相关接口")
public class SellerController {
private final UserLikeGroupService userLikeGroupService;
/**
* 根据designItemId列表获取设计相关的URL列表
* @param designItemIds designItemId列表
* @return 设计URLs DTO列表
*/
@GetMapping("/sketchDetail")
@Operation(summary = "获取设计相关URL列表", description = "根据designItemId列表获取设计相关的URL列表包括TO_PRODUCT_IMAGE类型的URL和DesignItemDetail的path列表")
public Response<List<DesignUrlsDTO>> getDesignUrlsByDesignItemIds(
@Parameter(description = "设计项ID列表", required = true, example = "1,2,3")
@RequestParam List<Long> designItemIds) {
List<DesignUrlsDTO> designUrlsDTOList = new ArrayList<>();
for (Long designItemId : designItemIds) {
DesignUrlsDTO designUrlsDTO = userLikeGroupService.getToProductImageUrlsByDesignItemId(designItemId);
designUrlsDTOList.add(designUrlsDTO);
}
return Response.success(designUrlsDTOList);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
package com.ai.da.service;
import com.ai.da.model.dto.ContestantDTO;
import com.ai.da.model.vo.CheckOTPVO;
import com.ai.da.model.vo.ContestantCountVO;
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);
/**
* 导出参赛者列表为 Excel二进制
* @return Excel 文件的字节数组
*/
byte[] exportContestants() throws Exception;
/**
* 将参赛者列表导出并保存到服务端本地目录(使用服务配置的 uploadDir/exports
*/
void saveContestantsToLocal() throws Exception;
/**
* 将参赛者文件打包为 ZIP 并返回字节数组(不落盘,直接响应给浏览器)
* @param minContestantNumber 最小参赛者编号
* @param maxContestantNumber 最大参赛者编号
* @return ZIP 文件的字节数组
*/
byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception;
/**
* 查询参赛者总数和最大参赛者编号
* @return 参赛者数量和最大参赛者编号
*/
ContestantCountVO getContestantCount();
}

View File

@@ -7,6 +7,8 @@ public interface RabbitMQService {
void publishMessageToGenerate(String message);
void publishMessageToGenerateResult(String message);
void publishMessageToSR(String message);
Integer getMessageCount(String queueUrl);

View File

@@ -1,124 +1,132 @@
package com.ai.da.service;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.*;
import com.ai.da.model.vo.*;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import io.minio.errors.MinioException;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* 服务类
*
* @author yanglei
* @since 2022-09-11
*/
public interface UserLikeGroupService extends IService<UserLikeGroup> {
void deleteUserGroup(Long userGroupId);
HistoryUpdateVO updateUserGroupName(Long userGroupId, String userGroupName, String timeZone);
Long insertUserGroup(Long userId, Long collectionId, String timeZone, Long projectId);
/**
* choose
*
* @param userGroupId
* @return
*/
UserLikeChooseVO choose(Long userGroupId);
ProjectChooseVO choose(ProjectDTO projectDTO);
UserLikeGroup getByProjectId(Long projectId);
void deleteTrialData(Long id);
void updateDate(Long id, String timeZone);
Long exportSave(MultipartFile file, Long projectId, String module, Long designItemDetailId);
List<ToProductImageResultVO> toProduct(ToProductImageDTO toProductImageDTO);
void toProduct(String taskId);
ToProductElementVO toProductImageElementUpload(MultipartFile file, Long projectId, String type);
CollectionSort productImageLike(ProductImageLikeDTO productImageLikeDTO);
List<MagicToolResultVO> getToProductImageResultList(List<String> taskIdList);
JSONObject exportSearch(ExportSearchDTO exportSearchDTO);
CanvasElementUpload canvasElementUpload(MultipartFile file);
List<ToProductImageResultVO> productImageLikeList(ToProductImageDTO toProductImageDTO);
Boolean productImageUnLike(ProductImageLikeDTO productImageLikeDTO);
void relight(String taskId);
List<ToProductImageResultVO> relight(ToProductImageDTO toProductImageDTO);
List<MagicToolResultVO> getRelightResult(List<String> taskIdList);
void deleteToProductRelightResult(Long id, Long projectId, String type);
String likeHistoryRelSketch();
String download();
Boolean productImageInitialize(ProductImageInitializeDTO productImageInitializeDTO);
InitializeProgressVO getInitializeProgress(ProductImageInitializeDTO productImageInitializeDTO);
IPage<ProjectVO> getPage(ProjectQueryDTO projectQueryDTO);
ModuleChooseVO getModuleContent(ProjectDTO projectDTO);
ModuleChooseVO saveModuleContent(ModuleSaveDTO moduleSaveDTO);
QueryLibraryPageVO getMannequinDetail(MannequinDTO mannequinDTO);
BrandLogoUploadVO brandLogoUpload(MultipartFile file);
Boolean brandDNASaveOrUpdate(BrandDNADTO brandDNADTO);
LibraryUpdateVo brandDNAUpload(MultipartFile file, Long brandId) throws IOException;
PageBaseResponse<BrandDNAVO> brandDNAPage(BrandDNAQueryDTO brandDNAQueryDTO);
BrandDNAGenerateVO brandDNAGenerate(String prompt);
IPage<ThreeDLayoutVO> getThreeDLayoutPage(ThreeDLayoutQueryDTO threeDLayoutQueryDTO);
ThreeDVO getLayoutDetail(Long threeDSimpleId);
ThreeDSizeVO getThreeDSize(Long threeDSimpleId);
void getThreeDGlb(Long threeDSimpleId, HttpServletResponse response) throws MinioException, IOException;
String downloadZip(Long threeDSimpleId, String sizeType, String size, HttpServletResponse response) throws MinioException, IOException;
Boolean delete(Long projectId);
Boolean brandDNADelete(BrandDNADTO brandDNADTO);
void toProductBatch(String taskId, String url, String progress) throws InterruptedException;
void relightBatch(String taskId, String url, String progress);
Boolean collectionLikeUpdate(CollectionLikeUpdateDTO collectionLikeUpdateDTO);
Boolean toProductImageElementDelete(Long id);
ToProductElementVO convertRelightElement(Long id);
}
package com.ai.da.service;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.*;
import com.ai.da.model.vo.*;
import com.ai.da.seller.DesignUrlsDTO;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import io.minio.errors.MinioException;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* 服务类
*
* @author yanglei
* @since 2022-09-11
*/
public interface UserLikeGroupService extends IService<UserLikeGroup> {
void deleteUserGroup(Long userGroupId);
HistoryUpdateVO updateUserGroupName(Long userGroupId, String userGroupName, String timeZone);
Long insertUserGroup(Long userId, Long collectionId, String timeZone, Long projectId);
/**
* choose
*
* @param userGroupId
* @return
*/
UserLikeChooseVO choose(Long userGroupId);
ProjectChooseVO choose(ProjectDTO projectDTO);
UserLikeGroup getByProjectId(Long projectId);
void deleteTrialData(Long id);
void updateDate(Long id, String timeZone);
Long exportSave(MultipartFile file, Long projectId, String module, Long designItemDetailId);
List<ToProductImageResultVO> toProduct(ToProductImageDTO toProductImageDTO);
void toProduct(String taskId);
ToProductElementVO toProductImageElementUpload(MultipartFile file, Long projectId, String type);
CollectionSort productImageLike(ProductImageLikeDTO productImageLikeDTO);
List<MagicToolResultVO> getToProductImageResultList(List<String> taskIdList);
JSONObject exportSearch(ExportSearchDTO exportSearchDTO);
CanvasElementUpload canvasElementUpload(MultipartFile file);
List<ToProductImageResultVO> productImageLikeList(ToProductImageDTO toProductImageDTO);
Boolean productImageUnLike(ProductImageLikeDTO productImageLikeDTO);
void relight(String taskId);
List<ToProductImageResultVO> relight(ToProductImageDTO toProductImageDTO);
List<MagicToolResultVO> getRelightResult(List<String> taskIdList);
void deleteToProductRelightResult(Long id, Long projectId, String type);
String likeHistoryRelSketch();
String download();
Boolean productImageInitialize(ProductImageInitializeDTO productImageInitializeDTO);
InitializeProgressVO getInitializeProgress(ProductImageInitializeDTO productImageInitializeDTO);
IPage<ProjectVO> getPage(ProjectQueryDTO projectQueryDTO);
ModuleChooseVO getModuleContent(ProjectDTO projectDTO);
ModuleChooseVO saveModuleContent(ModuleSaveDTO moduleSaveDTO);
QueryLibraryPageVO getMannequinDetail(MannequinDTO mannequinDTO);
BrandLogoUploadVO brandLogoUpload(MultipartFile file);
Boolean brandDNASaveOrUpdate(BrandDNADTO brandDNADTO);
LibraryUpdateVo brandDNAUpload(MultipartFile file, Long brandId) throws IOException;
PageBaseResponse<BrandDNAVO> brandDNAPage(BrandDNAQueryDTO brandDNAQueryDTO);
BrandDNAGenerateVO brandDNAGenerate(String prompt);
IPage<ThreeDLayoutVO> getThreeDLayoutPage(ThreeDLayoutQueryDTO threeDLayoutQueryDTO);
ThreeDVO getLayoutDetail(Long threeDSimpleId);
ThreeDSizeVO getThreeDSize(Long threeDSimpleId);
void getThreeDGlb(Long threeDSimpleId, HttpServletResponse response) throws MinioException, IOException;
String downloadZip(Long threeDSimpleId, String sizeType, String size, HttpServletResponse response) throws MinioException, IOException;
Boolean delete(Long projectId);
Boolean brandDNADelete(BrandDNADTO brandDNADTO);
void toProductBatch(String taskId, String url, String progress) throws InterruptedException;
void relightBatch(String taskId, String url, String progress);
Boolean collectionLikeUpdate(CollectionLikeUpdateDTO collectionLikeUpdateDTO);
Boolean toProductImageElementDelete(Long id);
ToProductElementVO convertRelightElement(Long id);
/**
* 根据designItemId获取TO_PRODUCT_IMAGE类型的URL列表和DesignItemDetail的path列表
* @param designItemId designItemId
* @return 包含TO_PRODUCT_IMAGE类型的URL列表和DesignItemDetail的path列表的对象
*/
DesignUrlsDTO getToProductImageUrlsByDesignItemId(Long designItemId);
}

View File

@@ -10,7 +10,9 @@ import com.ai.da.common.enums.LoginTypeEnum;
import com.ai.da.common.enums.ProductEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.TokenGenerateUtils;
import com.ai.da.feign.gateway.GatewayFeignClient;
import com.ai.da.feign.seller.SellerFeignClient;
import com.ai.da.common.utils.*;
import com.ai.da.mapper.primary.*;
import com.ai.da.mapper.primary.entity.*;
@@ -39,9 +41,10 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
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.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -83,6 +86,9 @@ import java.util.stream.Collectors;
@Slf4j
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
@Resource
private ApplicationContext applicationContext;
@Resource
private AccountMapper accountMapper;
@@ -90,7 +96,13 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
private AccountExtendMapper accountExtendMapper;
@Resource
private JWTTokenHelper jwtTokenHelper;
private TokenGenerateUtils tokenGenerateUtils;
@Resource
private SellerFeignClient sellerFeignClient;
@Resource
private GatewayFeignClient gatewayFeignClient;
@Resource
private AccountLoginLogService accountLoginLogService;
@@ -132,9 +144,6 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Resource
private RedisUtil redisUtil;
@Resource
private com.ai.da.common.security.config.SecurityProperties securityProperties;
@Resource
private UserFollowService userFollowService;
@@ -240,12 +249,13 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class);
response.setEmail(account.getUserEmail());
String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId()));
if (StringUtils.isNotBlank(token)) {
/*if (StringUtils.isNotBlank(token)) {
//用户已登入
response.setToken(token);
} else {
response.setToken(createAccountToken(account));
}
}*/
response.setToken(createAccountToken(account));
response.setUserId(account.getId());
response.setSystemUser(account.getSystemUser());
// 设置头像
@@ -279,7 +289,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
// 定义常量(临时)
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();
if (account.getSystemUser().equals(0)) {
return;
@@ -297,7 +307,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
if (isEduAdmin) {
setEduAdminToExpire(account);
} else {
toVisitor(account);
// 这里调用代理的 toVisitor 方法
AccountService proxy = applicationContext.getBean(AccountService.class);
proxy.toVisitor(account);
// return;
throw new BusinessException("account.expired", ResultEnum.PROMPT.getCode());
}
@@ -346,11 +358,13 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
principal.setUsername(account.getUserName());
principal.setLanguage(account.getLanguage());
principal.setCountry(account.getCountry());
String token2 = jwtTokenHelper.createToken(principal);
//区分买家端登录
principal.setSource("AIDA");
String token2 = tokenGenerateUtils.createToken(principal);
// 本地 JVM 缓存(适配旧逻辑)
LocalCacheUtils.setTokenCache(String.valueOf(account.getId()), token2);
// 同步写入 Redis重启后仍然可用
long jwtExpiration = securityProperties.getJwtExpiration();
long jwtExpiration = tokenGenerateUtils.getJwtExpiration();
redisUtil.setLoginToken(account.getId(), token2, jwtExpiration);
return token2;
}
@@ -607,11 +621,23 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Override
public Boolean logout(AccountLogoutDTO accountLogoutDTO) {
// jwt 本身失效比较难做,统一用缓存实现:删除缓存即失效
// 1. 删除本地缓存(保留,防止 Gateway 未启动时还能本地验证)
String userIdStr = String.valueOf(accountLogoutDTO.getUserId());
LocalCacheUtils.delTokenCache(userIdStr);
// 同时删除 Redis 中的 token,防止服务重启后仍然有效
// 2. 删除 Redis 中的 tokenGateway 黑名单会接力生效)
redisUtil.deleteLoginToken(accountLogoutDTO.getUserId());
// 3. 调用 Gateway 黑名单接口,将 token 加入 Redis 黑名单
try {
gatewayFeignClient.logout(accountLogoutDTO.getUserId());
} catch (Exception e) {
log.warn("调用 Gateway 黑名单接口失败userId={}, error={}", accountLogoutDTO.getUserId(), e.getMessage());
}
// 4. 同步调用 seller 清除本地缓存(兼容未接入 Gateway 的节点)
try {
sellerFeignClient.clearTokenCache(accountLogoutDTO.getUserId());
} catch (Exception e) {
log.warn("调用 seller 清理缓存失败userId={}, error={}", accountLogoutDTO.getUserId(), e.getMessage());
}
return Boolean.TRUE;
}
@@ -1949,6 +1975,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
return baseMapper.selectList(queryWrapper);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void toVisitor(Account account) {
accountMapper.toVisitor(account.getId());
}
@@ -2145,7 +2172,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
redisUtil.addToString(key, newMailbox, CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY / 1000);
String username = userHolder.getUsername();
String token = jwtTokenHelper.createToken(accountId, newMailbox);
String token = tokenGenerateUtils.createMailboxToken(accountId, newMailbox);
// 准备激活链接,链接应该要有有效期
String link = "?" + token;
// 向新邮箱发送邮件,邮件附带激活链接,点击链接进行验证
@@ -2155,7 +2182,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
// 验证激活链接
public void activateNewEmail(String token){
// 获取链接地址信息,更新指定用户邮箱
String emailAndId = jwtTokenHelper.parseToEmailAndId(token);
String emailAndId = tokenGenerateUtils.parseMailboxToken(token);
String newMailbox = emailAndId.substring(0, emailAndId.lastIndexOf("_"));
String accountId = emailAndId.substring(emailAndId.lastIndexOf("_") + 1);
@@ -2507,7 +2534,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());
}

View File

@@ -520,14 +520,16 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
.filter(f -> f.getDesignType().equals(DesignTypeEnum.COLLECTION.getRealName()))
.map(DesignCollectionPrintElementDTO::getId)
.collect(Collectors.toList());
List<CollectionElement> printBoardElements = new ArrayList<>();
elementVO.setPrintBoardElements(printBoardElements);
if (!CollectionUtils.isEmpty(printBoardIds)) {
// 从数据库批量查询printBoard元素
List<CollectionElement> printBoardElements = collectionElementMapper.selectBatchIds(printBoardIds);
printBoardElements.addAll(collectionElementMapper.selectBatchIds(printBoardIds));
// 验证查询结果的完整性
if (CollectionUtil.isEmpty(printBoardElements) || printBoardElements.size() != printBoardIds.size()) {
throw new BusinessException("get.printBoards.data.is.mismatch");
}
elementVO.setPrintBoardElements(printBoardElements);
// elementVO.setPrintBoardElements(printBoardElements);
usedElementIds.addAll(printBoardIds); // 记录已使用的元素ID
}
// 处理类型为LIBRARY的printBoard元素
@@ -543,7 +545,8 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
Map<Long, DesignCollectionPrintElementDTO> idToMap = designDTO.getPrintBoards()
.stream()
.collect(Collectors.toMap(DesignCollectionPrintElementDTO::getId, v -> v));
libraryCollectionElements.addAll(covertLibrarysToPrintCollections(librarys, idToMap));
printBoardElements.addAll(covertLibrarysToPrintCollections(librarys, idToMap));
// libraryCollectionElements.addAll(covertLibrarysToPrintCollections(librarys, idToMap));
}
}
@@ -559,7 +562,8 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
Map<Long, DesignCollectionPrintElementDTO> idToMap = designDTO.getPrintBoards()
.stream()
.collect(Collectors.toMap(DesignCollectionPrintElementDTO::getId, v -> v));
generateCollectionElements.addAll(covertGeneratesToPrintCollections(generateDetailList, idToMap));
printBoardElements.addAll(covertGeneratesToPrintCollections(generateDetailList, idToMap));
// generateCollectionElements.addAll(covertGeneratesToPrintCollections(generateDetailList, idToMap));
}
}
}

View File

@@ -33,7 +33,6 @@ import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -149,7 +148,17 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
|| ADMIN_IDS.contains(account.getId())
|| ADMIN_IDS_READ_ONLY.contains(account.getId())
)) {
if (StringUtil.isNullOrEmpty(startTime)) startTime = "2024-02-01 00:00:00";
boolean filterBySecond ;
if (StringUtil.isNullOrEmpty(startTime)) {
startTime = "2024-02-01 00:00:00";
filterBySecond = true;
} else {
LocalDateTime thresholdTime = LocalDateTime.of(2024, 5, 1, 0, 0, 0);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime startDateTime = LocalDateTime.parse(startTime, formatter);
filterBySecond = startDateTime.isBefore(thresholdTime);
}
if (StringUtil.isNullOrEmpty(endTime)) {
// yyyy-MM-dd HH:mm:ss "HH"表示24小时制 "hh"表示12小时制
endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
@@ -173,7 +182,7 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
default:
throw new BusinessException("have.no.permission", ResultEnum.PROMPT.getCode());
}
return designMapper.getDesignStatistic(startTime, endTime, ids, email, role, account.getOrganizationName());
return designMapper.getDesignStatistic(startTime, endTime, ids, email, role, account.getOrganizationName(), filterBySecond);
} else {
throw new BusinessException("have.no.permission", ResultEnum.PROMPT.getCode());
}
@@ -695,14 +704,19 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
String username = UserContext.getUserHolder().getUsername();
Account account = new Account();
account.setId(accountId);
// 修改用户有效期截止日期、用户类型、积分
if (!Objects.isNull(validEndTime)) {
account.setValidEndTime(validEndTime);
log.info("管理员:{},修改用户 {} 信息,将账号到期时间置为:{}", username, accountId, validEndTime);
}
if (!Objects.isNull(systemUser)) {
if (!Objects.isNull(systemUser) && !systemUser.equals(0)) {
account.setSystemUser(systemUser);
log.info("管理员:{},修改用户 {} 信息,将账号身份置为:{}", username, accountId, systemUser);
} else if (systemUser.equals(0)){
// 将用户身份设置为游客
accountService.toVisitor(account);
return true;
}
/*if (!StringUtils.isNullOrEmpty(systemUser)) {
int systemUser = 0;
@@ -728,7 +742,6 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
}
// todo 如果修改管理员账号的积分上限或子账号数量,则其所有子账号的积分上限需要重新计算
account.setId(accountId);
account.setUpdateDate(new Date());
return accountMapper.updateById(account) == 1;
// accountService.update(account,null);
@@ -857,19 +870,20 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
if (!StringUtil.isNullOrEmpty(queryPaymentInfoDTO.getOrder()) && queryPaymentInfoDTO.getOrder().equals("ASC")) {
order = "ASC";
}
String status = StringUtil.isNullOrEmpty(queryPaymentInfoDTO.getStatus()) ? "Success" : queryPaymentInfoDTO.getStatus();
List<PaymentInfoVO> paymentInfoVOS = paymentInfoMapper.queryPaymentInfo(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(),
queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(),
queryPaymentInfoDTO.getType(), status,
queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(),
queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(),
size, offset, order, queryPaymentInfoDTO.getPayer());
// 查询数据总量
Long total = paymentInfoMapper.queryPaymentInfoCount(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(),
queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(),
queryPaymentInfoDTO.getType(), status,
queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(),
queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(), queryPaymentInfoDTO.getPayer());
// 查询符合查询条件的总金额
BigDecimal payerTotal = paymentInfoMapper.queryTotalPaymentAmount(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(),
queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(),
queryPaymentInfoDTO.getType(), status,
queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(),
queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(), queryPaymentInfoDTO.getPayer());
// 总页数

View File

@@ -363,9 +363,9 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
public List<TDesignPythonOutfitDetail> saveDesignSingleItemDetailAndLayers(DesignPythonObjects pythonObjects
, Long designId, Long designItemId, Long userId
, JSONObject outfit, String timeZone, List<DesignSingleItemDTO> designSingleItemDTOList
, Map<String, List<String>> priorityAndUndividedLayer
/*, Map<String, List<String>> priorityAndUndividedLayer*/
, boolean changeModelFlag
, Long modelId, String modelType, boolean isSingleCollectionFlag) {
, Long modelId, String modelType, boolean isSingleCollectionFlag, String designType) {
DesignItem designItem = new DesignItem();
// String url = pythonObjects.getObjects().get(0).getBasic().getSave_name();
@@ -424,11 +424,15 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
designItemDetail.setColor(detail.getColor());
designItemDetail.setIconPath(detail.getIcon());
// designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getType().toLowerCase()));
/*// 取消存储UndividedLayer和UndividedLayerWithSinglePrint字段
if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(0));
designItemDetail.setUndividedLayerWithSinglePrint(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1));
}
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(0))) {
designItemDetail.setUndividedLayer(priorityAndUndividedLayer.get(detail.getPriority().toString()).getFirst());
}
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1))) {
designItemDetail.setUndividedLayerWithSinglePrint(priorityAndUndividedLayer.get(detail.getPriority().toString()).get(1));
}
}*/
// 印花存储在design_item_detail_print表中 这里还要存吗?
// DesignPythonItemPrint printObject = detail.getPrintToPython();
// designItemDetail.setPrintPath(Objects.isNull(printObject) ? "" : printObject.getPath());
@@ -436,7 +440,18 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
// designItemDetail.setPrintJson(JSON.toJSONString(printObject));
designItemDetail.setPartialDesign(Objects.isNull(detail.getPrint()) ? null : detail.getPrint().getPartial());
designItemDetail.setGradientString(detail.getGradientString());
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,11 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
priorityOffset = designSingleItemDTOList.stream()
.collect(Collectors.toMap(DesignSingleItemDTO::getPriority, DesignSingleItemDTO::getOffset));
}
// 创建 priority 到 DesignSingleItemDTO 的映射,用于获取 transpose 和 rotate
Map<Integer, DesignSingleItemDTO> priorityToItemDTOMap = designSingleItemDTOList.stream()
.collect(Collectors.toMap(DesignSingleItemDTO::getPriority, dto -> dto, (old, newVal) -> old));
List<TDesignPythonOutfitDetail> list = new ArrayList<>();
for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i);
@@ -509,8 +529,12 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
designPythonOutfitDetail.setOffset(String.valueOf(priorityOffset.get(Math.abs(priority))));
}
designPythonOutfitDetail.setPriority(priority);
designPythonOutfitDetail.setTranspose(jsonObject.getString("transpose"));
designPythonOutfitDetail.setRotate(jsonObject.getDouble("rotate"));
// 从前端传入的 DesignSingleItemDTO 中获取 transpose 和 rotate不再从 Python 返回的数据获取
DesignSingleItemDTO itemDTO = priorityToItemDTOMap.get(Math.abs(priority));
if (itemDTO != null) {
designPythonOutfitDetail.setTranspose(itemDTO.getTranspose() != null ? Arrays.toString(itemDTO.getTranspose()) : null);
designPythonOutfitDetail.setRotate(itemDTO.getRotate());
}
list.add(designPythonOutfitDetail);
}
@@ -685,6 +709,9 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
@Override
@Transactional(rollbackFor = Exception.class)
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数据太长所以这里去掉
DesignSingleIncludeLayersDTO clone = SerializationUtils.clone(designSingleIncludeLayersDTO);
clone.getDesignSingleItemDTOList().forEach(i -> {
@@ -708,6 +735,17 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
log.info("set partialDesignBase64为空便于日志打印");
i.getPartialDesign().setPartialDesignBase64(null);
}
// undividedLayerBase64 undividedLayerWithSinglePrintBase64 置空
/*// 前端合成的未分割的图
if (!StringUtil.isNullOrEmpty(i.getUndividedLayerBase64())) {
log.info("set UndividedLayerBase64为空便于日志打印");
i.setUndividedLayerBase64(null);
}
// 前端合成的未分割的图
if (!StringUtil.isNullOrEmpty(i.getUndividedLayerWithSinglePrintBase64())) {
log.info("set UndividedLayerWithSinglePrintBase64为空便于日志打印");
i.setUndividedLayerWithSinglePrintBase64(null);
}*/
});
log.info("designSingle request入参 ==> " + JSONObject.toJSONString(clone));
@@ -802,7 +840,7 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
maskBase64ToPath(designSingleIncludeLayersDTO, setNull);
// maskBase64ToPath(designSingleIncludeLayersDTO, Boolean.TRUE);
partialDesignBase64ToImage(designSingleIncludeLayersDTO, userId, designSingleIncludeLayersDTO.getIsPreview());
partialDesignBase64ToImage(designSingleIncludeLayersDTO, userId, designSingleIncludeLayersDTO.getIsPreview(), designSingleIncludeLayersDTO.getDesignType());
// 组装入参
DesignPythonObjects objects = pythonService.covertDesignSingleParam(
@@ -820,13 +858,13 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
JSONObject outfit = data.getJSONObject("0");
JSONArray layers = outfit.getJSONArray("layers");
Map<String, List<String>> priorityAndUndividedLayer = setPriorityAndUndividedLayer(layers);
// Map<String, List<String>> priorityAndUndividedLayer = setPriorityAndUndividedLayer(layers, designSingleIncludeLayersDTO);
if (!designSingleIncludeLayersDTO.getIsPreview()) {
// 更新及保存图层信息
tDesignPythonOutfitDetails = saveDesignSingleItemDetailAndLayers(objects, design.getId(), designSingleIncludeLayersDTO.getDesignItemId()
, userId, outfit, designSingleIncludeLayersDTO.getTimeZone()
, designSingleIncludeLayersDTO.getDesignSingleItemDTOList()
, priorityAndUndividedLayer, changeModelFlag, modelId, modelType, isSingleCollectionFlag);
/*, priorityAndUndividedLayer*/, changeModelFlag, modelId, modelType, isSingleCollectionFlag, designSingleIncludeLayersDTO.getDesignType());
saveCollectionElement(designSingleIncludeLayersDTO);
} else {
@@ -860,8 +898,8 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
outfit.getString("synthesis_url"),
designSingleIncludeLayersDTO.getDesignSingleItemDTOList(),
detailsVO,
design.getSingleOverall(),
priorityAndUndividedLayer);
design.getSingleOverall()/*,
priorityAndUndividedLayer*/);
}
// 方法1仅查询无事务
@@ -921,12 +959,12 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
item.setMaskUrl(path);
}
}
log.info("服装{} 的maskUrl为null", item.getType());
// log.info("服装{} 的maskUrl为null", item.getType());
}
});
}
private void partialDesignBase64ToImage(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO, Long accountId, boolean preview) {
private void partialDesignBase64ToImage(DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO, Long accountId, boolean preview, String designType) {
designSingleIncludeLayersDTO.getDesignSingleItemDTOList().forEach(item -> {
PartialDesignDTO partialDesignDTO = item.getPartialDesign();
if (!Objects.isNull(item.getPartialDesign())
@@ -948,21 +986,90 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
item.getPartialDesign().setPartialDesignMinioPath(newPath);
} else if (Objects.isNull(item.getPartialDesign())
|| 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
public Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers) {
HashMap<String, List<String>> priorityAndLayer = new HashMap<>();
for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i);
String priority = jsonObject.getString("priority");
String category = jsonObject.getString("image_category").split("_")[0];
if (!category.equals("body") && !priorityAndLayer.containsKey(priority))
priorityAndLayer.put(priority, Arrays.asList(jsonObject.getString("pattern_overall_image_url"), jsonObject.getString("pattern_print_image_url")));
public Map<String, List<String>> setPriorityAndUndividedLayer(JSONArray layers, DesignSingleIncludeLayersDTO designSingleIncludeLayersDTO) {
String designType = "default";
if (Objects.nonNull(designSingleIncludeLayersDTO)) {
designType = designSingleIncludeLayersDTO.getDesignType();
}
HashMap<String, List<String>> priorityAndLayer = new HashMap<>();
if (designType.equals("default")) {
for (int i = 0; i < layers.size(); i++) {
JSONObject jsonObject = layers.getJSONObject(i);
String priority = jsonObject.getString("priority");
String category = jsonObject.getString("image_category").split("_")[0];
if (!category.equals("body") && !priorityAndLayer.containsKey(priority)) {
// pattern_overall_image_url | pattern_print_image_url 这俩字段来源有俩merge模式下来自前端default模式下来自python
priorityAndLayer.put(priority, Arrays.asList(jsonObject.getString("pattern_overall_image_url"), jsonObject.getString("pattern_print_image_url")));
}
}
} else {
if (designSingleIncludeLayersDTO.getIsPreview()) {
// 如果是预览,则不处理、不存储前端传过来的数据
return priorityAndLayer;
}
undividedLayerBase64ToImage(designSingleIncludeLayersDTO.getDesignSingleItemDTOList());
for (DesignSingleItemDTO designSingleItemDTO : designSingleIncludeLayersDTO.getDesignSingleItemDTOList()) {
priorityAndLayer.put(designSingleItemDTO.getPriority().toString(), Arrays.asList(designSingleItemDTO.getUndividedLayerBase64(), designSingleItemDTO.getUndividedLayerWithSinglePrintBase64()));
}
}
return priorityAndLayer;
}
@@ -1077,8 +1184,8 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
String currentFullBodyView,
List<DesignSingleItemDTO> designSingleItemDTOList,
List<DesignPythonOutfitVO> layersObject,
String singleOrOverall,
Map<String, List<String>> priorityAndUndividedLayer) {
String singleOrOverall/*,
Map<String, List<String>> priorityAndUndividedLayer*/) {
DesignSingleVO designSingleVO = new DesignSingleVO();
ArrayList<DesignItemClothesDetailVO> clothes = new ArrayList<>();
@@ -1118,10 +1225,15 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
String preSignedUrl = StringUtil.isNullOrEmpty(partialDesignMinioPath) ? null : minioUtil.getPreSignedUrl(partialDesignMinioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true);
designItemClothesDetailVO.setPartialDesign(new PartialDesignDTO(partialDesignMinioPath, preSignedUrl));
/*// 取消存储/返回UndividedLayer和UndividedLayerWithSinglePrint字段
if (priorityAndUndividedLayer.containsKey(singleItem.getPriority().toString())) {
designItemClothesDetailVO.setUndividedLayer(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(0), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
designItemClothesDetailVO.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
}
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(0))) {
designItemClothesDetailVO.setUndividedLayer(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).getFirst(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
}
if (!StringUtil.isNullOrEmpty(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1))) {
designItemClothesDetailVO.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(priorityAndUndividedLayer.get(singleItem.getPriority().toString()).get(1), CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true));
}
}*/
body.setLayersObject(layersObject.stream().filter(layers -> layers.getImageCategory().equals("body")).collect(Collectors.toList()));
clothes.add(designItemClothesDetailVO);
@@ -1297,6 +1409,9 @@ public class DesignItemServiceImpl extends ServiceImpl<DesignItemMapper, DesignI
}
}
if (Objects.isNull(designSingleItem.getPrintObject()) || Objects.isNull(designSingleItem.getPrintObject().getPrints())) {
return;
}
// 添加print到library
designSingleItem.getPrintObject().getPrints().forEach(print -> {
if (!StringUtil.isNullOrEmpty(print.getDesignType()) && print.getDesignType().equals("Collection")) {

View File

@@ -46,6 +46,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
@@ -715,7 +716,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
(existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
log.info("all typeLayers Map:{}", typeAndUndividedLayer);
Map<String, List<String>> priorityAndUndividedLayer = designItemService.setPriorityAndUndividedLayer(layers);
Map<String, List<String>> priorityAndUndividedLayer = designItemService.setPriorityAndUndividedLayer(layers, null);
for (DesignPythonItem detail : item.getItems()) {
if (null == detail) {
continue;
@@ -755,7 +756,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
print.setPosition("[0.0,0.0]");
// print.setScale(1d);
// todo mark 将print默认scale置为0.3
print.setScale(Arrays.toString(new Float[]{0.3f, 0.3f}));
print.setScale(Arrays.toString(new Float[]{1.0f, 1.0f}));
print.setAngle(0.0);
print.setPriority(1);
QueryWrapper<CollectionElement> getPrintboardLevel2TypeQw = new QueryWrapper<>();
@@ -763,7 +764,20 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
getPrintboardLevel2TypeQw.lambda().orderByDesc(CollectionElement::getCreateDate);
getPrintboardLevel2TypeQw.last("limit 1");
CollectionElement one = collectionElementService.getOne(getPrintboardLevel2TypeQw);
print.setLevel2Type(one.getLevel2Type());
if (Objects.isNull(one)) {
QueryWrapper<Library> libraryQueryWrapper = new QueryWrapper<>();
libraryQueryWrapper.lambda().eq(Library::getUrl, print.getPath());
libraryQueryWrapper.lambda().orderByDesc(Library::getCreateDate);
getPrintboardLevel2TypeQw.last("limit 1");
Library library = libraryService.getOne(libraryQueryWrapper);
if (Objects.isNull(library)) {
print.setLevel2Type("Pattern");
} else {
print.setLevel2Type(library.getLevel2Type());
}
} else {
print.setLevel2Type(one.getLevel2Type());
}
print.setCreateDate(LocalDateTime.now());
designItemDetailPrintService.save(print);
}
@@ -853,7 +867,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
Map<String, Integer> typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0],
d -> Math.abs(d.getPriority()),
(existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
// Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
for (DesignPythonItem detail : item.getItems()) {
if (null == detail) {
continue;
@@ -864,9 +878,9 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
designItemDetail.setDesignItemId(designItemId);
designItemDetail.setCollectionElementId(detail.getElementId());
designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone));
if (!detail.getType().equals("Body")) {
/*if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType()));
}
}*/
if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) {
designItemDetail.setPath(detail.getBody_path());
@@ -1140,12 +1154,12 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
userPreference.setPath(designItemDetail.getPath());
SysFile sysFile = sysFileService.getOne(new LambdaQueryWrapper<SysFile>().eq(SysFile::getUrl, designItemDetail.getPath()));
userPreference.setAccountId(userHolder.getId());
if (sysFile != null){
userPreference.setCategory(sysFile.getLevel3Type().toLowerCase()+"_"+sysFile.getLevel2Type().toLowerCase());
if (sysFile != null) {
userPreference.setCategory(sysFile.getLevel3Type().toLowerCase() + "_" + sysFile.getLevel2Type().toLowerCase());
userPreference.setStyle(sysFile.getStyle());
}else {
log.error("sysFile not found:{}",designItemDetail.getPath());
SendEmailUtil.commonExceptionReminder("url在sysFile里找不到"+designItemDetail.getPath(), new String[]{"litianxiangxtt@163.com"});
} else {
log.error("sysFile not found:{}", designItemDetail.getPath());
SendEmailUtil.commonExceptionReminder("url在sysFile里找不到" + designItemDetail.getPath(), new String[]{"litianxiangxtt@163.com"});
continue;
}
@@ -1154,7 +1168,12 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
.toLocalDateTime());
userPreference.setDesignItemId(designItem.getId());
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);
}
}
@@ -1326,12 +1345,13 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
d.setScope(o.getPath().startsWith("aida-sys-image") ? "sys" : "user");
d.setLevel1Type(converTypeToLevel1(o.getType()));
d.setGradient(JSONObject.parseObject(o.getGradientString(), Gradient.class));
/*// 取消存储/返回UndividedLayer和UndividedLayerWithSinglePrint字段
if (!StringUtil.isNullOrEmpty(o.getUndividedLayer())) {
d.setUndividedLayer(minioUtil.getPreSignedUrl(o.getUndividedLayer(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
}
if (!StringUtil.isNullOrEmpty(o.getUndividedLayerWithSinglePrint())) {
d.setUndividedLayerWithSinglePrint(minioUtil.getPreSignedUrl(o.getUndividedLayerWithSinglePrint(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
}
}*/
// 根据designItemDetailId获取印花
List<DesignItemDetailPrint> prints = designItemDetailPrintService.getByDesignItemDetailId(o.getId(), "print");
prints.removeIf(print -> !minioUtil.doesObjectExist(print.getPath()));
@@ -1663,6 +1683,24 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
.lt("create_date", endTime)
.select("count(id) as count");
// 如果startTime早于2024-05-01 00:00:00添加额外的筛选条件
LocalDateTime thresholdTime = LocalDateTime.of(2024, 5, 1, 0, 0, 0);
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime startDateTime = LocalDateTime.parse(startTime, formatter);
if (startDateTime.isBefore(thresholdTime)) {
// 使用 notLike 来排除以 ":01" 或 ":02" 结尾的时间
// 方案2.1:使用 apply 方法执行数据库函数 create_date 字段的实际存储格式(可能包含毫秒),所以取最后三个字符进行匹配)
queryWrapper.apply("DATE_FORMAT(create_date, '%s') NOT IN ('01', '02')");
/*queryWrapper.notLike("create_date", "%:01")
.notLike("create_date", "%:02");*/
}
} catch (Exception e) {
log.warn("Failed to parse startTime: {}, skip time-based filtering", startTime, e);
}
if (!Objects.isNull(accountIds) && !accountIds.isEmpty()) {
queryWrapper.in("account_id", accountIds);
}
@@ -1670,7 +1708,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
List<Map<String, Object>> result = baseMapper.selectMaps(queryWrapper);
if (result != null && !result.isEmpty()) {
Object countObj = result.get(0).get("count");
return (Long) countObj;
return countObj != null ? ((Number) countObj).longValue() : 0L;
} else {
return 0L;
}
@@ -2539,7 +2577,7 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
Map<String, Integer> typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0],
d -> Math.abs(d.getPriority()),
(existing, replacement) -> replacement));
Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
// Map<String, String> typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers);
for (DesignPythonItem detail : item.getItems()) {
if (null == detail) {
continue;
@@ -2550,9 +2588,9 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
designItemDetail.setDesignItemId(designItemId);
designItemDetail.setCollectionElementId(detail.getElementId());
designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone));
if (!detail.getType().equals("Body")) {
/*if (!detail.getType().equals("Body")) {
designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType()));
}
}*/
if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) {
designItemDetail.setPath(detail.getBody_path());
//BODY不关联businessId

View File

@@ -48,6 +48,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.io.IOUtils;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -59,10 +61,12 @@ import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import jakarta.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
@@ -194,10 +198,13 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
generate.setText(text);
Long elementId = generateThroughImageTextDTO.getCollectionElementId();
// validateGeneraType(generate, text, elementId);
if (!StringUtil.isNullOrEmpty(text)) {
text = modifyPrompt(text, generate, generateThroughImageTextDTO.getLevel1Type(), generateThroughImageTextDTO.getAgeGroup());
if (!(generateThroughImageTextDTO.getLevel1Type().equals(MOOD_BOARD.getRealName())&&generateThroughImageTextDTO.getModelName().equals("high"))){
if (!StringUtil.isNullOrEmpty(text)) {
text = modifyPrompt(text, generate, generateThroughImageTextDTO.getLevel1Type(), generateThroughImageTextDTO.getAgeGroup());
}
}
// todo 这一步现在还是有必要的吗?
// 2.1 sketch或print在t_collection_element表/t_library表中的信息是否需要更新 如 level2Type
CollectionElement collectionElement = collectionElementService.editLevel2Type(elementId, generateThroughImageTextDTO.getLevel2Type(), generateThroughImageTextDTO.getDesignType());
@@ -218,6 +225,8 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
version = "fast";
params.put("version", "fast");
}
// 4、将请求信息落库,将本次generate的请求信息添加到t_generate表中
saveGenerateImmediately(generate);
// 3.1 确定不同类型的印花分别调哪个接口
if (generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())) {
switch (generateThroughImageTextDTO.getLevel2Type()) {
@@ -243,15 +252,28 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue);
}
} else {
GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(),
mode, category, generateThroughImageTextDTO.getGender(), version);
jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue);
if (Objects.equals(version, "fast")) {
GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(),
mode, category, generateThroughImageTextDTO.getGender(), version);
jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue);
} else {
path = CommonConstant.GENERATE_PATH_FLUX2_KLEIN;
// 构建object_name: {userId}/{category}/{uuid}.png
String objectName = generateThroughImageTextDTO.getUserId() + "/" + category + "/" + UUID.randomUUID() + ".png";
ImageProcessRequest imageProcessRequest = ImageProcessRequest.builder()
.object_name(objectName)
.bucket_name(userBucket)
.prompt(text).build();
jsonString = JSON.toJSONString(imageProcessRequest);
}
}
Boolean requestResult = pythonService.generateSketchOrPrint(jsonString, port, path);
Boolean requestResult = pythonService.generateSketchOrPrint(jsonString, port, path, generateThroughImageTextDTO.getUniqueId());
// 4、将请求信息落库,将本次generate的请求信息添加到t_generate表中
save(generate);
// 5、将本次请求存入redis
String key = generateResultKey + ":" + generateThroughImageTextDTO.getUniqueId();
@@ -266,6 +288,40 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
public void saveGenerateImmediately(Generate generate) {
save(generate);
// 使用 TransactionSynchronizationManager 在事务真正提交后再设锁
// 否则 save() 完成后事务尚未 commitMQ 消费者立即读到 null
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
String lockKey = "generate:lock:" + generate.getUniqueId();
redisUtil.addToString(lockKey, "1", 60L);
log.debug("Save lock set after commit for uniqueId: {}", generate.getUniqueId());
}
});
}
private void waitForSaveLock(String uniqueId) {
String lockKey = "generate:lock:" + uniqueId;
int maxRetries = 30;
int retryIntervalMs = 200;
for (int i = 0; i < maxRetries; i++) {
if (Boolean.TRUE.equals(redisUtil.hasKey(lockKey))) {
log.debug("Save lock acquired for uniqueId: {} after {} retries", uniqueId, i);
return;
}
try {
Thread.sleep(retryIntervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Interrupted while waiting for save lock: {}", uniqueId);
return;
}
}
log.warn("Save lock timeout for uniqueId: {}, proceeding anyway", uniqueId);
}
public GenerateModeEnum getMode(GenerateThroughImageTextDTO generateThroughImageTextDTO) {
if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getText())) {
if (Objects.nonNull(generateThroughImageTextDTO.getCollectionElementId())) {
@@ -284,11 +340,16 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
@Override
@Transactional(rollbackFor = Exception.class)
public void processGenerateResult(String taskId, String url, String category) {
log.info("============ProcessGenerateResult listening==========");
log.debug("taskId: " + taskId);
String status = null;
// 1、处理模型返回的数据
GenerateDetail generateDetail = new GenerateDetail();
GenerateCollectionItemVO generateCollectionItemVO = new GenerateCollectionItemVO();
Generate generate;
try {
// 等待 HTTP 线程写入完成后再查库
waitForSaveLock(taskId);
generate = selectByUniqueId(taskId);
} catch (MybatisPlusException e) {
log.error(e.getMessage());
@@ -311,14 +372,15 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
generateDetail.setUrl(url);
generateDetail.setGenerateId(generate.getId());
generateDetail.setCreateDate(LocalDateTime.now());
generateDetail.setMd5(md5);
generateDetail.setMd5("");
// 将相应的url保存到数据库
generateDetailMapper.insert(generateDetail);
log.debug("generateDetail: " + generateDetail.toString());
// String uuid = taskId.substring(0, taskId.substring(0, taskId.lastIndexOf("-")).lastIndexOf("-"));
String key = generateResultKey + ":" + taskId;
String imageName = url.substring(url.lastIndexOf("/") + 1);
String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success";
status = imageName.equals("white_image.jpg") ? "Invalid" : "Success";
if (StringUtil.isNullOrEmpty(category)) {
Generate generateRecord = selectByUniqueId(taskId);
category = generateRecord.getLevel2Type();
@@ -326,6 +388,8 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), url, status, category);
// 更新redis
redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
log.debug("generateResultVO: " + generateResultVO.toString());
// 执行积分扣除
// ** 注:如果生成的图片都是空白 则不扣积分
@@ -785,8 +849,9 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
long requestEndTime = System.currentTimeMillis();
log.info("HTTP请求完成 - 响应状态: {}, 耗时: {}ms, taskId: {}",
response.code(), (requestEndTime - requestStartTime), taskId);
String result = response.body().string();
if (!response.isSuccessful()) {
log.warn("Google API响应失败状态码: {} for taskId: {}", response.code(), taskId);
log.warn("Google API响应失败状态码: {} for taskId: {},结果:{}", response.code(), taskId, result);
if (attempt < maxRetries) {
Thread.sleep(retryDelay * attempt); // 递增延迟
continue;
@@ -795,7 +860,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
}
String result = response.body().string();
// log.info("Google 响应结果:{}", result);
com.alibaba.fastjson.JSONObject jsonResponse = JSON.parseObject(result);
@@ -1065,6 +1130,12 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
String result = response.body().string();
if (response.code() != 200) {
log.error("Google API 请求失败 - taskId: {}, 尝试: {}, URL: {}, 状态码: {}, 响应结果: {}",
taskId, attempt, endpoint, response.code(), result);
throw new BusinessException("system.error");
}
// log.info("Google 响应结果:{}", result);
com.alibaba.fastjson.JSONObject jsonResponse = JSON.parseObject(result);
@@ -1203,7 +1274,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
* @param modelName advanced high normal
*/
private HashMap<String, String> chooseModelAndPrompt(GenerateThroughImageTextDTO generateDTO, String modelName) {
if (StringUtil.isNullOrEmpty(modelName)){
if (StringUtil.isNullOrEmpty(modelName)) {
throw new BusinessException("system error");
}
HashMap<String, String> modelAndPromptMap = new HashMap<>();
@@ -1221,7 +1292,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
String style = generateDTO.getText().substring(0, firstCommaIndex).trim();
String prompt = generateDTO.getText().substring(firstCommaIndex + 1).trim();
prompt = getPrintboardPrompt(style, prompt,modelName);
prompt = getPrintboardPrompt(style, prompt, modelName, isUseImage);
modelAndPromptMap.put(ModelConstants.PROMPT, prompt);
@@ -1260,8 +1331,31 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.LOCAL_MODEL);
}
} else if (ModelConstants.SKETCHBOARD.equals(generateDTO.getLevel1Type())) {
String prompt = generateDTO.getText() + "rules:front view sketch only,plain white background, single garment only, orthographic, centered on white background, borderless canvas, thin monochrome black line art.\n" +
String style = "";
String userPrompt = "";
// 找到第一个逗号的位置
int firstCommaIndex = generateDTO.getText().indexOf(",");
if (firstCommaIndex != -1) {
// 截取第一个逗号前的内容作为style
style = generateDTO.getText().substring(0, firstCommaIndex).trim();
// 截取第一个逗号后的所有内容作为userPrompt去除首尾空格
userPrompt = generateDTO.getText().substring(firstCommaIndex + 1).trim();
if ("Lolita".equals(style)) {
style = "洛丽塔";
}
} else {
// 兼容无逗号的情况style为空全部内容作为userPrompt
userPrompt = generateDTO.getText().trim();
}
String prompt = userPrompt + "rules:front view sketch only,plain white background, single garment only, orthographic, centered on white background, borderless canvas, thin monochrome black line art.\n" +
" No clothes hanger, no fake clothes hanger, no human-related lines, no color fill, no words, no text, no black background, no boundary or frame.";
if (!style.trim().isEmpty() && !"all".equalsIgnoreCase(style)) {
prompt += ".sketch style:" + style.trim();
}
modelAndPromptMap.put(ModelConstants.PROMPT, prompt);
if (isUseImage) {
if (ModelConstants.ADVANCED.equals(modelName)) {
@@ -1459,6 +1553,13 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
if (imagePath != null) {
requestBuilder.image(finalImagePath1);
}
if (useModel.equals(ModelConstants.PRINTBOARD_HIGH_I2I)|| useModel.equals(ModelConstants.PRINTBOARD_HIGH_T2I)) {
GenerateImagesRequest.OptimizePromptOptions optimizePromptOptions = new GenerateImagesRequest.OptimizePromptOptions();
optimizePromptOptions.setMode("fast");
requestBuilder.optimizePromptOptions(optimizePromptOptions);
//由于PRINTBOARD_HIGH_T2I,PRINTBOARD_HIGH_I2I与PRINTBOARD_ADVANCED_I2I使用模型一致为了区别积分扣除PRINTBOARD_HIGH_I2I加入了-fast或者-high但传入模型时需要去掉-fast或者-high用PRINTBOARD_ADVANCED_I2I的常量做替代
requestBuilder.model(ModelConstants.PRINTBOARD_ADVANCED_I2I);
}
// 保存生成记录到数据库
Generate generate = new Generate(
@@ -1570,7 +1671,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
private String getPrintboardPrompt(String style, String userInput, String modelName) {
private String getPrintboardPrompt(String style, String userInput, String modelName, boolean isUseImage) {
String systemPrompt = null;
String prompt;
@@ -1596,12 +1697,16 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
"Flat textile pattern printed directly on fabric surface, no three-dimensional objects, no items placed on cloth. \n" +
"Real style: fabric print, realistic woven/printed pattern, detailed surface pattern only";
}
}else {
throw new BusinessException("style error:"+ style);
} else {
throw new BusinessException("style error:" + style);
}
if (userInput == null || userInput.trim().isEmpty()) {
throw new BusinessException("prompt null");
if (isUseImage) {
prompt = "Theme: Image content" + "\nRequirement: " + systemPrompt;
} else {
throw new BusinessException("prompt null");
}
} else {
prompt = "Theme: " + userInput.trim() + "\nRequirement: " + systemPrompt;
}
@@ -2001,7 +2106,9 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
public Generate selectByUniqueId(String uniqueId) {
QueryWrapper<Generate> qw = new QueryWrapper<>();
qw.eq("unique_id", uniqueId);
log.debug("selectByUniqueId: " + uniqueId);
Generate one = getOne(qw);
log.debug("Generate: " + one);
return getOne(qw);
}
@@ -4118,8 +4225,11 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
// 发送POST请求到Flux API
long start = System.currentTimeMillis();
String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString());
JSONObject respObj = JSONUtil.parseObj(resp);
long end = System.currentTimeMillis();
log.info("flux 耗时:{}ms", end - start);
log.info("flux 发起生成请求返回结果: {}", respObj);
// 从响应中提取任务ID
@@ -4172,11 +4282,11 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
// 处理不同状态
switch (statusEnum) {
case TASK_NOT_FOUND:
// 审核没过
// 审核没过
case REQUEST_MODERATED:
// 审核没过
// 审核没过
case CONTENT_MODERATED:
// 出错
// 出错
case ERROR:
return "Fail";
case PENDING_F:
@@ -4295,7 +4405,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
MotionModeEnum motionModeEnum = MotionModeEnum.of(poseTransformDTO.getMode());
switch (motionModeEnum) {
case POSE_TO_VIDEO:
params.put("pose_id", poseTransformDTO.getPoseId());
params.put("pose_id", poseTransformDTO.getPoseId().toString());
params.put("image_url", poseTransformDTO.getProductImage());
break;
case PROMPT_TO_VIDEO:

View File

@@ -0,0 +1,624 @@
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.model.vo.ContestantCountVO;
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 org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@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.temp.dir}")
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
@Transactional(rollbackFor = Exception.class)
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_number 在并发下自增分配
final int maxAttempts = 5;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
// 获取当前最大 contestant_number 并加行锁LIMIT 1 FOR UPDATE
QueryWrapper<Contestant> qMax = new QueryWrapper<>();
qMax.isNotNull("contestant_number");
qMax.orderByDesc("contestant_number");
qMax.last("LIMIT 1 FOR UPDATE");
Contestant last = contestantMapper.selectOne(qMax);
Integer nextNumber = (last == null || last.getContestantNumber() == null) ? 10000 : last.getContestantNumber() + 1;
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())
.videoDuration(request.getVideoDuration())
.videoSize(request.getVideoSize())
.pdfSize(request.getPdfSize())
.contestantNumber(nextNumber)
.portfolioUrl(request.getPortfolioUrl())
.createdAt(now)
.updatedAt(now)
.build();
contestantMapper.insert(toInsert);
resp.put("success", true);
sendSiteMsg(toInsert.getId(), toInsert.getEmail());
return resp;
} catch (Exception e) {
log.warn("Attempt {} to assign contestant_number failed", attempt, e);
String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
if ((msg.contains("duplicate") || msg.contains("uniq_contestant_number") || msg.contains("contestant_number")) && attempt < maxAttempts) {
try {
Thread.sleep(100L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
continue;
}
throw e;
}
}
throw new BusinessException("Failed to assign contestant number after retries.");
} 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.setVideoDuration(request.getVideoDuration());
existing.setVideoSize(request.getVideoSize());
existing.setPdfSize(request.getPdfSize());
existing.setPortfolioUrl(request.getPortfolioUrl());
existing.setUpdatedAt(now);
contestantMapper.updateById(existing);
resp.put("success", true);
return resp;
}
}
@Override
public byte[] exportContestants() throws Exception {
List<Contestant> list = contestantMapper.selectList(new QueryWrapper<>());
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("contestants");
int rowIdx = 0;
Row header = sheet.createRow(rowIdx++);
String[] headers = new String[] {
"contestantNumber", "email", "firstName", "lastName", "gender", "occupation",
"age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription",
"pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "portfolioUrl", "createdAt", "updatedAt"
};
for (int i = 0; i < headers.length; i++) {
Cell c = header.createCell(i);
c.setCellValue(headers[i]);
}
for (Contestant cst : list) {
Row r = sheet.createRow(rowIdx++);
int ci = 0;
r.createCell(ci++).setCellValue(cst.getContestantNumber() == null ? "" : cst.getContestantNumber().toString());
r.createCell(ci++).setCellValue(cst.getEmail() == null ? "" : cst.getEmail());
r.createCell(ci++).setCellValue(cst.getFirstName() == null ? "" : cst.getFirstName());
r.createCell(ci++).setCellValue(cst.getLastName() == null ? "" : cst.getLastName());
r.createCell(ci++).setCellValue(cst.getGender() == null ? "" : cst.getGender());
r.createCell(ci++).setCellValue(cst.getOccupation() == null ? "" : cst.getOccupation());
r.createCell(ci++).setCellValue(cst.getAge() == null ? "" : cst.getAge().toString());
r.createCell(ci++).setCellValue(cst.getCountryRegionCity() == null ? "" : cst.getCountryRegionCity());
r.createCell(ci++).setCellValue(cst.getPhoneNumber() == null ? "" : cst.getPhoneNumber());
r.createCell(ci++).setCellValue(cst.getDesignTitle() == null ? "" : cst.getDesignTitle());
r.createCell(ci++).setCellValue(cst.getDesignDescription() == null ? "" : cst.getDesignDescription());
r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath());
r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath());
r.createCell(ci++).setCellValue(cst.getVideoDuration() == null ? "" : cst.getVideoDuration().toString());
if (cst.getVideoSize() == null) {
r.createCell(ci++).setCellValue("");
} else {
double vMb = cst.getVideoSize() / 1024.0 / 1024.0;
r.createCell(ci++).setCellValue(String.format("%.2f", vMb));
}
if (cst.getPdfSize() == null) {
r.createCell(ci++).setCellValue("");
} else {
double pMb = cst.getPdfSize() / 1024.0 / 1024.0;
r.createCell(ci++).setCellValue(String.format("%.2f", pMb));
}
r.createCell(ci++).setCellValue(cst.getPortfolioUrl() == null ? "" : cst.getPortfolioUrl());
r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString());
r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString());
}
workbook.write(out);
out.flush();
return out.toByteArray();
} catch (IOException e) {
log.error("export contestants failed", e);
throw e;
}
}
@Override
public void saveContestantsToLocal() throws Exception {
List<Contestant> list = contestantMapper.selectList(new QueryWrapper<>());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
String ts = LocalDateTime.now().format(fmt);
Path exportDir = Paths.get(uploadDir == null ? "uploads" : uploadDir, "exports");
Files.createDirectories(exportDir);
Path outPath = exportDir.resolve("contestants_" + ts + ".xlsx");
try (XSSFWorkbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream(outPath.toFile())) {
Sheet sheet = workbook.createSheet("contestants");
int rowIdx = 0;
Row header = sheet.createRow(rowIdx++);
String[] headers = new String[] {
"contestantNumber", "email", "firstName", "lastName", "gender", "occupation",
"age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription",
"pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "portfolioUrl", "createdAt", "updatedAt"
};
for (int i = 0; i < headers.length; i++) {
Cell c = header.createCell(i);
c.setCellValue(headers[i]);
}
for (Contestant cst : list) {
Row r = sheet.createRow(rowIdx++);
int ci = 0;
r.createCell(ci++).setCellValue(cst.getContestantNumber() == null ? "" : cst.getContestantNumber().toString());
r.createCell(ci++).setCellValue(cst.getEmail() == null ? "" : cst.getEmail());
r.createCell(ci++).setCellValue(cst.getFirstName() == null ? "" : cst.getFirstName());
r.createCell(ci++).setCellValue(cst.getLastName() == null ? "" : cst.getLastName());
r.createCell(ci++).setCellValue(cst.getGender() == null ? "" : cst.getGender());
r.createCell(ci++).setCellValue(cst.getOccupation() == null ? "" : cst.getOccupation());
r.createCell(ci++).setCellValue(cst.getAge() == null ? "" : cst.getAge().toString());
r.createCell(ci++).setCellValue(cst.getCountryRegionCity() == null ? "" : cst.getCountryRegionCity());
r.createCell(ci++).setCellValue(cst.getPhoneNumber() == null ? "" : cst.getPhoneNumber());
r.createCell(ci++).setCellValue(cst.getDesignTitle() == null ? "" : cst.getDesignTitle());
r.createCell(ci++).setCellValue(cst.getDesignDescription() == null ? "" : cst.getDesignDescription());
r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath());
r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath());
r.createCell(ci++).setCellValue(cst.getVideoDuration() == null ? "" : cst.getVideoDuration().toString());
if (cst.getVideoSize() == null) {
r.createCell(ci++).setCellValue("");
} else {
double vMb = cst.getVideoSize() / 1024.0 / 1024.0;
r.createCell(ci++).setCellValue(String.format("%.2f", vMb));
}
if (cst.getPdfSize() == null) {
r.createCell(ci++).setCellValue("");
} else {
double pMb = cst.getPdfSize() / 1024.0 / 1024.0;
r.createCell(ci++).setCellValue(String.format("%.2f", pMb));
}
r.createCell(ci++).setCellValue(cst.getPortfolioUrl() == null ? "" : cst.getPortfolioUrl());
r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString());
r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString());
}
workbook.write(fos);
fos.flush();
log.info("Exported contestants to local file: {}", outPath.toString());
} catch (IOException e) {
log.error("save contestants to local failed", e);
throw e;
}
}
@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());
dto.setVideoDuration(existing.getVideoDuration());
dto.setPdfSize(existing.getPdfSize());
dto.setVideoSize(existing.getVideoSize());
dto.setPortfolioUrl(existing.getPortfolioUrl());
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);
}
@Override
public byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception {
if (minContestantNumber == null || maxContestantNumber == null) {
throw new BusinessException("minContestantNumber and maxContestantNumber are required.");
}
if (minContestantNumber > maxContestantNumber) {
throw new BusinessException("minContestantNumber cannot be greater than maxContestantNumber.");
}
// 1. 根据 contestantNumber 范围查询参赛者
QueryWrapper<Contestant> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.ge(Contestant::getContestantNumber, minContestantNumber)
.le(Contestant::getContestantNumber, maxContestantNumber)
.orderByAsc(Contestant::getContestantNumber);
List<Contestant> contestants = contestantMapper.selectList(queryWrapper);
if (contestants.isEmpty()) {
log.info("No contestants found in range [{}, {}]", minContestantNumber, maxContestantNumber);
return new byte[0];
}
// 2. 在内存中构建 ZIP
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos)) {
for (Contestant contestant : contestants) {
Integer contestantNumber = contestant.getContestantNumber();
if (contestantNumber == null) {
log.warn("Contestant {} has no contestantNumber, skipping", contestant.getId());
continue;
}
String dirPrefix = contestantNumber + "/";
// 添加 PDF 文件
String pdfPath = contestant.getPdfPath();
if (StringUtils.isNotBlank(pdfPath)) {
addMinioFileToZip(zos, pdfPath, dirPrefix + "design.pdf");
}
// 添加视频文件
String videoPath = contestant.getVideoPath();
if (StringUtils.isNotBlank(videoPath)) {
String fileName = videoPath.contains("/") ?
videoPath.substring(videoPath.lastIndexOf("/") + 1) : "video.mp4";
addMinioFileToZip(zos, videoPath, dirPrefix + fileName);
}
// 添加参赛者信息 txt 文件
StringBuilder sb = new StringBuilder();
sb.append("=== Contestant Information ===\n\n");
sb.append("ID: ").append(nullSafe(contestant.getId())).append("\n");
sb.append("Email: ").append(nullSafe(contestant.getEmail())).append("\n");
sb.append("Contestant Number: ").append(contestantNumber).append("\n");
sb.append("First Name: ").append(nullSafe(contestant.getFirstName())).append("\n");
sb.append("Last Name: ").append(nullSafe(contestant.getLastName())).append("\n");
sb.append("Gender: ").append(nullSafe(contestant.getGender())).append("\n");
sb.append("Occupation: ").append(nullSafe(contestant.getOccupation())).append("\n");
sb.append("Age: ").append(contestant.getAge() != null ? contestant.getAge() : "N/A").append("\n");
sb.append("Country/Region/City: ").append(nullSafe(contestant.getCountryRegionCity())).append("\n");
sb.append("Phone Number: ").append(nullSafe(contestant.getPhoneNumber())).append("\n");
sb.append("Design Title: ").append(nullSafe(contestant.getDesignTitle())).append("\n");
sb.append("Design Description: ").append(nullSafe(contestant.getDesignDescription())).append("\n");
sb.append("PDF Path: ").append(nullSafe(pdfPath)).append("\n");
sb.append("PDF Size (bytes): ").append(contestant.getPdfSize() != null ? contestant.getPdfSize() : "N/A").append("\n");
sb.append("Video Path: ").append(nullSafe(videoPath)).append("\n");
sb.append("Video Duration (seconds): ").append(contestant.getVideoDuration() != null ? contestant.getVideoDuration() : "N/A").append("\n");
sb.append("Video Size (bytes): ").append(contestant.getVideoSize() != null ? contestant.getVideoSize() : "N/A").append("\n");
sb.append("Portfolio URL: ").append(nullSafe(contestant.getPortfolioUrl())).append("\n");
sb.append("Created At: ").append(contestant.getCreatedAt() != null ? contestant.getCreatedAt() : "N/A").append("\n");
sb.append("Updated At: ").append(contestant.getUpdatedAt() != null ? contestant.getUpdatedAt() : "N/A").append("\n");
ZipEntry infoEntry = new ZipEntry(dirPrefix + "contestant_info.txt");
zos.putNextEntry(infoEntry);
zos.write(sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
zos.closeEntry();
log.info("Added contestant {} info to zip", contestantNumber);
}
zos.finish();
log.info("ZIP built for {} contestants, size: {} bytes", contestants.size(), baos.size());
return baos.toByteArray();
}
}
/**
* 将 MinIO 文件流式写入 ZIP不落盘
* @param zos ZIP 输出流
* @param minioPath MinIO 路径(格式: bucketName/objectPath
* @param entryName ZIP 条目名称
*/
private void addMinioFileToZip(java.util.zip.ZipOutputStream zos, String minioPath, String entryName) {
if (StringUtils.isBlank(minioPath)) {
return;
}
int index = minioPath.indexOf("/");
if (index == -1) {
log.warn("Invalid MinIO path: {}", minioPath);
return;
}
String bucketName = minioPath.substring(0, index);
String objectName = minioPath.substring(index + 1);
try (InputStream in = minioUtil.download(bucketName, objectName)) {
ZipEntry entry = new ZipEntry(entryName);
zos.putNextEntry(entry);
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
zos.write(buffer, 0, bytesRead);
}
zos.closeEntry();
log.info("Added {} to zip ({} bytes)", entryName, entry.getSize());
} catch (Exception e) {
log.error("Failed to add {} to zip: {}", entryName, e.getMessage());
}
}
@Override
public ContestantCountVO getContestantCount() {
long count = contestantMapper.selectCount(null);
Integer maxContestantNumber = null;
QueryWrapper<Contestant> qMax = new QueryWrapper<>();
qMax.isNotNull("contestant_number");
qMax.orderByDesc("contestant_number");
qMax.last("LIMIT 1");
Contestant last = contestantMapper.selectOne(qMax);
if (last != null) {
maxContestantNumber = last.getContestantNumber();
}
return ContestantCountVO.builder()
.count(count)
.maxContestantNumber(maxContestantNumber)
.build();
}
private String nullSafe(String value) {
return value != null ? value : "N/A";
}
}

View File

@@ -7,7 +7,7 @@ import com.ai.da.common.enums.CollectionLevel1TypeEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.PageResponse;
import com.ai.da.common.response.Response;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.TokenGenerateUtils;
import com.ai.da.common.utils.MinioUtil;
import com.ai.da.mapper.primary.*;
import com.ai.da.mapper.primary.entity.*;
@@ -70,7 +70,7 @@ public class LLMServiceImpl implements LLMService {
@Resource
private WorkspaceRelStyleMapper workspaceRelStyleMapper;
@Resource
private JWTTokenHelper jwtTokenHelper;
private TokenGenerateUtils tokenGenerateUtils;
@Resource
private DesignService designService;
private final ExecutorService executor = Executors.newCachedThreadPool();
@@ -89,9 +89,9 @@ public class LLMServiceImpl implements LLMService {
executor.submit(() -> {
try {
boolean validate = jwtTokenHelper.validateToken(token); //
boolean validate = tokenGenerateUtils.validateToken(token); //
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(token);
AuthPrincipalVo principal = tokenGenerateUtils.parserToUser(token);
Long accountId = principal.getId();
// String url = "http://18.167.251.121:10002/chat-stream";
String url = "http://10.1.1.240:1013/chat-stream";
@@ -237,10 +237,10 @@ public class LLMServiceImpl implements LLMService {
executor.submit(() -> {
try {
boolean validate = jwtTokenHelper.validateToken(token);
boolean validate = tokenGenerateUtils.validateToken(token);
// boolean validate = true;
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(token);
AuthPrincipalVo principal = tokenGenerateUtils.parserToUser(token);
Long accountId = principal.getId();
// String url = "http://18.167.251.121:10002/api/chat_stream";
String url = "http://18.167.251.121:2011/api/chat_stream";

View File

@@ -28,6 +28,7 @@ import io.netty.util.internal.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -53,7 +54,11 @@ public class LibraryModelPointServiceImpl extends ServiceImpl<LibraryModelPointM
private final PythonTAllInfoService pythonTAllInfoService;
@Override
@Transactional(rollbackFor = Exception.class)
public LibraryModelPointVO saveOrEditTemplatePoint(LibraryModelPointDTO libraryModelPointDTO) {
// 参数校验
validateInputParams(libraryModelPointDTO);
LibraryModelPointVO libraryModelPointVO = CopyUtil.copyObject(libraryModelPointDTO, LibraryModelPointVO.class);
// 不管是保存还是另存为都需要传模特的libraryId
@@ -71,7 +76,8 @@ public class LibraryModelPointServiceImpl extends ServiceImpl<LibraryModelPointM
// 更新模特图片
if (flag) {
libModel.setUrl(url);
libModel.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false));
String preSignedUrl = minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME);
libModel.setMd5(MD5Utils.encryptFile(preSignedUrl, false));
List<Integer> imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(url);
libModel.setWidth(imagesWidthAndHeight.get(0));
libModel.setHigh(imagesWidthAndHeight.get(1));
@@ -104,25 +110,10 @@ public class LibraryModelPointServiceImpl extends ServiceImpl<LibraryModelPointM
} else {
// 不覆盖,即另存为
// 新增模特library信息
Library saveAsModel = new Library();
saveAsModel.setAccountId(libModel.getAccountId());
saveAsModel.setLevel1Type(libModel.getLevel1Type());
saveAsModel.setLevel2Type(libModel.getLevel2Type());
String ageGroup = StringUtil.isNullOrEmpty(libModel.getLevel3Type()) ? "Adult" : libModel.getLevel3Type();
saveAsModel.setLevel3Type(ageGroup);
saveAsModel.setName(DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD));
saveAsModel.setUrl(url);
saveAsModel.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false));
List<Integer> imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(url);
saveAsModel.setWidth(imagesWidthAndHeight.get(0));
saveAsModel.setHigh(imagesWidthAndHeight.get(1));
saveAsModel.setCreateDate(DateUtil.getByTimeZone(libraryModelPointDTO.getTimeZone()));
libraryService.save(saveAsModel);
// 更新新的模特在library中的id,用于后面新建模特点位信息用
libraryModelPointDTO.setLibraryId(saveAsModel.getId());
Library saveAsModel = createNewLibraryCopy(libModel, libraryModelPointDTO);
// 新增模特点位信息
libraryModelPointDTO.setLibraryId(saveAsModel.getId()); // 更新libraryId为新创建的模型ID
LibraryModelPoint libraryModelPoint = resolvePoint(libraryModelPointDTO);
libraryModelPoint.setModelType("Library");
libraryModelPoint.setCreateDate(DateUtil.getByTimeZone(libraryModelPointDTO.getTimeZone()));
@@ -130,22 +121,50 @@ public class LibraryModelPointServiceImpl extends ServiceImpl<LibraryModelPointM
libraryModelPointVO.setTemplateId(libraryModelPoint.getId());
libraryModelPointVO.setRelationId(libraryModelPoint.getRelationId());
}
//编辑
/*if (!StringUtils.isEmpty(libraryModelPointDTO.getModelSex())) {
Library byId = libraryService.getById(libraryModelPointDTO.getLibraryId());
if (!byId.getLevel2Type().equals(libraryModelPointDTO.getModelSex())) {
if (byId.getLevel2Type().equals(Sex.FEMALE.getValue())) {
libraryService.checkModel(Sex.FEMALE.getValue(), Collections.singletonList(byId.getId()), 1);
}else {
libraryService.checkModel(Sex.MALE.getValue(), Collections.singletonList(byId.getId()), 1);
}
byId.setLevel2Type(libraryModelPointDTO.getModelSex());
libraryService.updateById(byId);
}
}*/
return libraryModelPointVO;
}
/**
* 验证输入参数
*/
private void validateInputParams(LibraryModelPointDTO libraryModelPointDTO) {
if (libraryModelPointDTO == null) {
throw new BusinessException("libraryModelPointDTO cannot be null");
}
if (libraryModelPointDTO.getLibraryId() == null || libraryModelPointDTO.getLibraryId() <= 0) {
throw new BusinessException("libraryId is required");
}
if (StringUtils.isEmpty(libraryModelPointDTO.getModelPath())) {
throw new BusinessException("modelPath is required");
}
if (StringUtils.isEmpty(libraryModelPointDTO.getTimeZone())) {
throw new BusinessException("timeZone is required");
}
}
/**
* 创建新的库模型副本
*/
private Library createNewLibraryCopy(Library originalModel, LibraryModelPointDTO libraryModelPointDTO) {
// 新增模特library信息
Library saveAsModel = new Library();
saveAsModel.setAccountId(originalModel.getAccountId());
saveAsModel.setLevel1Type(originalModel.getLevel1Type());
saveAsModel.setLevel2Type(originalModel.getLevel2Type());
String ageGroup = StringUtil.isNullOrEmpty(originalModel.getLevel3Type()) ? "Adult" : originalModel.getLevel3Type();
saveAsModel.setLevel3Type(ageGroup);
saveAsModel.setName(DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD));
saveAsModel.setUrl(libraryModelPointDTO.getModelPath());
String preSignedUrl = minioUtil.getPreSignedUrl(libraryModelPointDTO.getModelPath(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME);
saveAsModel.setMd5(MD5Utils.encryptFile(preSignedUrl, false));
List<Integer> imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(libraryModelPointDTO.getModelPath());
saveAsModel.setWidth(imagesWidthAndHeight.get(0));
saveAsModel.setHigh(imagesWidthAndHeight.get(1));
saveAsModel.setCreateDate(DateUtil.getByTimeZone(libraryModelPointDTO.getTimeZone()));
libraryService.save(saveAsModel);
return saveAsModel;
}
@Override
public LibraryModelPointVO saveOrEditTemplatePointOld(LibraryModelPointDTO libraryModelPointDTO) {
// Library library = libraryService.getById(libraryModelPointDTO.getLibraryId());

View File

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

View File

@@ -258,6 +258,11 @@ public class PanToneServiceImpl extends ServiceImpl<PanToneMapper, PanTone> impl
d.setH(getRgbByHsvBatchDTO.getH());
d.setS(getRgbByHsvBatchDTO.getS());
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(

View File

@@ -31,6 +31,11 @@ public class RabbitMQServiceImpl implements RabbitMQService {
mqPublisher.sendGenerateMessage(message);
}
@Override
public void publishMessageToGenerateResult(String message) {
mqPublisher.sendGenerateResultMessage(message);
}
@Override
public void publishMessageToSR(String message) {
mqPublisher.sendSRMessage(message);

View File

@@ -1060,11 +1060,12 @@ public class StripeServiceImpl implements StripeService {
String periodEnd = DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);
qwPI.lambda().eq(PaymentInfo::getOrderNo, subscriptionInfo.getOrderNo())
.eq(PaymentInfo::getTradeState, "paid")
.between(PaymentInfo::getCreateTime, periodStart, periodEnd)
.orderByDesc(PaymentInfo::getId);
List<PaymentInfo> paymentInfos = paymentInfoMapper.selectList(qwPI);
if (paymentInfos.isEmpty()) {
log.info("不发送邮件原因【根据order_no:{},查询到的paymentInfos为空】", orderNo);
log.info("不发送邮件原因【根据order_no:{},查询到的成功的paymentInfos为空】", orderNo);
return false;
}
PaymentInfo paymentInfo = paymentInfos.get(0);

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

@@ -1,161 +0,0 @@
server.port=5567
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.primary.jdbcUrl=jdbc:mysql://18.167.251.121:33008/aida?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.primary.jdbcUrl=jdbc:mysql://18.167.251.121:33008/aida_back?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
#spring.datasource.primary.jdbcUrl=jdbc:mysql://18.167.251.121:33008/test_aida_3.1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.primary.username=aida_con
spring.datasource.primary.password=123456
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.jdbcUrl=jdbc:mysql://18.167.251.121:33008/attribute_retrieval_style?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.secondary.username=aida_con
spring.datasource.secondary.password=123456
#security
spring.security.jwtSecret=JWTSECRET
spring.security.jwtTokenHeader=Authorization
spring.security.jwtTokenPrefix=Bearer-
## 24Сʱ
spring.security.jwtExpiration=8640000000
#spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\
/api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\
/api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**,/api/credits/**,/api/inquiry/**,/api/tasks/**,/api/python/prepareForSR,/api/alipay-hk/**,/api/portfolio/**,\
/api/stripe/**,/api/message/**,/api/tags/**,/notification/**,/api/affiliate/**,/api/project/**,/api/llm/**, /api/subscription_plan/**
spring.security.authApi=/auth/login
rsa.private_key=MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
#mybatis
mybatis-plus.global-config.banner=false
mybatis-plus.mapper-locations=classpath:mapper/*/*.xml
mybatis-plus.global-config.db-config.logic-delete-field=isDeleted
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
file.mac.path=~/file/
file.linux.path=/workspace/home/aida/file/
#linux服务器域名(预览和下载用)
file.linuxDomain=https://www.aida.com.hk/download/
file.windows.path=D:\\upload\\
spring.servlet.multipart.max-file-size = 10MB
spring.servlet.multipart.max-request-size= 10MB
#访问python服务的ip(对应环境)
access.python.ip=http://18.167.251.121
access.python.port=9994
access.python.generate_sr_port=9994
access.python.address=http://18.167.251.121:9994
minio.endpoint=https://www.minio-api.aida.com.hk
minio.accessKey=admin
minio.secretKey=Aidlab123123!
minio.bucketName.clothing=aida-clothing
minio.bucketName.mannequins=aida-mannequins
minio.bucketName.results=aida-results
minio.bucketName.sysImage=aida-sys-image
minio.bucketName.users=aida-users
minio.bucketName.collectionElement=aida-collection-element
minio.bucketName.gradient=aida-gradient
minio.bucketName.modifiedSketch=aida-modified-sketch
minio.bucketName.slogan=aida-slogan
minio.bucketName.partialDesign=aida-partial-design
redirect_url=http://18.167.251.121:7788
spring.rabbitmq.host=18.167.251.121
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbit
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/
spring.data.redis.host=172.31.11.32
#spring.data.redis.host=18.167.251.121
spring.data.redis.port=6379
spring.data.redis.database=1
spring.data.redis.password=Aidlab
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.max-wait=5
redis.key.orderForGenerate=OrderForGenerate
redis.key.generateCancelSet=GenerateCancelSet
redis.key.generateExceptionMap=Generate:Exception
redis.key.resultMap=ResultMap
redis.key.orderForSR=OrderForSR
redis.key.SRCancelSet=SRCancelSet
redis.key.SRExceptionMap=SRExceptionMap
redis.key.taskList=TaskList
redis.key.credits.pre-deduction=Credits:PreDeduction
redis.key.generateResult=Generate:Result
redis.key.toProductImageResultKey=ToProductImage:Result
redis.key.relightResultKey=Relight:Result
redis.key.newPosted=LastViewNewPostedTime
redis.key.maximumUserId=CodeCreate:MaximumUserId
aws.s3.accessKeyId=AKIAVD3OJIMF6UJFLSHZ
aws.s3.secretKey=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
aws.s3.regionName=ap-east-1
# RabbitMQ Exchange and Queue configurations
rabbitmq.queues.generate=generate-queue-dev
rabbitmq.queues.sr=SR-queue-dev
rabbitmq.queues.srResult=SuperResolution-dev
rabbitmq.queues.generateResult=GenerateImage-dev
rabbitmq.queues.toProductImageResult=ToProductImage-dev
rabbitmq.queues.relightResult=Relight-dev
rabbitmq.queues.poseTransform=PoseTransform-dev
rabbitmq.exchange.generate=generate-exchange
rabbitmq.queues.designBatch=DesignBatch-dev
rabbitmq.queues.relightBatch=BatchRelight-dev
rabbitmq.queues.toProductImageBatch=BatchToProductImage-dev
rabbitmq.queues.poseTransformBatch=BatchPoseTransform-dev
rabbitmq.queues.emailRetry=emailRetry-business
# 死信队列配置
rabbitmq.dead-letter.exchange=dlx.email-retry
rabbitmq.dead-letter.queue=dlx.email-retry.queue
rabbitmq.dead-letter.routing-key=dlx.email-retry.key
orderList.link=https://develop.aida.com.hk/home/homePage?order=
# 0 不发送邮件通知 1 发送邮件通知
stripe.webhook.fail.reminder=0
# kim test
stripe.paymentMethodConfiguration=pmc_1LywTWH7nPZ8bkrN6FvdCUWG
# developer test
#stripe.paymentMethodConfiguration=pmc_1QIKyq02n1TEydyNKVEYvhW7
#thymelea模板配置
#控制 Thymeleaf 是否启用模板缓存 生产环境用true,以提高性能
spring.thymeleaf.cache=false
#指定邮件服务器的地址。
spring.mail.host=mail.aida.com.hk
#指定邮件服务器的端口号。
spring.mail.port=465
#指定登录邮件服务器的用户名
spring.mail.username=info@aida.com.hk
#指定登录邮件服务器的密码 / 授权码
spring.mail.password=AIdlab@2025
spring.mail.default-encoding=UTF-8
# SSL 配置
#启用 SSL 加密
spring.mail.properties.mail.smtp.ssl.enable=true
#指定 SSL 连接的端口号。通常与 spring.mail.port 一致
spring.mail.properties.mail.smtp.socketFactory.port=465
ALIYUN_API_KEY=sk-dc3f88b7df844fc5a7d3616ebd8a589c
DOUBAO_API_KEY=853b3c55-f1dd-406e-a356-64123637f635
FREEPIK_API_KEY=FPSX94e5917d376a4facb87dabbaa0319c72
ollama.url=http://localhost:11434/api/chat
#google.client.id=29310152396-c44dcsoksjirhn7vbo29p8u8n0sg4qps.apps.googleusercontent.com
google.client.id=157095842121-kdd1fdf8m8nudvj9sprstb2k2prnf9e4.apps.googleusercontent.com
#google.client.secret=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
google.client.secret=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
google.redirect.uri=https://develop.api.aida.com.hk/api/third/party/auth/google_callback
design.callback.url=https://develop.api.aida.com.hk/api/third/party/receiveDesignResults

View File

@@ -1,159 +0,0 @@
server.port=5567
#datasource
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.primary.jdbcUrl=jdbc:mysql://18.167.251.121:3306/aida?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.primary.username=root
spring.datasource.primary.password=QWa998345
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.jdbcUrl=jdbc:mysql://18.167.251.121:33008/attribute_retrieval_style?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.secondary.username=aida_con
spring.datasource.secondary.password=123456
#security
spring.security.jwtSecret=JWTSECRET
spring.security.jwtTokenHeader=Authorization
spring.security.jwtTokenPrefix=Bearer-
## 24Сʱ
#spring.security.jwtExpiration=8640000000
spring.security.jwtExpiration=604800000
#spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\
/api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\
/api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**,/api/credits/**,/api/inquiry/**,/api/tasks/**,/api/python/prepareForSR,/api/alipay-hk/**,/api/portfolio/**,\
/api/stripe/**,/api/message/**,/api/tags/**,/notification/**,/api/affiliate/**,/api/project/**,/api/llm/**, /api/subscription_plan/**
spring.security.authApi=/auth/login
rsa.private_key=MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
#mybatis
mybatis-plus.global-config.banner=false
mybatis-plus.mapper-locations=classpath:mapper/*/*.xml
mybatis-plus.global-config.db-config.logic-delete-field=isDeleted
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
file.mac.path=~/file/
file.linux.path=/workspace/home/aida/file/
#linux服务器域名(预览和下载用)
file.linuxDomain=https://www.aida.com.hk/download/
file.windows.path=D:\\upload\\
spring.servlet.multipart.max-file-size = 10MB
spring.servlet.multipart.max-request-size= 10MB
#访问python服务的ip(对应环境)
access.python.ip=http://18.167.251.121
access.python.port=9990
access.python.generate_sr_port=9990
access.python.address=http://18.167.251.121:9990
minio.endpoint=https://www.minio-api.aida.com.hk
minio.accessKey=admin
minio.secretKey=Aidlab123123!
minio.bucketName.clothing=aida-clothing
minio.bucketName.mannequins=aida-mannequins
minio.bucketName.results=aida-results
minio.bucketName.sysImage=aida-sys-image
minio.bucketName.users=aida-users
minio.bucketName.collectionElement=aida-collection-element
minio.bucketName.gradient=aida-gradient
minio.bucketName.modifiedSketch=aida-modified-sketch
minio.bucketName.slogan=aida-slogan
minio.bucketName.partialDesign=aida-partial-design
redirect_url=http://18.167.251.121:7788
spring.rabbitmq.host=18.167.251.121
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbit
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/
spring.data.redis.host=172.31.11.32
#spring.data.redis.host=18.167.251.121
spring.data.redis.port=6379
spring.data.redis.database=2
spring.data.redis.password=Aidlab
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.max-wait=5
redis.key.orderForGenerate=OrderForGenerate
redis.key.generateCancelSet=GenerateCancelSet
redis.key.generateExceptionMap=Generate:Exception
redis.key.resultMap=ResultMap
redis.key.orderForSR=OrderForSR
redis.key.SRCancelSet=SRCancelSet
redis.key.SRExceptionMap=SRExceptionMap
redis.key.taskList=TaskList
redis.key.credits.pre-deduction=Credits:PreDeduction
redis.key.generateResult=Generate:Result
redis.key.toProductImageResultKey=ToProductImage:Result
redis.key.relightResultKey=Relight:Result
redis.key.newPosted=LastViewNewPostedTime
redis.key.maximumUserId=CodeCreate:MaximumUserId
aws.s3.accessKeyId=AKIAVD3OJIMF6UJFLSHZ
aws.s3.secretKey=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
aws.s3.regionName=ap-east-1
# RabbitMQ Exchange and Queue configurations
rabbitmq.queues.generate=generate-queue-prod
rabbitmq.queues.sr=SR-queue-prod
rabbitmq.queues.srResult=SuperResolution-prod
rabbitmq.queues.generateResult=GenerateImage-prod
rabbitmq.queues.toProductImageResult=ToProductImage-prod
rabbitmq.queues.relightResult=Relight-prod
rabbitmq.queues.poseTransform=PoseTransform-prod
rabbitmq.exchange.generate=generate-exchange
rabbitmq.queues.designBatch=DesignBatch-dev
rabbitmq.queues.relightBatch=BatchRelight-dev
rabbitmq.queues.toProductImageBatch=BatchToProductImage-dev
rabbitmq.queues.poseTransformBatch=BatchPoseTransform-dev
rabbitmq.queues.emailRetry=emailRetry-business
# 死信队列配置
rabbitmq.dead-letter.exchange=dlx.email-retry
rabbitmq.dead-letter.queue=dlx.email-retry.queue
rabbitmq.dead-letter.routing-key=dlx.email-retry.key
orderList.link=https://aida.com.hk/home/homePage?order=
# 0 不发送邮件通知 1 发送邮件通知
stripe.webhook.fail.reminder=1
# kim live
stripe.paymentMethodConfiguration=pmc_1Qu6yJH7nPZ8bkrNULYnFFPf
# kim test
#stripe.paymentMethodConfiguration=pmc_1LywTWH7nPZ8bkrN6FvdCUWG
# developer test
#stripe.paymentMethodConfiguration=pmc_1QIKyq02n1TEydyNKVEYvhW7
#thymelea模板配置
#控制 Thymeleaf 是否启用模板缓存 生产环境用true,以提高性能
spring.thymeleaf.cache=false
#指定邮件服务器的地址。
spring.mail.host=mail.aida.com.hk
#指定邮件服务器的端口号。
spring.mail.port=465
#指定登录邮件服务器的用户名
spring.mail.username=info@aida.com.hk
#指定登录邮件服务器的密码 / 授权码
spring.mail.password=AIdlab@2025
spring.mail.default-encoding=UTF-8
# SSL 配置
#启用 SSL 加密
spring.mail.properties.mail.smtp.ssl.enable=true
#指定 SSL 连接的端口号。通常与 spring.mail.port 一致
spring.mail.properties.mail.smtp.socketFactory.port=465
ALIYUN_API_KEY=sk-dc3f88b7df844fc5a7d3616ebd8a589c
DOUBAO_API_KEY=853b3c55-f1dd-406e-a356-64123637f635
FREEPIK_API_KEY=FPSX94e5917d376a4facb87dabbaa0319c72
google.client.id=29310152396-nnsd3h533fld665oguu8ovrt1nukmt46.apps.googleusercontent.com
google.client.secret=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
google.redirect.uri=https://www.api.aida.com.hk/api/third/party/auth/google_callback
design.callback.url=https://api.aida.com.hk/api/third/party/receiveDesignResults

View File

@@ -1,101 +0,0 @@
server.port=5567
#datasource
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.primary.jdbcUrl=jdbc:mysql://18.167.251.121:3306/aida?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.primary.username=root
spring.datasource.primary.password=QWa998345
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.jdbcUrl=jdbc:mysql://18.167.251.121:33008/attribute_retrieval_new?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.datasource.secondary.username=aida_con
spring.datasource.secondary.password=123456
#security
spring.security.jwtSecret=JWTSECRET
spring.security.jwtTokenHeader=Authorization
spring.security.jwtTokenPrefix=Bearer-
## 24Сʱ
spring.security.jwtExpiration=8640000000
#spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource
spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\
/api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\
/api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**,/api/credits/**,/api/inquiry/**,/api/tasks/**,/api/python/prepareForSR
spring.security.authApi=/auth/login
rsa.private_key=MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
#mybatis
mybatis-plus.global-config.banner=false
mybatis-plus.mapper-locations=classpath:mapper/*/*.xml
mybatis-plus.global-config.db-config.logic-delete-field=isDeleted
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
file.mac.path=~/file/
file.linux.path=/workspace/home/aida/file/
#linux服务器域名(预览和下载用)
file.linuxDomain=https://www.aida.com.hk/download/
file.windows.path=D:\\upload\\
spring.servlet.multipart.max-file-size = 10MB
spring.servlet.multipart.max-request-size= 10MB
#访问python服务的ip(对应环境)
#access.python.ip=http://43.198.80.117
access.python.ip=http://18.167.251.121
#access.python.ip=http://18.167.251.121:9991/
access.python.port=9992
access.python.sr=http://18.167.251.121:9994
minio.endpoint=https://www.minio.aida.com.hk:12024
minio.accessKey=admin
minio.secretKey=Aidlab123123!
minio.bucketName.clothing=aida-clothing
minio.bucketName.mannequins=aida-mannequins
minio.bucketName.results=aida-results
minio.bucketName.sysImage=aida-sys-image
minio.bucketName.users=aida-users
minio.bucketName.collectionElement=aida-collection-element
redirect_url=http://18.167.251.121:7788
spring.rabbitmq.host=18.167.251.121
spring.rabbitmq.port=5672
spring.rabbitmq.username=rabbit
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/
spring.redis.host=172.31.11.32
#spring.redis.host=18.167.251.121
spring.redis.port=6379
spring.redis.database=1
spring.redis.password=Aidlab
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=5
redis.key.orderForGenerate=OrderForGenerate
redis.key.generateCancelSet=GenerateCancelSet
redis.key.generateExceptionMap=Generate:Exception
redis.key.resultMap=ResultMap
redis.key.orderForSR=OrderForSR
redis.key.SRCancelSet=SRCancelSet
redis.key.SRExceptionMap=SRExceptionMap
redis.key.taskList=TaskList
redis.key.credits.pre-deduction=Credits:PreDeduction
redis.key.generateResult=Generate:Result
aws.s3.accessKeyId=AKIAVD3OJIMF6UJFLSHZ
aws.s3.secretKey=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
aws.s3.regionName=ap-east-1
# RabbitMQ Exchange and Queue configurations
rabbitmq.queues.generate=generate-queue-test
rabbitmq.queues.sr=SR-queue-test
rabbitmq.queues.srResult=SuperResolution-test
rabbitmq.queues.generateResult=GenerateImage-test
rabbitmq.queues.toProductImageResult=ToProductImage-test
rabbitmq.queues.relightResult=Relight-test
rabbitmq.exchange.generate=generate-exchange

View File

@@ -1,8 +0,0 @@
#<23><><EFBFBD><EFBFBD>application-test<73>ļ<EFBFBD>(<28><><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>)
#spring.profiles.active=test
#<23><><EFBFBD><EFBFBD>application-prod<6F>ļ<EFBFBD>(<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
#spring.profiles.active=prod
#<23><><EFBFBD><EFBFBD>application-dev<65>ļ<EFBFBD>(<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
spring.profiles.active=dev

View File

@@ -0,0 +1,114 @@
# ============================================================
# aida-back - 本地配置(不区分环境)
# 公共配置DB、Redis、RabbitMQ、MinIO、API Keys 等)由 Nacos 统一管理
# 此文件仅包含 back 服务私有的业务配置
# ============================================================
server:
port: 10092
spring:
application:
name: aida-back
# ---------- MinIO Buckets ----------
minio:
bucketName:
clothing: aida-clothing
mannequins: aida-mannequins
results: aida-results
sysImage: aida-sys-image
users: aida-users
collectionElement: aida-collection-element
gradient: aida-gradient
modifiedSketch: aida-modified-sketch
slogan: aida-slogan
partialDesign: aida-partial-design
globalAward: global-award
# ---------- Redis Keys ----------
redis:
key:
orderForGenerate: OrderForGenerate
generateCancelSet: GenerateCancelSet
generateExceptionMap: Generate:Exception
resultMap: ResultMap
orderForSR: OrderForSR
SRCancelSet: SRCancelSet
SRExceptionMap: SRExceptionMap
taskList: TaskList
credits:
pre-deduction: Credits:PreDeduction
generateResult: Generate:Result
toProductImageResultKey: ToProductImage:Result
relightResultKey: Relight:Result
newPosted: LastViewNewPostedTime
maximumUserId: CodeCreate:MaximumUserId
# ---------- RabbitMQ 队列 ----------
rabbitmq:
queues:
generate: generate-queue
sr: SR-queue
srResult: SuperResolution
generateResult: GenerateImage
toProductImageResult: ToProductImage
relightResult: Relight
poseTransform: PoseTransform
designBatch: DesignBatch
relightBatch: BatchRelight
toProductImageBatch: BatchToProductImage
poseTransformBatch: BatchPoseTransform
emailRetry: emailRetry-business
exchange:
generate: generate-exchange
dead-letter:
exchange: dlx.email-retry
queue: dlx.email-retry.queue
routing-key: dlx.email-retry.key
# ---------- 第三方服务 ----------
orderList:
link: https://develop.aida.com.hk/home/homePage?order=
stripe:
webhook:
fail:
reminder: 0
paymentMethodConfiguration: pmc_1QIKyq02n1TEydyNKVEYvhW7
google:
client:
id: 157095842121-kdd1fdf8m8nudvj9sprstb2k2prnf9e4.apps.googleusercontent.com
secret: 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
redirect:
uri: https://develop.api.aida.com.hk/api/third/party/auth/google_callback
redirect:
url: http://18.167.251.121:7788
global:
award:
link: https://aida-global-design-awards.com.hk/contestants?id=
# ---------- 文件上传 ----------
file:
upload:
temp:
dir: temp/uploads
chunk:
size:
pdf: 1048576
video: 2097152
max:
size:
pdf: 20971520
video: 104857600
task:
expiry:
hours: 24
logging:
level:
com.aida: debug

Some files were not shown because too many files have changed in this diff Show More