267 Commits

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

View File

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

View File

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

@@ -3,6 +3,7 @@ package com.ai.da.common.task;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.service.AccountService;
import com.ai.da.service.SubscriptionPlanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -18,6 +19,8 @@ public class AccountTask {
private AccountService accountService;
@Resource
private RedisUtil redisUtil;
@Resource
private SubscriptionPlanService subscriptionPlanService;
/**
* 每周日晚上刷新 年付用户、月付用户的积分
@@ -25,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);
@@ -48,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);
@@ -74,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("重置所有用户的用户名修改次数");
@@ -82,8 +85,18 @@ 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 * * ?")
public void activeSubscriptionPlan() {
subscriptionPlanService.activeSubscriptionPlan(null);
}
// @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,28 @@ public class MinioUtil {
@Autowired
private MinioClient minioClient;
@Value("${minio.endpoint}")
private String endpoint;
/**
* 获取MinIO客户端实例
*/
public MinioClient getMinioClient() {
return minioClient;
}
@Autowired
private RedisUtil redisUtil;
/**
* Redis缓存key前缀用于Minio签名URL缓存
*/
private static final String REDIS_MINIO_URL_PREFIX = "minio:url:";
/**
* 签名URL缓存过期时间默认1天
*/
private static final long URL_CACHE_EXPIRE_SECONDS = 24 * 60 * 60;
/**
* description: 判断bucket是否存在不存在则创建
*
@@ -381,6 +404,11 @@ public class MinioUtil {
* @return 文件的临时URL如果出现异常则返回null
*/
public String getPreSignedUrl(String bucketName, String fileName, int expiry) {
String cacheKey = REDIS_MINIO_URL_PREFIX + bucketName + "/" + fileName;
Object cachedUrl = redisUtil.getFromString(cacheKey);
if (cachedUrl != null) {
return cachedUrl.toString();
}
try {
String lowerName = fileName.toLowerCase();
@@ -408,8 +436,9 @@ public class MinioUtil {
builder.extraQueryParams(queryParams);
}
return minioClient.getPresignedObjectUrl(builder.build());
String presignedObjectUrl = minioClient.getPresignedObjectUrl(builder.build());
redisUtil.addToString(cacheKey, presignedObjectUrl, URL_CACHE_EXPIRE_SECONDS);
return presignedObjectUrl;
} catch (MinioException | InvalidKeyException
| IOException | NoSuchAlgorithmException | IllegalArgumentException e) {
e.printStackTrace();
@@ -951,6 +980,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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -2,8 +2,10 @@ package com.ai.da.python;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.ai.da.common.RabbitMQ.RabbitMQProperties;
import com.ai.da.common.config.FileProperties;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.*;
import com.ai.da.common.utils.*;
@@ -20,6 +22,7 @@ import com.ai.da.model.vo.*;
import com.ai.da.python.vo.*;
import com.ai.da.service.DesignHistoryService;
import com.ai.da.service.PythonTAllInfoService;
import com.ai.da.service.RabbitMQService;
import com.ai.da.service.SysFileService;
import com.alibaba.fastjson.*;
import com.alibaba.fastjson.serializer.SerializerFeature;
@@ -39,6 +42,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -68,10 +72,12 @@ public class PythonService {
private String accessPythonPort;
@Value("${minio.bucketName.gradient}")
private String gradientBucketName;
@Value("${minio.bucketName.users}")
private String userBucketName;
@Value("${access.python.generate_sr_port}")
private String srServicePort;
@Value("${design.callback.url}")
@Value("${design.callback.url.aida}")
private String callbackUrl;
@Resource
@@ -83,6 +89,12 @@ public class PythonService {
@Resource
private RedisUtil redisUtil;
@Resource
private RabbitMQService rabbitMQService;
@Resource
private RabbitMQProperties rabbitMQProperties;
/**
* 生成打印的图片 二合一 (废弃于2024/01/02)
*
@@ -279,7 +291,7 @@ public class PythonService {
if (isDuplicate) {
elementVO.setHasUseMd5List(beforeAssemblyHasUseMd5List);
i --;
i--;
switch (designPrintPictureType) {
case PIN:
@@ -406,13 +418,14 @@ public class PythonService {
// if (CollectionUtil.isEmpty(pinData)) {
// return 0;
// }
//// long topNum = sketchBoardElements.stream()
//// .filter(skecth -> skecth.getHasPin() == 1
//// && DesignPythonItem.OUTWEAR_DRESS_BLOUSE.contains(skecth.getLevel2Type())).count();
//// long bottomNum = sketchBoardElements.stream()
//// .filter(skecth -> skecth.getHasPin() == 1
//// && DesignPythonItem.SKIRT_TROUSERS.contains(skecth.getLevel2Type())).count();
//// int num = Arrays.asList(topNum, bottomNum).stream().max(Comparator.comparing(Long::valueOf)).get().intValue();
/// / long topNum = sketchBoardElements.stream()
/// / .filter(skecth -> skecth.getHasPin() == 1
/// / && DesignPythonItem.OUTWEAR_DRESS_BLOUSE.contains(skecth.getLevel2Type())).count();
/// / long bottomNum = sketchBoardElements.stream()
/// / .filter(skecth -> skecth.getHasPin() == 1
/// / && DesignPythonItem.SKIRT_TROUSERS.contains(skecth.getLevel2Type())).count();
/// / int num = Arrays.asList(topNum, bottomNum).stream().max(Comparator.comparing(Long::valueOf)).get().intValue();
// int num = pinData.size();
// return Math.min(num, 8);
// }
@@ -560,12 +573,12 @@ public class PythonService {
return 0;
} else {
long pinNum = printBoardElements.stream().filter(f -> f.getHasPin() == 1).count();
if (designNum - pinNum < 0){
return RandomsUtil.randomSysFile(0L, (long) (pinNum/2 + 1));
} else if (designNum - pinNum < designNum/2) {
if (designNum - pinNum < 0) {
return RandomsUtil.randomSysFile(0L, (long) (pinNum / 2 + 1));
} else if (designNum - pinNum < designNum / 2) {
return RandomsUtil.randomSysFile(0L, designNum - pinNum + 1);
} else {
return RandomsUtil.randomSysFile(0L, (long) (designNum/2 + 1));
return RandomsUtil.randomSysFile(0L, (long) (designNum / 2 + 1));
}
}
}
@@ -680,8 +693,7 @@ public class PythonService {
// 其他所有情况,都回退到使用系统推荐
String categoryParam = elementVO.getModelSex().toLowerCase() + "_" + styleCategory.toLowerCase();
List<String> recommentdUrlList = getSystemSketchByCategory(categoryParam, elementVO.getBrandId(), elementVO.getBrandScale(),elementVO.getStyle());
List<String> recommentdUrlList = getSystemSketchByCategory(categoryParam, elementVO.getBrandId(), elementVO.getBrandScale(), elementVO.getStyle());
if (CollectionUtils.isEmpty(recommentdUrlList)) {
throw new BusinessException("failed.to.obtain.system.sketch.recommendation");
}
@@ -1039,7 +1051,7 @@ public class PythonService {
if (!CollectionUtils.isEmpty(recommentdUrlList)) {
String recommendSystemSketch = recommentdUrlList.get(0);
return coverSystemSketchUrlToDesignPythonItem(recommendSystemSketch, category, validateElementVO);
}else {
} else {
throw new BusinessException("failed.to.obtain.system.sketch.recommendation");
}
// JSONObject attributeRecognition = getAttributeRecognitionBySameCategory(element, validateElementVO.getModelSex());
@@ -1062,7 +1074,7 @@ public class PythonService {
if (!CollectionUtils.isEmpty(recommentdUrlList)) {
String recommendSystemSketch = recommentdUrlList.get(0);
return coverSystemSketchUrlToDesignPythonItem(recommendSystemSketch, category, validateElementVO);
}else {
} else {
throw new BusinessException("failed.to.obtain.system.sketch.recommendation");
}
@@ -1098,7 +1110,7 @@ public class PythonService {
if (!CollectionUtils.isEmpty(recommentdUrlList)) {
String recommendSystemSketch = recommentdUrlList.get(0);
return coverSystemSketchUrlToDesignPythonItem(recommendSystemSketch, category, validateElementVO);
}else {
} else {
throw new BusinessException("failed.to.obtain.system.sketch.recommendation");
}
// JSONObject attributeRecognition = getAttributeRecognitionBySameCategory(element, validateElementVO.getModelSex());
@@ -1122,7 +1134,7 @@ public class PythonService {
if (!CollectionUtils.isEmpty(recommentdUrlList)) {
String recommendSystemSketch = recommentdUrlList.get(0);
return coverSystemSketchUrlToDesignPythonItem(recommendSystemSketch, category, validateElementVO);
}else {
} else {
throw new BusinessException("failed.to.obtain.system.sketch.recommendation");
}
// String tableName = getTableName(validateElementVO.getModelSex(), category);
@@ -2190,7 +2202,7 @@ public class PythonService {
basic.setScale_earrings(0.16);
if (Objects.nonNull(designLibraryModelPointVO)) {
basic.setBody_point_test(getMap(designLibraryModelPointVO));
}else {
} else {
basic.setBody_point_test(getMap(designLibraryModelPoint));
}
return basic;
@@ -2789,7 +2801,7 @@ public class PythonService {
DesignPythonObject pythonObject = new DesignPythonObject();
designPythonObjects.setProcess_id(designSingleDTO.getProcessId());
pythonObject.setItems(coverToDesignSinglePythonItem(designSingleDTO, designLibraryModelPoint, singleOverall));
pythonObject.setBasic(coverToSingleBasic(singleOverall, switchCategory, designLibraryModelPoint, previewOrSubmit));
pythonObject.setBasic(coverToSingleBasic(singleOverall, switchCategory, designLibraryModelPoint, designSingleDTO.getDesignType()));
objects.add(pythonObject);
return designPythonObjects;
}
@@ -2851,10 +2863,17 @@ public class PythonService {
gradientString = JSONObject.toJSONString(designSingleItem.getGradient());
}
PrintToPython printToPython = resolveDesignSinglePrint(designSingleItem.getPrintObject().getPrints(),
PrintToPython printToPython;
printToPython = resolveDesignSinglePrint(designSingleItem.getPrintObject().getPrints(),
designSingleItem.getPartialDesign().getPartialDesignMinioPath());
resolveDesignElement(designSingleItem.getTrims(), printToPython);
log.info("组装参数【服装:{}的maskUrl: {}】",designSingleItem.getType(), designSingleItem.getMaskUrl());
/*PrintToPython printToPython = resolveDesignSinglePrint(designSingleItem.getPrintObject().getPrints(),
designSingleItem.getPartialDesign().getPartialDesignMinioPath());*/
// resolveDesignElement(designSingleItem.getTrims(), printToPython);
log.info("组装参数【服装:{}的maskUrl: {}】", designSingleItem.getType(), designSingleItem.getMaskUrl());
String partialDesign = designSingleItem.getPartialDesign().getPartialDesignMinioPath();
String mergeImagePath = !StringUtil.isNullOrEmpty(partialDesign)
? partialDesign : designSingleItem.getPath();
response.add(new DesignPythonItem(
designSingleItem.getType(),
designSingleItem.getPath(),
@@ -2871,12 +2890,13 @@ public class PythonService {
gradientString,
designSingleItem.getMaskUrl(),
designSingleItem.getTranspose(),
designSingleItem.getRotate()
designSingleItem.getRotate(),
mergeImagePath
));
});
if (singleOverall.equals("overall")){
if (singleOverall.equals("overall")) {
String bodyPath;
if (Objects.nonNull(designLibraryModelPoint)) {
bodyPath = designLibraryModelPoint.getTemplateUrl();
@@ -2892,12 +2912,12 @@ public class PythonService {
private PrintToPython resolveDesignSinglePrint(List<DesignSinglePrint> printObject, String partialDesign) {
PrintToPython printToPython = new PrintToPython();
DesignPythonItemPrint printSingle = new DesignPythonItemPrint();
// DesignPythonItemPrint printSingle = new DesignPythonItemPrint();
DesignPythonItemPrint printOverall = new DesignPythonItemPrint();
printToPython.setSingle(printSingle);
// printToPython.setSingle(printSingle);
printToPython.setOverall(printOverall);
printToPython.setPartial(StringUtil.isNullOrEmpty(partialDesign) ? null : partialDesign);
if (Objects.isNull(printObject) || printObject.isEmpty()){
if (Objects.isNull(printObject) || printObject.isEmpty()) {
return printToPython;
}
@@ -2928,12 +2948,12 @@ public class PythonService {
Integer priority = p.getPriority();
setUriToMinioPath(p);
// todo 下标越界问题
if (p.getIfSingle()){
if (p.getIfSingle()) {
locationS.set(priority - 1, p.getLocation());
scaleS.set(priority - 1, p.getScale());
angleS.set( priority - 1, p.getAngle());
angleS.set(priority - 1, p.getAngle());
pathsS.set(priority - 1, p.getMinIOPath());
}else {
} else {
locationO.set(priority - 1, p.getLocation());
scaleO.set(priority - 1, p.getScale());
angleO.set(priority - 1, p.getAngle());
@@ -2941,14 +2961,14 @@ public class PythonService {
}
// log.info("本次print打点locations###{}###fileVO{}", p.getLocation(), JSON.toJSONString(fileVO));
});
locationS.removeAll(Collections.singleton(null));
/*locationS.removeAll(Collections.singleton(null));
scaleS.removeAll(Collections.singleton(null));
angleS.removeAll(Collections.singleton(null));
pathsS.removeAll(Collections.singleton(null));
printSingle.setLocation(locationS);
printSingle.setPrint_scale_list(scaleS);
printSingle.setPrint_angle_list(angleS);
printSingle.setPrint_path_list(pathsS);
printSingle.setPrint_path_list(pathsS);*/
locationO.removeAll(Collections.singleton(null));
scaleO.removeAll(Collections.singleton(null));
@@ -2963,9 +2983,9 @@ public class PythonService {
}
// 对印花类型为Generate的图片路径进行特殊处理
private void setUriToMinioPath(DesignSinglePrint print){
if (!StringUtil.isNullOrEmpty(print.getDesignType()) && print.getDesignType().equals("Generate")){
if (!StringUtil.isNullOrEmpty(print.getPath())){
private void setUriToMinioPath(DesignSinglePrint print) {
if (!StringUtil.isNullOrEmpty(print.getDesignType()) && print.getDesignType().equals("Generate")) {
if (!StringUtil.isNullOrEmpty(print.getPath())) {
try {
URI uri = new URI(print.getPath());
String path = uri.getPath(); // 获取路径部分: /aida-users/87/print/9ac32f65-6043-424d-a146-92c9c6d204ee-4-87.png
@@ -3049,7 +3069,7 @@ public class PythonService {
*/
private DesignPythonBasic coverToSingleBasic(String singleOverall, String switchCategory,
DesignLibraryModelPointVO designLibraryModelPoint,
String previewOrSubmit) {
String designType) {
DesignPythonBasic basic = new DesignPythonBasic();
basic.setSingle_overall(singleOverall);
basic.setSwitch_category(switchCategory);
@@ -3061,7 +3081,8 @@ public class PythonService {
basic.setScale_earrings(0.16);
basic.setBody_point_test(getMap(designLibraryModelPoint));
basic.setLayer_order(Boolean.TRUE);
basic.setPreview_submit(previewOrSubmit);
// basic.setPreview_submit(previewOrSubmit);
basic.setDesign_type(designType);
return basic;
}
@@ -3324,7 +3345,7 @@ public class PythonService {
throw new BusinessException("system error!");
}
public Boolean generateSketchOrPrint(String params, String port, String servicePath) {
public Boolean generateSketchOrPrint(String params, String port, String servicePath, String taskId) {
//限流校验
// AccessLimitUtils.validate("generateSketchOrPrint", 5);
OkHttpClient client = new OkHttpClient().newBuilder()
@@ -3386,12 +3407,36 @@ public class PythonService {
if (result && jsonObject.get("code").equals(200)) {
log.info("Generate##responseObject###{}", jsonObject);
// return setGenerateImageList(jsonObject.getJSONObject("data"));
if (servicePath == CommonConstant.GENERATE_PATH_FLUX2_KLEIN) {
//放入结果到mq
JSONObject data = jsonObject.getJSONObject("data");
String outputPath = data.getString("output_path");
Map<String, String> mqMessage = new HashMap<>();
mqMessage.put("tasks_id", taskId);
mqMessage.put("status", "SUCCESS");
mqMessage.put("message", "success");
mqMessage.put("image_url", outputPath);
mqMessage.put("category", "");
String mqMessageBody = JSON.toJSONString(mqMessage);
rabbitMQService.publishMessageToGenerateResult(mqMessageBody);
}
return Boolean.TRUE;
} else {
log.info("generateSketchOrPrintPrint失败###{}", jsonObject);
log.info("Generate Exception! Code : " + jsonObject.get("code"));
Map<String, String> mqMessage = new HashMap<>();
mqMessage.put("tasks_id", taskId);
mqMessage.put("status", "ERROR");
mqMessage.put("message", "");
mqMessage.put("image_url", "");
mqMessage.put("category", "");
String mqMessageBody = JSON.toJSONString(mqMessage);
rabbitMQService.publishMessageToGenerateResult(mqMessageBody);
return Boolean.FALSE;
}
}
public Response sendPostToModel(String content, String portAndRoute, String functionName) {
@@ -3433,7 +3478,10 @@ public class PythonService {
return imageUrlList;
}*/
/** 废弃状态 */
/**
* 废弃状态
*/
public String composeLayers(List<OutfitDetailPythonItem> layersDetail) {
HashMap<String, List<OutfitDetailPythonItem>> layers = new HashMap<>();
HashMap<String, HashMap<String, List<OutfitDetailPythonItem>>> content = new HashMap<>();
@@ -3744,7 +3792,7 @@ public class PythonService {
throw new BusinessException("relightImage.interface.exception");
}
public String imageToSketch(String imagePath, String bucket, String objectName, String styleCode, String styleImageUrl){
public String imageToSketch(String imagePath, String bucket, String objectName, String styleCode, String styleImageUrl) {
OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
.pingInterval(5, TimeUnit.SECONDS)//websocket轮训间隔(单位:秒)
@@ -3791,7 +3839,7 @@ public class PythonService {
String sketchResult = jsonObject.get("data").toString();
log.info("ImageToSketch 结果 {}", sketchResult);
return sketchResult;
}else {
} else {
log.info("ImageToSketch 失败。 Response code {}", responseCode);
throw new BusinessException("ImageToSketch 失败。 Response code " + responseCode);
}
@@ -3844,7 +3892,7 @@ public class PythonService {
throw new BusinessException("bright.interface.exception");
}
public JSONObject attributeRecognition(List<String> pictureUrls,List<String> ids, List<String> category) {
public JSONObject attributeRecognition(List<String> pictureUrls, List<String> ids, List<String> category) {
//限流校验
AccessLimitUtils.validate("attributeRecognition", 20);
OkHttpClient client = new OkHttpClient().newBuilder()
@@ -3876,7 +3924,7 @@ public class PythonService {
} catch (IOException ioException) {
log.error("PythonService###attributeRecognition异常##{}", ExceptionUtil.getThrowableList(ioException));
}
log.info("识别python对应的属性标签值结果###{}",bodyStr.trim());
log.info("识别python对应的属性标签值结果###{}", bodyStr.trim());
//去除限流
AccessLimitUtils.validateOut("attributeRecognition");
if (Objects.isNull(response)) {
@@ -3960,7 +4008,7 @@ public class PythonService {
throw new BusinessException("design.interface.exception");
}
public List<String> getSystemSketchByCategory(String category, Long brandId, Double brandScale,String style) {
public List<String> getSystemSketchByCategory(String category, Long brandId, Double brandScale, String style) {
//******3.1.2版本临时使用java推荐方案去解决style未使用的问题**********
// try {
// //使用新库attribute_retrieval_style表命名修改为elementVO.getModelSex().toLowerCase() + "_" + styleCategory.toLowerCase()比如female_skirt,与传入的category保持一致
@@ -3984,6 +4032,14 @@ public class PythonService {
// throw new BusinessException("system.error");
// }
//**********************end***********************************
//临时补丁
if (category != null && style != null) {
String categoryPrefix = category.split("_")[0];
if ("male".equals(categoryPrefix) &&
(style.equalsIgnoreCase("lolita") || style.equalsIgnoreCase("romantic"))) {
style = null;
}
}
AuthPrincipalVo userHolder = UserContext.getUserHolder();
OkHttpClient client = new OkHttpClient().newBuilder()
@@ -4105,6 +4161,7 @@ public class PythonService {
//生成失败
throw new BusinessException("segProduct.interface.exception");
}
/**
* 转发 seg_anything 请求到 python 服务
* 请求 body 由调用方组装(包含 user_id, image_path, type, points, labels, box 等)
@@ -4117,6 +4174,9 @@ public class PythonService {
.writeTimeout(60, TimeUnit.SECONDS)
.build();
MediaType mediaType = MediaType.parse("application/json");
content.put("bucket", userBucketName);
content.put("object_name", content.get("user_id") + "/" + "segment" + "/" + UUID.randomUUID() + ".png");
content.remove("user_id");
RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(content));
String url = accessPythonIp + ":" + accessPythonPort + "/api/seg_anything";
@@ -4147,21 +4207,19 @@ public class PythonService {
if (responseObject != null) {
JSONObject dataObj = responseObject.getJSONObject("data");
if (dataObj != null) {
JSONObject inner = dataObj.getJSONObject("data");
if (inner != null) {
String output = inner.getString("output");
if (!StringUtil.isNullOrEmpty(output)) {
try {
String presigned = minioUtil.getPreSignedUrl(output, 24 * 60);
return presigned;
} catch (Exception e) {
log.error("Failed to generate presigned url for {}: {}", output, e.getMessage());
throw new BusinessException("segAnything.presign.failed");
}
String output = dataObj.getString("output");
if (!StringUtil.isNullOrEmpty(output)) {
try {
String presigned = minioUtil.getPreSignedUrl(output, 24 * 60);
return presigned;
} catch (Exception e) {
log.error("Failed to generate presigned url for {}: {}", output, e.getMessage());
throw new BusinessException("segAnything.presign.failed");
}
}
}
}
} catch (Exception ex) {
log.error("PythonService##segAnything post-process error###{}", ex.getMessage());
throw new BusinessException("segAnything.interface.exception");
@@ -4178,6 +4236,7 @@ public class PythonService {
log.error("PythonService##segAnything异常response###{}", response);
throw new BusinessException("segAnything.interface.exception");
}
public Boolean poseTransformation(JSONObject content, String apiUri) {
OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
@@ -4281,7 +4340,7 @@ public class PythonService {
String modifiedModel = jsonObject.get("data").toString();
log.info("modifyModelProportion 结果 {}", modifiedModel);
return modifiedModel;
}else {
} else {
log.info("modifyModelProportion 失败。 Response code {}", responseCode);
throw new BusinessException("modifyModelProportion 失败。 Response code " + responseCode);
}
@@ -4306,7 +4365,7 @@ public class PythonService {
Map<String, Object> map = new HashMap<>();
map.put("prompt", prompt);
AuthPrincipalVo userHolder = UserContext.getUserHolder();
map.put("user_id", userHolder.getId());
map.put("user_id", userHolder.getId().toString());
log.info("brandDNAGenerate请求python 参数:####{}", map);
String param = JSON.toJSONString(map, SerializerFeature.WriteNullStringAsEmpty);
log.info(param);
@@ -4384,10 +4443,11 @@ public class PythonService {
log.info("imageSegmentation 结果 {}", jsonObject.get("data").toString());
List<ImageSegmentation.ImageDate> seg = JSONObject.parseObject(
jsonObject.get("data").toString(),
new TypeReference<List<ImageSegmentation.ImageDate>>() {}
new TypeReference<List<ImageSegmentation.ImageDate>>() {
}
);
return seg;
}else {
} else {
log.info("imageSegmentation 失败。 Response code {}", responseCode);
throw new BusinessException("imageSegmentation 失败。 Response code " + responseCode);
}

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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