Compare commits
267 Commits
3f5ce6e0e7
...
dev/3.1_re
| Author | SHA1 | Date | |
|---|---|---|---|
| 11073690e5 | |||
|
|
921d2d956e | ||
|
|
d700f94f9d | ||
|
|
b277479e73 | ||
|
|
83cbd57dea | ||
|
|
4d3b22de82 | ||
|
|
6b5c2cfec0 | ||
|
|
b676de054a | ||
|
|
4c169ef67e | ||
|
|
f2bce066b6 | ||
|
|
6af442eb15 | ||
|
|
768df55309 | ||
|
|
f351277b73 | ||
|
|
a799162ea4 | ||
|
|
c035eb9d7d | ||
|
|
906a54b3c8 | ||
|
|
643799546b | ||
|
|
f582464cd3 | ||
|
|
b864b393bc | ||
|
|
c03a8762e7 | ||
|
|
cb87ad1099 | ||
|
|
fb229764f8 | ||
|
|
8bec1f842d | ||
|
|
b54bd04cff | ||
|
|
b4ccad6242 | ||
|
|
6068bf7d7d | ||
|
|
d36baf747f | ||
| 7c8f1bee6a | |||
|
|
62bd145e2c | ||
|
|
23716984cc | ||
|
|
d0b8b8d674 | ||
|
|
92e7dbf258 | ||
|
|
32a228485b | ||
|
|
560e47747a | ||
|
|
c8dc38575a | ||
|
|
c00d906083 | ||
| 4df3f9cc53 | |||
|
|
b0343be544 | ||
|
|
d33cb9f0bf | ||
|
|
40f2735831 | ||
| 01d3806d5f | |||
| 107e4e9771 | |||
|
|
716d720782 | ||
|
|
6b5bacc49b | ||
|
|
409bc7b1fd | ||
|
|
ec6a5df8af | ||
|
|
029b96ae99 | ||
|
|
14002e7331 | ||
| 14dfe2806c | |||
| 798c7b0592 | |||
| 9bd10581f4 | |||
| 1f288fe5e3 | |||
| 72602eb245 | |||
| 983d53268d | |||
| f3aeeb3584 | |||
| 5d3692a204 | |||
| f2a074b2f6 | |||
| 6a7a37dcec | |||
|
|
c4d2780f0e | ||
|
|
1da6b7728c | ||
|
|
0faf77899b | ||
|
|
e4940019bf | ||
|
|
0da66ff210 | ||
|
|
5dd862ff79 | ||
|
|
edaec9884d | ||
|
|
76eeb2be53 | ||
|
|
cb6f94d2d4 | ||
|
|
28656c44c8 | ||
|
|
6757a89d04 | ||
|
|
9be1a1e307 | ||
|
|
2168978f61 | ||
|
|
54466b935d | ||
|
|
c970ebe691 | ||
|
|
1c5a3a12b9 | ||
|
|
6e06000083 | ||
|
|
dea2b3be42 | ||
|
|
bcf51aea23 | ||
|
|
0c9d5404c6 | ||
|
|
93429839c0 | ||
|
|
27859c3e28 | ||
|
|
f02c0930a6 | ||
|
|
d57bb83b25 | ||
| 731e34f133 | |||
| 75eca8d6ba | |||
| 3e53401f76 | |||
|
|
b6a068ebcd | ||
|
|
dc291ea086 | ||
|
|
2e846e671a | ||
|
|
a5093311f9 | ||
|
|
aed338a6d7 | ||
|
|
8bdb49d25c | ||
|
|
5d53a8cd42 | ||
|
|
61b7f3072f | ||
|
|
a1f489f3a1 | ||
|
|
fc3fd877a8 | ||
|
|
fc72d2c430 | ||
|
|
1ac01dd090 | ||
|
|
3bbdf7c672 | ||
|
|
0646484fba | ||
|
|
96b8613741 | ||
|
|
cf30226a51 | ||
|
|
3c15a3ff68 | ||
|
|
0c904be227 | ||
|
|
7759b56123 | ||
|
|
d5bfaa8822 | ||
| 967c0cbc01 | |||
| 417e34b41a | |||
|
|
d51aa84647 | ||
|
|
5895bc6ab6 | ||
|
|
3301869f20 | ||
|
|
1ec42f4ad5 | ||
| cc506ff7e9 | |||
|
|
f2d43f06f4 | ||
|
|
9251df49f8 | ||
| 430156f4e8 | |||
|
|
d1123aedcc | ||
| 8c007077a3 | |||
|
|
d63b4b4e63 | ||
|
|
b826f0bf39 | ||
|
|
1decd8e258 | ||
|
|
1286e84488 | ||
| a252fdf7f9 | |||
| 807d802178 | |||
| 53f1b548be | |||
| 45dd78032a | |||
| c160da5132 | |||
| b23faeeee2 | |||
| 67789abca4 | |||
| 1c78d66aab | |||
| 528bc69923 | |||
|
|
22880d128d | ||
| 9c56a102cc | |||
| 2f59fe074f | |||
| 9c61b1c8fe | |||
| e30fdf7401 | |||
| ba2d10afbc | |||
| 6146112d04 | |||
| 412550df27 | |||
| 497421e7fe | |||
| 891527426c | |||
|
|
3e334d7956 | ||
|
|
8f0d0953b2 | ||
| f5c3621a5d | |||
|
|
9a1a0045e0 | ||
| 6223c8e994 | |||
| 67bbee49fd | |||
| ad62ceb32a | |||
| 082afe9e94 | |||
| 49288c3a31 | |||
| 81624e36db | |||
| a526b122d1 | |||
|
|
d882b2e817 | ||
|
|
ebf6427d42 | ||
| 77fe03d361 | |||
| 7a44d67dbf | |||
| 55ce2c6c7e | |||
| a426caaca3 | |||
| 7cb7ce2836 | |||
| 8e075f1da4 | |||
|
|
0f0fde2a3e | ||
|
|
8c6389a1f6 | ||
| 652f82b6a4 | |||
| 7ca2528dcf | |||
|
|
a7800913d2 | ||
|
|
1eaec64ff4 | ||
| e603952332 | |||
| 2bc8b8ef96 | |||
| 0ce968b919 | |||
|
|
dfc9ae4db2 | ||
|
|
a3505c6d95 | ||
|
|
6db0afd515 | ||
|
|
b1e6183dd1 | ||
| 30d08356c0 | |||
| 64cc29f456 | |||
|
|
2b3e12a11c | ||
|
|
d4a4724f61 | ||
|
|
ba6e2bd24c | ||
|
|
a38895b028 | ||
|
|
69a95e66ca | ||
|
|
40518cab37 | ||
|
|
46d61cb73f | ||
| 08f20fd1fe | |||
| d7edc166b3 | |||
|
|
79ad02f66b | ||
|
|
5e261b55c7 | ||
|
|
bc92fcbaf4 | ||
|
|
c6aec917c2 | ||
| 6bc500e78f | |||
| 4c43b98c02 | |||
|
|
5bae785a9f | ||
|
|
7b619aa4cb | ||
| c93ad6daa9 | |||
| 0047be7a03 | |||
| 4ef209cfd4 | |||
| a19751b4b7 | |||
|
|
bb0e5a4263 | ||
|
|
9e9df5367d | ||
| ba8a2c52de | |||
| 39d8c7efcf | |||
|
|
401910901a | ||
| 0787025151 | |||
| 08b26872ff | |||
| 5bbf1326bb | |||
|
|
c5e27cd220 | ||
|
|
ce95cb5080 | ||
| 72ad977dcb | |||
|
|
6400e79929 | ||
| 13151b65f5 | |||
| 9f523d5953 | |||
| 4879cfeb60 | |||
| 9e252b16ef | |||
| e64add14af | |||
| 3beb27e491 | |||
| 501032ef17 | |||
|
|
cb25bdd2e0 | ||
| cd767dce6f | |||
| bf95b85841 | |||
| 9e58bd9e7d | |||
| d0ec5c5c26 | |||
| ab8aa5ea5c | |||
| aefcd2fdb0 | |||
| e74eab1070 | |||
|
|
34da437a26 | ||
| 35edaa0f27 | |||
| f43099e19e | |||
| 8079877734 | |||
| ef686e38ac | |||
| 100019d2a4 | |||
|
|
12af237d76 | ||
|
|
44dbbb2a4b | ||
| 9f42e153a4 | |||
| 4fa70a1c90 | |||
| dfb34916e7 | |||
| 9f7987306c | |||
|
|
d3fc70fbf2 | ||
|
|
17645d17e5 | ||
|
|
258eea5277 | ||
|
|
bb1d3bd359 | ||
|
|
6a8c87ed95 | ||
|
|
eb3826927d | ||
|
|
7720c8c771 | ||
|
|
b459148d58 | ||
|
|
eadda18d1e | ||
|
|
d403df51ec | ||
| 6903b98b60 | |||
| 81bf65515c | |||
| 8e984eb283 | |||
|
|
afc6041570 | ||
| bce368248c | |||
| ca7121dcda | |||
|
|
9a206f9979 | ||
|
|
eb7a46c7e8 | ||
|
|
95ef68a784 | ||
| c5af194876 | |||
| da84e1e4b4 | |||
| 95a9c81a1b | |||
| 81c0d7eeac | |||
| 6cd42b799a | |||
| 6e1ed7f9b8 | |||
| b7be16738b | |||
| 6da5e91ec1 | |||
| a710fdd432 | |||
| d598f53d3c | |||
| 96170a9956 | |||
| 8205fb5290 | |||
| fcbe4762b3 | |||
| e750adcc94 |
111
.gitea/workflows/develop_3.1_MS_build_manual.yaml
Normal file
111
.gitea/workflows/develop_3.1_MS_build_manual.yaml
Normal 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
|
||||
@@ -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
70
pom.xml
@@ -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>
|
||||
|
||||
@@ -3,6 +3,8 @@ 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;
|
||||
|
||||
@@ -10,6 +12,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
@EnableFeignClients
|
||||
@EnableDiscoveryClient
|
||||
public class AiDaApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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 含有的字段
|
||||
|
||||
@@ -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");
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
34
src/main/java/com/ai/da/common/config/SecurityConfig.java
Normal file
34
src/main/java/com/ai/da/common/config/SecurityConfig.java
Normal 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 配置。
|
||||
* 由于鉴权逻辑已迁移至 Gateway(GlobalAuthWebFilter),
|
||||
* 后端服务 (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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -4,11 +4,13 @@ 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
|
||||
@@ -36,6 +38,14 @@ public class ExceptionCatch {
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -3,17 +3,39 @@ package com.ai.da.common.context;
|
||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||
|
||||
public class UserContext {
|
||||
private static ThreadLocal<AuthPrincipalVo> userHolder = new ThreadLocal<AuthPrincipalVo>();
|
||||
private static final ThreadLocal<AuthPrincipalVo> userHolder = new ThreadLocal<>();
|
||||
|
||||
public static void setUserHolder(AuthPrincipalVo authPrincipalVo) {
|
||||
userHolder.set(authPrincipalVo);
|
||||
}
|
||||
|
||||
public static AuthPrincipalVo getUserHolder() {
|
||||
return userHolder.get();
|
||||
AuthPrincipalVo holder = userHolder.get();
|
||||
if (holder == null) {
|
||||
throw new RuntimeException("User not authenticated");
|
||||
}
|
||||
if (!"AIDA".equals(holder.getSource())) {
|
||||
throw new RuntimeException("Access denied: source must be AIDA");
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
userHolder.remove();
|
||||
}
|
||||
|
||||
public static void setUserHolder(AuthPrincipalVo authPrincipalVo) {
|
||||
userHolder.set(authPrincipalVo);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,7 +28,7 @@ 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();
|
||||
@@ -37,8 +40,8 @@ public class AccountTask {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -97,14 +97,14 @@ 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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -35,6 +34,7 @@ public class MailUtil {
|
||||
* 发送邮件 - 默认发件人
|
||||
*
|
||||
* @param basicEmailParamDTO 发送邮件所需参数
|
||||
* @param fileName 附件名(如果有)
|
||||
* @param inputStreamSource 附件(如果有)
|
||||
*/
|
||||
public int sendMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
|
||||
@@ -62,30 +62,23 @@ public class MailUtil {
|
||||
}
|
||||
|
||||
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();
|
||||
// 配置连接属性
|
||||
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);
|
||||
|
||||
log.info("SMTP 状态码: {}, SMTP 服务器响应: {}", lastReturnCode, lastServerResponse);
|
||||
return lastReturnCode;
|
||||
} catch (MailException | MessagingException e) {
|
||||
// 记录日志或执行其他补偿逻辑
|
||||
// 使用 JavaMailSender 发送邮件(Spring Boot 3.x 标准方式)
|
||||
javaMailSender.send(mimeMessage);
|
||||
log.info("邮件发送成功至: {}", host);
|
||||
return 1;
|
||||
} catch (MailException e) {
|
||||
log.info("邮件发送失败:{}", e.getMessage());
|
||||
} finally {
|
||||
// 关闭连接
|
||||
assert transport != null;
|
||||
transport.close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一封邮件
|
||||
|
||||
@@ -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表示是预签名URL,false表示不是
|
||||
*/
|
||||
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或逻辑路径");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
131
src/main/java/com/ai/da/common/utils/TokenGenerateUtils.java
Normal file
131
src/main/java/com/ai/da/common/utils/TokenGenerateUtils.java
Normal 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 生成工具类(仅负责生成,不负责鉴权)。
|
||||
* 鉴权逻辑已迁移至 Gateway(GlobalAuthWebFilter)。
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -191,6 +191,7 @@ public class AccountController {
|
||||
|
||||
/**
|
||||
* 参与活动 获取福利
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
/* @Operation(summary = "参与活动 获取福利")
|
||||
|
||||
@@ -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 = "获取所有交易信息")
|
||||
|
||||
203
src/main/java/com/ai/da/controller/GlobalAwardController.java
Normal file
203
src/main/java/com/ai/da/controller/GlobalAwardController.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
20
src/main/java/com/ai/da/feign/seller/SellerFeignClient.java
Normal file
20
src/main/java/com/ai/da/feign/seller/SellerFeignClient.java
Normal 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);
|
||||
}
|
||||
12
src/main/java/com/ai/da/mapper/primary/ContestantMapper.java
Normal file
12
src/main/java/com/ai/da/mapper/primary/ContestantMapper.java
Normal 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> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,11 @@ public class SubscriptionPlan extends BaseEntity{
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 国家或地区
|
||||
*/
|
||||
private String countryOrRegion;
|
||||
|
||||
// 在类内部定义的枚举
|
||||
@Getter
|
||||
public enum SubscriptionStatus {
|
||||
|
||||
74
src/main/java/com/ai/da/model/dto/ContestantDTO.java
Normal file
74
src/main/java/com/ai/da/model/dto/ContestantDTO.java
Normal 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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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{" +
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
55
src/main/java/com/ai/da/model/dto/ImageProcessRequest.java
Normal file
55
src/main/java/com/ai/da/model/dto/ImageProcessRequest.java
Normal 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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -14,4 +14,6 @@ public class SubAccountPageDTO extends PageQueryBaseVo {
|
||||
private List<String> email;
|
||||
|
||||
private List<String> userName;
|
||||
|
||||
private Long subscriptionPlanId;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -29,4 +29,10 @@ public class UpdateSubscriptionPlanDTO {
|
||||
@Schema(description = "管理员账户id")
|
||||
private Long adminAccId;
|
||||
|
||||
@Schema(description = "订阅重命名")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "国家或地区")
|
||||
private String countryOrRegion;
|
||||
|
||||
}
|
||||
|
||||
33
src/main/java/com/ai/da/model/dto/UploadChunkResponse.java
Normal file
33
src/main/java/com/ai/da/model/dto/UploadChunkResponse.java
Normal 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;
|
||||
}
|
||||
53
src/main/java/com/ai/da/model/dto/UploadCompleteRequest.java
Normal file
53
src/main/java/com/ai/da/model/dto/UploadCompleteRequest.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
53
src/main/java/com/ai/da/model/dto/UploadInitRequest.java
Normal file
53
src/main/java/com/ai/da/model/dto/UploadInitRequest.java
Normal 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;
|
||||
}
|
||||
41
src/main/java/com/ai/da/model/dto/UploadInitResponse.java
Normal file
41
src/main/java/com/ai/da/model/dto/UploadInitResponse.java
Normal 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;
|
||||
}
|
||||
59
src/main/java/com/ai/da/model/dto/UploadStatusResponse.java
Normal file
59
src/main/java/com/ai/da/model/dto/UploadStatusResponse.java
Normal 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;
|
||||
}
|
||||
@@ -79,4 +79,10 @@ public class AccountLoginVO {
|
||||
|
||||
private String givenName;
|
||||
|
||||
private Long organizationId;
|
||||
|
||||
private String organizationName;
|
||||
|
||||
private Long subscriptionPlanId;
|
||||
|
||||
}
|
||||
|
||||
16
src/main/java/com/ai/da/model/vo/CheckOTPVO.java
Normal file
16
src/main/java/com/ai/da/model/vo/CheckOTPVO.java
Normal 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;
|
||||
}
|
||||
17
src/main/java/com/ai/da/model/vo/ContestantCountVO.java
Normal file
17
src/main/java/com/ai/da/model/vo/ContestantCountVO.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
*
|
||||
@@ -406,6 +418,7 @@ 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();
|
||||
@@ -681,7 +694,6 @@ public class PythonService {
|
||||
// 其他所有情况,都回退到使用系统推荐
|
||||
String categoryParam = elementVO.getModelSex().toLowerCase() + "_" + styleCategory.toLowerCase();
|
||||
List<String> recommentdUrlList = getSystemSketchByCategory(categoryParam, elementVO.getBrandId(), elementVO.getBrandScale(), elementVO.getStyle());
|
||||
|
||||
if (CollectionUtils.isEmpty(recommentdUrlList)) {
|
||||
throw new BusinessException("failed.to.obtain.system.sketch.recommendation");
|
||||
}
|
||||
@@ -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);
|
||||
/*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,7 +2890,8 @@ public class PythonService {
|
||||
gradientString,
|
||||
designSingleItem.getMaskUrl(),
|
||||
designSingleItem.getTranspose(),
|
||||
designSingleItem.getRotate()
|
||||
designSingleItem.getRotate(),
|
||||
mergeImagePath
|
||||
));
|
||||
|
||||
});
|
||||
@@ -2892,9 +2912,9 @@ 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()) {
|
||||
@@ -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));
|
||||
@@ -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<>();
|
||||
@@ -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,9 +4207,7 @@ 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");
|
||||
String output = dataObj.getString("output");
|
||||
if (!StringUtil.isNullOrEmpty(output)) {
|
||||
try {
|
||||
String presigned = minioUtil.getPreSignedUrl(output, 24 * 60);
|
||||
@@ -4161,7 +4219,7 @@ public class PythonService {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} 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)
|
||||
@@ -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,7 +4443,8 @@ 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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
37
src/main/java/com/ai/da/seller/DesignUrlsDTO.java
Normal file
37
src/main/java/com/ai/da/seller/DesignUrlsDTO.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
44
src/main/java/com/ai/da/seller/SellerController.java
Normal file
44
src/main/java/com/ai/da/seller/SellerController.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -246,4 +246,6 @@ public interface AccountService extends IService<Account> {
|
||||
void setEduAdminToExpire(Account adminAccount);
|
||||
|
||||
String getOrganizationTypeByRole(Integer roleNum);
|
||||
|
||||
void validateUserValidaExpire(Account account);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
51
src/main/java/com/ai/da/service/GlobalAwardService.java
Normal file
51
src/main/java/com/ai/da/service/GlobalAwardService.java
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ public interface RabbitMQService {
|
||||
|
||||
void publishMessageToGenerate(String message);
|
||||
|
||||
void publishMessageToGenerateResult(String message);
|
||||
|
||||
void publishMessageToSR(String message);
|
||||
|
||||
Integer getMessageCount(String queueUrl);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -121,4 +122,11 @@ public interface UserLikeGroupService extends IService<UserLikeGroup> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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 中的 token(Gateway 黑名单会接力生效)
|
||||
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());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)) {
|
||||
|
||||
// 权限校验
|
||||
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());
|
||||
}
|
||||
|
||||
// 创建分页对象
|
||||
Page<Account> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
// 构建查询条件
|
||||
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.select("id as value, user_name as label");
|
||||
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());
|
||||
}
|
||||
return accountMapper.selectMaps(queryWrapper);
|
||||
} else {
|
||||
throw new BusinessException("have.no.permission", ResultEnum.PROMPT.getCode());
|
||||
|
||||
if (!StringUtil.isNullOrEmpty(email)) {
|
||||
queryWrapper.lambda().like(Account::getUserEmail, email);
|
||||
}
|
||||
// return maps.stream().map(map -> (Long)map.get("id")).collect(Collectors.toList());
|
||||
|
||||
// 执行分页查询
|
||||
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());
|
||||
// 总页数
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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())) {
|
||||
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) {
|
||||
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))
|
||||
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));
|
||||
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")) {
|
||||
|
||||
@@ -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);
|
||||
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());
|
||||
@@ -1154,7 +1168,12 @@ public class DesignServiceImpl extends ServiceImpl<DesignMapper, Design> impleme
|
||||
.toLocalDateTime());
|
||||
userPreference.setDesignItemId(designItem.getId());
|
||||
userPreference.setProjectId(projectId);
|
||||
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
|
||||
|
||||
@@ -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,9 +198,12 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
||||
generate.setText(text);
|
||||
Long elementId = generateThroughImageTextDTO.getCollectionElementId();
|
||||
// validateGeneraType(generate, text, elementId);
|
||||
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
|
||||
@@ -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 {
|
||||
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() 完成后事务尚未 commit,MQ 消费者立即读到 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
|
||||
@@ -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:
|
||||
|
||||
624
src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java
Normal file
624
src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java
Normal file
@@ -0,0 +1,624 @@
|
||||
package com.ai.da.service.impl;
|
||||
|
||||
import com.ai.da.common.config.exception.BusinessException;
|
||||
import com.ai.da.common.enums.AuthenticationOperationTypeEnum;
|
||||
import com.ai.da.common.utils.*;
|
||||
import com.ai.da.mapper.primary.AccountMapper;
|
||||
import com.ai.da.mapper.primary.ContestantMapper;
|
||||
import com.ai.da.mapper.primary.NotificationMapper;
|
||||
import com.ai.da.mapper.primary.entity.Account;
|
||||
import com.ai.da.mapper.primary.entity.Contestant;
|
||||
import com.ai.da.mapper.primary.entity.Notification;
|
||||
import com.ai.da.model.dto.ContestantDTO;
|
||||
import com.ai.da.model.dto.PublishSysNotificationDTO;
|
||||
import com.ai.da.model.vo.CheckOTPVO;
|
||||
import com.ai.da.model.vo.ContestantCountVO;
|
||||
import com.ai.da.service.GlobalAwardService;
|
||||
import com.ai.da.service.MessageCenterService;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class GlobalAwardServiceImpl implements GlobalAwardService {
|
||||
|
||||
@Resource
|
||||
private ContestantMapper contestantMapper;
|
||||
|
||||
private final AccountMapper accountMapper;
|
||||
|
||||
private final MessageCenterService messageCenterService;
|
||||
|
||||
private final NotificationMapper notificationMapper;
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
@Value("${file.upload.temp.dir}")
|
||||
private String uploadDir;
|
||||
|
||||
private static final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyy/MM");
|
||||
|
||||
private static final String tokenCacheKey = AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + ":";
|
||||
|
||||
@Value("${minio.bucket:contestants}")
|
||||
private String minioBucket;
|
||||
|
||||
@Value("${global.award.link}")
|
||||
private String link;
|
||||
|
||||
@Resource
|
||||
private MinioUtil minioUtil;
|
||||
|
||||
@Override
|
||||
public String uploadPdf(MultipartFile file, String email) throws Exception {
|
||||
validatePdf(file);
|
||||
String path = storeFile(file, email, "pdf");
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadVideo(MultipartFile file, String email) throws Exception {
|
||||
validateVideo(file);
|
||||
String path = storeFile(file, email, "video");
|
||||
return path;
|
||||
}
|
||||
|
||||
private void validatePdf(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BusinessException("File is empty.");
|
||||
}
|
||||
String ct = file.getContentType();
|
||||
if (ct == null || !ct.toLowerCase().contains("pdf")) {
|
||||
throw new BusinessException("Only PDF files are allowed.");
|
||||
}
|
||||
// size limit example 20MB
|
||||
if (file.getSize() > 20L * 1024 * 1024) {
|
||||
throw new BusinessException("PDF file size exceeds the limit.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateVideo(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BusinessException("File is empty.");
|
||||
}
|
||||
String ct = file.getContentType();
|
||||
if (ct == null || !(ct.toLowerCase().contains("mp4") || ct.toLowerCase().contains("video") )) {
|
||||
throw new BusinessException("Invalid video file type.");
|
||||
}
|
||||
// size limit example 100MB
|
||||
if (file.getSize() > 100L * 1024 * 1024) {
|
||||
throw new BusinessException("Video file size exceeds the limit.");
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeEmail(String email) {
|
||||
if (email == null) {
|
||||
return "anonymous";
|
||||
}
|
||||
return email.replaceAll("[^a-zA-Z0-9]", "_");
|
||||
}
|
||||
|
||||
private String storeFile(MultipartFile file, String email, String kind) throws IOException {
|
||||
String normalized = normalizeEmail(email);
|
||||
String datePart = LocalDateTime.now().format(YYYY_MM_DD);
|
||||
String ext = "";
|
||||
String original = file.getOriginalFilename();
|
||||
if (original != null && original.contains(".")) {
|
||||
ext = original.substring(original.lastIndexOf('.'));
|
||||
}
|
||||
String filename = System.currentTimeMillis() + "_" + UUID.randomUUID().toString() + ext;
|
||||
String relativePath = "contestants/" + normalized + "/" + datePart + "/" + filename;
|
||||
|
||||
String uploadedPath = minioUtil.upload(minioBucket, relativePath, file, null);
|
||||
log.info("uploaded via MinioUtil: {}", uploadedPath);
|
||||
return uploadedPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> saveContestant(ContestantDTO request) {
|
||||
Map<String,Object> resp = new HashMap<>();
|
||||
if (request.getEmail() == null) {
|
||||
throw new BusinessException("Email is required.");
|
||||
}
|
||||
|
||||
checkSecurityToken(request.getEmail(), request.getSecureToken());
|
||||
|
||||
QueryWrapper<Contestant> qw = new QueryWrapper<>();
|
||||
qw.eq("email", request.getEmail());
|
||||
Contestant existing = contestantMapper.selectOne(qw);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (existing == null) {
|
||||
// 通过行锁 + 重试机制保证 contestant_number 在并发下自增分配
|
||||
final int maxAttempts = 5;
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
// 获取当前最大 contestant_number 并加行锁(LIMIT 1 FOR UPDATE)
|
||||
QueryWrapper<Contestant> qMax = new QueryWrapper<>();
|
||||
qMax.isNotNull("contestant_number");
|
||||
qMax.orderByDesc("contestant_number");
|
||||
qMax.last("LIMIT 1 FOR UPDATE");
|
||||
Contestant last = contestantMapper.selectOne(qMax);
|
||||
Integer nextNumber = (last == null || last.getContestantNumber() == null) ? 10000 : last.getContestantNumber() + 1;
|
||||
|
||||
Contestant toInsert = Contestant.builder()
|
||||
.email(request.getEmail())
|
||||
.firstName(request.getFirstName())
|
||||
.lastName(request.getLastName())
|
||||
.gender(request.getGender())
|
||||
.occupation(request.getOccupation())
|
||||
.age(request.getAge())
|
||||
.countryRegionCity(request.getCountryRegionCity())
|
||||
.phoneNumber(request.getPhoneNumber())
|
||||
.designTitle(request.getDesignTitle())
|
||||
.designDescription(request.getDesignDescription())
|
||||
.pdfPath(request.getPdfPath())
|
||||
.videoPath(request.getVideoPath())
|
||||
.videoDuration(request.getVideoDuration())
|
||||
.videoSize(request.getVideoSize())
|
||||
.pdfSize(request.getPdfSize())
|
||||
.contestantNumber(nextNumber)
|
||||
.portfolioUrl(request.getPortfolioUrl())
|
||||
.createdAt(now)
|
||||
.updatedAt(now)
|
||||
.build();
|
||||
|
||||
contestantMapper.insert(toInsert);
|
||||
|
||||
resp.put("success", true);
|
||||
sendSiteMsg(toInsert.getId(), toInsert.getEmail());
|
||||
return resp;
|
||||
} catch (Exception e) {
|
||||
log.warn("Attempt {} to assign contestant_number failed", attempt, e);
|
||||
String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
|
||||
if ((msg.contains("duplicate") || msg.contains("uniq_contestant_number") || msg.contains("contestant_number")) && attempt < maxAttempts) {
|
||||
try {
|
||||
Thread.sleep(100L);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw new BusinessException("Failed to assign contestant number after retries.");
|
||||
} else {
|
||||
// update existing contestant
|
||||
existing.setFirstName(request.getFirstName());
|
||||
existing.setLastName(request.getLastName());
|
||||
existing.setGender(request.getGender());
|
||||
existing.setOccupation(request.getOccupation());
|
||||
existing.setAge(request.getAge());
|
||||
existing.setCountryRegionCity(request.getCountryRegionCity());
|
||||
existing.setPhoneNumber(request.getPhoneNumber());
|
||||
existing.setDesignTitle(request.getDesignTitle());
|
||||
existing.setDesignDescription(request.getDesignDescription());
|
||||
existing.setPdfPath(request.getPdfPath());
|
||||
existing.setVideoPath(request.getVideoPath());
|
||||
existing.setVideoDuration(request.getVideoDuration());
|
||||
existing.setVideoSize(request.getVideoSize());
|
||||
existing.setPdfSize(request.getPdfSize());
|
||||
existing.setPortfolioUrl(request.getPortfolioUrl());
|
||||
existing.setUpdatedAt(now);
|
||||
contestantMapper.updateById(existing);
|
||||
resp.put("success", true);
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportContestants() throws Exception {
|
||||
List<Contestant> list = contestantMapper.selectList(new QueryWrapper<>());
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = workbook.createSheet("contestants");
|
||||
int rowIdx = 0;
|
||||
Row header = sheet.createRow(rowIdx++);
|
||||
String[] headers = new String[] {
|
||||
"contestantNumber", "email", "firstName", "lastName", "gender", "occupation",
|
||||
"age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription",
|
||||
"pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "portfolioUrl", "createdAt", "updatedAt"
|
||||
};
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
Cell c = header.createCell(i);
|
||||
c.setCellValue(headers[i]);
|
||||
}
|
||||
|
||||
for (Contestant cst : list) {
|
||||
Row r = sheet.createRow(rowIdx++);
|
||||
int ci = 0;
|
||||
r.createCell(ci++).setCellValue(cst.getContestantNumber() == null ? "" : cst.getContestantNumber().toString());
|
||||
r.createCell(ci++).setCellValue(cst.getEmail() == null ? "" : cst.getEmail());
|
||||
r.createCell(ci++).setCellValue(cst.getFirstName() == null ? "" : cst.getFirstName());
|
||||
r.createCell(ci++).setCellValue(cst.getLastName() == null ? "" : cst.getLastName());
|
||||
r.createCell(ci++).setCellValue(cst.getGender() == null ? "" : cst.getGender());
|
||||
r.createCell(ci++).setCellValue(cst.getOccupation() == null ? "" : cst.getOccupation());
|
||||
r.createCell(ci++).setCellValue(cst.getAge() == null ? "" : cst.getAge().toString());
|
||||
r.createCell(ci++).setCellValue(cst.getCountryRegionCity() == null ? "" : cst.getCountryRegionCity());
|
||||
r.createCell(ci++).setCellValue(cst.getPhoneNumber() == null ? "" : cst.getPhoneNumber());
|
||||
r.createCell(ci++).setCellValue(cst.getDesignTitle() == null ? "" : cst.getDesignTitle());
|
||||
r.createCell(ci++).setCellValue(cst.getDesignDescription() == null ? "" : cst.getDesignDescription());
|
||||
r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath());
|
||||
r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath());
|
||||
r.createCell(ci++).setCellValue(cst.getVideoDuration() == null ? "" : cst.getVideoDuration().toString());
|
||||
if (cst.getVideoSize() == null) {
|
||||
r.createCell(ci++).setCellValue("");
|
||||
} else {
|
||||
double vMb = cst.getVideoSize() / 1024.0 / 1024.0;
|
||||
r.createCell(ci++).setCellValue(String.format("%.2f", vMb));
|
||||
}
|
||||
if (cst.getPdfSize() == null) {
|
||||
r.createCell(ci++).setCellValue("");
|
||||
} else {
|
||||
double pMb = cst.getPdfSize() / 1024.0 / 1024.0;
|
||||
r.createCell(ci++).setCellValue(String.format("%.2f", pMb));
|
||||
}
|
||||
r.createCell(ci++).setCellValue(cst.getPortfolioUrl() == null ? "" : cst.getPortfolioUrl());
|
||||
r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString());
|
||||
r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString());
|
||||
}
|
||||
|
||||
workbook.write(out);
|
||||
out.flush();
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
log.error("export contestants failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveContestantsToLocal() throws Exception {
|
||||
List<Contestant> list = contestantMapper.selectList(new QueryWrapper<>());
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||
String ts = LocalDateTime.now().format(fmt);
|
||||
Path exportDir = Paths.get(uploadDir == null ? "uploads" : uploadDir, "exports");
|
||||
Files.createDirectories(exportDir);
|
||||
Path outPath = exportDir.resolve("contestants_" + ts + ".xlsx");
|
||||
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream(outPath.toFile())) {
|
||||
Sheet sheet = workbook.createSheet("contestants");
|
||||
int rowIdx = 0;
|
||||
Row header = sheet.createRow(rowIdx++);
|
||||
String[] headers = new String[] {
|
||||
"contestantNumber", "email", "firstName", "lastName", "gender", "occupation",
|
||||
"age", "countryRegionCity", "phoneNumber", "designTitle", "designDescription",
|
||||
"pdfPath", "videoPath", "videoDuration", "videoSizeMB", "pdfSizeMB", "portfolioUrl", "createdAt", "updatedAt"
|
||||
};
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
Cell c = header.createCell(i);
|
||||
c.setCellValue(headers[i]);
|
||||
}
|
||||
|
||||
for (Contestant cst : list) {
|
||||
Row r = sheet.createRow(rowIdx++);
|
||||
int ci = 0;
|
||||
r.createCell(ci++).setCellValue(cst.getContestantNumber() == null ? "" : cst.getContestantNumber().toString());
|
||||
r.createCell(ci++).setCellValue(cst.getEmail() == null ? "" : cst.getEmail());
|
||||
r.createCell(ci++).setCellValue(cst.getFirstName() == null ? "" : cst.getFirstName());
|
||||
r.createCell(ci++).setCellValue(cst.getLastName() == null ? "" : cst.getLastName());
|
||||
r.createCell(ci++).setCellValue(cst.getGender() == null ? "" : cst.getGender());
|
||||
r.createCell(ci++).setCellValue(cst.getOccupation() == null ? "" : cst.getOccupation());
|
||||
r.createCell(ci++).setCellValue(cst.getAge() == null ? "" : cst.getAge().toString());
|
||||
r.createCell(ci++).setCellValue(cst.getCountryRegionCity() == null ? "" : cst.getCountryRegionCity());
|
||||
r.createCell(ci++).setCellValue(cst.getPhoneNumber() == null ? "" : cst.getPhoneNumber());
|
||||
r.createCell(ci++).setCellValue(cst.getDesignTitle() == null ? "" : cst.getDesignTitle());
|
||||
r.createCell(ci++).setCellValue(cst.getDesignDescription() == null ? "" : cst.getDesignDescription());
|
||||
r.createCell(ci++).setCellValue(cst.getPdfPath() == null ? "" : cst.getPdfPath());
|
||||
r.createCell(ci++).setCellValue(cst.getVideoPath() == null ? "" : cst.getVideoPath());
|
||||
r.createCell(ci++).setCellValue(cst.getVideoDuration() == null ? "" : cst.getVideoDuration().toString());
|
||||
if (cst.getVideoSize() == null) {
|
||||
r.createCell(ci++).setCellValue("");
|
||||
} else {
|
||||
double vMb = cst.getVideoSize() / 1024.0 / 1024.0;
|
||||
r.createCell(ci++).setCellValue(String.format("%.2f", vMb));
|
||||
}
|
||||
if (cst.getPdfSize() == null) {
|
||||
r.createCell(ci++).setCellValue("");
|
||||
} else {
|
||||
double pMb = cst.getPdfSize() / 1024.0 / 1024.0;
|
||||
r.createCell(ci++).setCellValue(String.format("%.2f", pMb));
|
||||
}
|
||||
r.createCell(ci++).setCellValue(cst.getPortfolioUrl() == null ? "" : cst.getPortfolioUrl());
|
||||
r.createCell(ci++).setCellValue(cst.getCreatedAt() == null ? "" : cst.getCreatedAt().toString());
|
||||
r.createCell(ci++).setCellValue(cst.getUpdatedAt() == null ? "" : cst.getUpdatedAt().toString());
|
||||
}
|
||||
|
||||
workbook.write(fos);
|
||||
fos.flush();
|
||||
log.info("Exported contestants to local file: {}", outPath.toString());
|
||||
} catch (IOException e) {
|
||||
log.error("save contestants to local failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContestantDTO getContestantByID(String id) {
|
||||
if (id == null) {
|
||||
throw new BusinessException("id is required.");
|
||||
}
|
||||
Contestant existing = contestantMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
return null;
|
||||
}
|
||||
ContestantDTO dto = new ContestantDTO();
|
||||
// dto.setEmail(existing.getEmail());
|
||||
dto.setFirstName(existing.getFirstName());
|
||||
dto.setLastName(existing.getLastName());
|
||||
dto.setGender(existing.getGender());
|
||||
dto.setOccupation(existing.getOccupation());
|
||||
dto.setAge(existing.getAge());
|
||||
dto.setCountryRegionCity(existing.getCountryRegionCity());
|
||||
dto.setPhoneNumber(existing.getPhoneNumber());
|
||||
dto.setDesignTitle(existing.getDesignTitle());
|
||||
dto.setDesignDescription(existing.getDesignDescription());
|
||||
dto.setPdfPath(existing.getPdfPath());
|
||||
dto.setVideoPath(existing.getVideoPath());
|
||||
dto.setVideoDuration(existing.getVideoDuration());
|
||||
dto.setPdfSize(existing.getPdfSize());
|
||||
dto.setVideoSize(existing.getVideoSize());
|
||||
dto.setPortfolioUrl(existing.getPortfolioUrl());
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查邮箱是否符合申请要求,发送验证码
|
||||
* @param email AiDA邮箱
|
||||
*/
|
||||
public void checkEmail(String email) {
|
||||
List<Integer> validRole = Arrays.asList(1, 2, 7, 8);
|
||||
// 1. 验证邮箱在aida中有无账号
|
||||
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda().eq(Account::getUserEmail, email);
|
||||
List<Account> accounts = accountMapper.selectList(queryWrapper);
|
||||
if (accounts.isEmpty()) {
|
||||
throw new BusinessException("Please register and subscribe to AiDA, then resubmit your application.");
|
||||
}
|
||||
|
||||
// 2. 验证账号是否是付费用户(如果首次提交是,但是修改的时候已经不是了,how?不允许修改吗)
|
||||
if (validRole.contains(accounts.getFirst().getSystemUser())) {
|
||||
String randomVerifyCode = RandomsUtil.generateVerifyCode(100000L, 999999L);
|
||||
LocalCacheUtils.setVerifyCodeCache(
|
||||
AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + "_" + email, randomVerifyCode);
|
||||
SendEmailUtil.send(email, null,
|
||||
SendEmailUtil.LOGIN_TEMPLATE_ID, randomVerifyCode);
|
||||
} else {
|
||||
throw new BusinessException("Please subscribe to AiDA, then resubmit your application.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证验证码是否正确
|
||||
* @param email 邮箱
|
||||
* @param otp 一次性验证码
|
||||
* @return 临时token和之前提交的表单内容
|
||||
*/
|
||||
public CheckOTPVO checkCode(String email, String otp) {
|
||||
String otpCache = LocalCacheUtils.getVerifyCodeCache(AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + "_" + email);
|
||||
assert otpCache != null;
|
||||
if (otpCache.equals(otp)) {
|
||||
// 1. 生成唯一token
|
||||
String secureToken = UUID.randomUUID().toString().replace("-", "");
|
||||
redisUtil.addToString(tokenCacheKey + email, secureToken, 3 * 24 * 60 * 60L);
|
||||
|
||||
return new CheckOTPVO(secureToken, getContestantByID(email));
|
||||
} else {
|
||||
throw new BusinessException("Verification code is incorrect. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkSecurityToken(String email, String securityToken) {
|
||||
String key = tokenCacheKey + email;
|
||||
if (StringUtils.isBlank(securityToken)) {
|
||||
log.error("security token 缺失");
|
||||
throw new BusinessException("Please complete email verification first.");
|
||||
}
|
||||
|
||||
String tokenCache = redisUtil.getFromString(key);
|
||||
if (StringUtils.isBlank(tokenCache)) {
|
||||
log.error("security token 过期");
|
||||
throw new BusinessException("Email verification has expired. Please verify again.");
|
||||
} else if (!tokenCache.equals(securityToken)){
|
||||
log.error("security token 与缓存不符");
|
||||
throw new BusinessException("Identity verification failed. Please complete email verification first.");
|
||||
}
|
||||
}
|
||||
|
||||
// 发送站内信
|
||||
public void sendSiteMsg(String applicationId, String email) {
|
||||
Long userId;
|
||||
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda().eq(Account::getUserEmail, email);
|
||||
List<Account> accounts = accountMapper.selectList(queryWrapper);
|
||||
if (accounts.isEmpty()) {
|
||||
throw new BusinessException("Please register and subscribe to AiDA, then resubmit your application.");
|
||||
}else {
|
||||
userId = accounts.get(0).getId();
|
||||
}
|
||||
PublishSysNotificationDTO sysNotificationDTO = new PublishSysNotificationDTO();
|
||||
Notification notification = new Notification();
|
||||
notification.setType("system");
|
||||
notification.setReceiverId(userId);
|
||||
sysNotificationDTO.setTitle("System Notification 系统通知");
|
||||
// todo
|
||||
sysNotificationDTO.setContent(link + applicationId);
|
||||
notification.setContent(JSON.toJSONString(sysNotificationDTO));
|
||||
notification.setIsRead(0);
|
||||
notification.setCreateTime(LocalDateTime.now());
|
||||
notificationMapper.insert(notification);
|
||||
// 这里推送消息是在接受到视频生成结束后发生的,所以UserContext中没有用户信息
|
||||
messageCenterService.pushMessage("system", userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportContestantFilesAsZip(Integer minContestantNumber, Integer maxContestantNumber) throws Exception {
|
||||
if (minContestantNumber == null || maxContestantNumber == null) {
|
||||
throw new BusinessException("minContestantNumber and maxContestantNumber are required.");
|
||||
}
|
||||
if (minContestantNumber > maxContestantNumber) {
|
||||
throw new BusinessException("minContestantNumber cannot be greater than maxContestantNumber.");
|
||||
}
|
||||
|
||||
// 1. 根据 contestantNumber 范围查询参赛者
|
||||
QueryWrapper<Contestant> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda()
|
||||
.ge(Contestant::getContestantNumber, minContestantNumber)
|
||||
.le(Contestant::getContestantNumber, maxContestantNumber)
|
||||
.orderByAsc(Contestant::getContestantNumber);
|
||||
List<Contestant> contestants = contestantMapper.selectList(queryWrapper);
|
||||
|
||||
if (contestants.isEmpty()) {
|
||||
log.info("No contestants found in range [{}, {}]", minContestantNumber, maxContestantNumber);
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
// 2. 在内存中构建 ZIP
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(baos)) {
|
||||
|
||||
for (Contestant contestant : contestants) {
|
||||
Integer contestantNumber = contestant.getContestantNumber();
|
||||
if (contestantNumber == null) {
|
||||
log.warn("Contestant {} has no contestantNumber, skipping", contestant.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
String dirPrefix = contestantNumber + "/";
|
||||
|
||||
// 添加 PDF 文件
|
||||
String pdfPath = contestant.getPdfPath();
|
||||
if (StringUtils.isNotBlank(pdfPath)) {
|
||||
addMinioFileToZip(zos, pdfPath, dirPrefix + "design.pdf");
|
||||
}
|
||||
|
||||
// 添加视频文件
|
||||
String videoPath = contestant.getVideoPath();
|
||||
if (StringUtils.isNotBlank(videoPath)) {
|
||||
String fileName = videoPath.contains("/") ?
|
||||
videoPath.substring(videoPath.lastIndexOf("/") + 1) : "video.mp4";
|
||||
addMinioFileToZip(zos, videoPath, dirPrefix + fileName);
|
||||
}
|
||||
|
||||
// 添加参赛者信息 txt 文件
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("=== Contestant Information ===\n\n");
|
||||
sb.append("ID: ").append(nullSafe(contestant.getId())).append("\n");
|
||||
sb.append("Email: ").append(nullSafe(contestant.getEmail())).append("\n");
|
||||
sb.append("Contestant Number: ").append(contestantNumber).append("\n");
|
||||
sb.append("First Name: ").append(nullSafe(contestant.getFirstName())).append("\n");
|
||||
sb.append("Last Name: ").append(nullSafe(contestant.getLastName())).append("\n");
|
||||
sb.append("Gender: ").append(nullSafe(contestant.getGender())).append("\n");
|
||||
sb.append("Occupation: ").append(nullSafe(contestant.getOccupation())).append("\n");
|
||||
sb.append("Age: ").append(contestant.getAge() != null ? contestant.getAge() : "N/A").append("\n");
|
||||
sb.append("Country/Region/City: ").append(nullSafe(contestant.getCountryRegionCity())).append("\n");
|
||||
sb.append("Phone Number: ").append(nullSafe(contestant.getPhoneNumber())).append("\n");
|
||||
sb.append("Design Title: ").append(nullSafe(contestant.getDesignTitle())).append("\n");
|
||||
sb.append("Design Description: ").append(nullSafe(contestant.getDesignDescription())).append("\n");
|
||||
sb.append("PDF Path: ").append(nullSafe(pdfPath)).append("\n");
|
||||
sb.append("PDF Size (bytes): ").append(contestant.getPdfSize() != null ? contestant.getPdfSize() : "N/A").append("\n");
|
||||
sb.append("Video Path: ").append(nullSafe(videoPath)).append("\n");
|
||||
sb.append("Video Duration (seconds): ").append(contestant.getVideoDuration() != null ? contestant.getVideoDuration() : "N/A").append("\n");
|
||||
sb.append("Video Size (bytes): ").append(contestant.getVideoSize() != null ? contestant.getVideoSize() : "N/A").append("\n");
|
||||
sb.append("Portfolio URL: ").append(nullSafe(contestant.getPortfolioUrl())).append("\n");
|
||||
sb.append("Created At: ").append(contestant.getCreatedAt() != null ? contestant.getCreatedAt() : "N/A").append("\n");
|
||||
sb.append("Updated At: ").append(contestant.getUpdatedAt() != null ? contestant.getUpdatedAt() : "N/A").append("\n");
|
||||
|
||||
ZipEntry infoEntry = new ZipEntry(dirPrefix + "contestant_info.txt");
|
||||
zos.putNextEntry(infoEntry);
|
||||
zos.write(sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||
zos.closeEntry();
|
||||
log.info("Added contestant {} info to zip", contestantNumber);
|
||||
}
|
||||
|
||||
zos.finish();
|
||||
log.info("ZIP built for {} contestants, size: {} bytes", contestants.size(), baos.size());
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 MinIO 文件流式写入 ZIP,不落盘
|
||||
* @param zos ZIP 输出流
|
||||
* @param minioPath MinIO 路径(格式: bucketName/objectPath)
|
||||
* @param entryName ZIP 条目名称
|
||||
*/
|
||||
private void addMinioFileToZip(java.util.zip.ZipOutputStream zos, String minioPath, String entryName) {
|
||||
if (StringUtils.isBlank(minioPath)) {
|
||||
return;
|
||||
}
|
||||
int index = minioPath.indexOf("/");
|
||||
if (index == -1) {
|
||||
log.warn("Invalid MinIO path: {}", minioPath);
|
||||
return;
|
||||
}
|
||||
String bucketName = minioPath.substring(0, index);
|
||||
String objectName = minioPath.substring(index + 1);
|
||||
|
||||
try (InputStream in = minioUtil.download(bucketName, objectName)) {
|
||||
ZipEntry entry = new ZipEntry(entryName);
|
||||
zos.putNextEntry(entry);
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
zos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
zos.closeEntry();
|
||||
log.info("Added {} to zip ({} bytes)", entryName, entry.getSize());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to add {} to zip: {}", entryName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContestantCountVO getContestantCount() {
|
||||
long count = contestantMapper.selectCount(null);
|
||||
Integer maxContestantNumber = null;
|
||||
QueryWrapper<Contestant> qMax = new QueryWrapper<>();
|
||||
qMax.isNotNull("contestant_number");
|
||||
qMax.orderByDesc("contestant_number");
|
||||
qMax.last("LIMIT 1");
|
||||
Contestant last = contestantMapper.selectOne(qMax);
|
||||
if (last != null) {
|
||||
maxContestantNumber = last.getContestantNumber();
|
||||
}
|
||||
return ContestantCountVO.builder()
|
||||
.count(count)
|
||||
.maxContestantNumber(maxContestantNumber)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String nullSafe(String value) {
|
||||
return value != null ? value : "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user