5 Commits

Author SHA1 Message Date
litianxiang
23716984cc 微服务改造 2026-04-22 15:54:42 +08:00
litianxiang
d0b8b8d674 微服务改造 2026-04-22 11:16:03 +08:00
litianxiang
92e7dbf258 微服务改造 2026-04-22 11:15:34 +08:00
litianxiang
32a228485b token保持一致 2026-04-20 10:26:04 +08:00
litianxiang
560e47747a 微服务改造 2026-04-16 15:51:05 +08:00
51 changed files with 633 additions and 1629 deletions

59
pom.xml
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,8 +18,8 @@ public class ModelConstants {
// 模型名称常量
public static final String PRINTBOARD_ADVANCED_T2I = "qwen-image";
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 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-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";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,162 +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",
//GlobalAwardController
"/api/global-award"
);
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException {
String requestURI = httpServletRequest.getRequestURI();
if (calculateUrl(requestURI)/* || hasAuthorizationToken(httpServletRequest)*/) {
StopWatch stopWatch = new StopWatch();
HttpServletRequest wrappedRequest = httpServletRequest;
HttpServletResponse wrappedResponse = httpServletResponse;
try {
stopWatch.start();
if ((httpServletRequest.getContentType() == null && httpServletRequest.getContentLength() > 0) || (httpServletRequest.getContentType() != null && !httpServletRequest.getContentType().contains("application/json"))) {
extracted(wrappedRequest);
filterChain.doFilter(wrappedRequest, wrappedResponse);
} else {
wrappedRequest = new MultiReadHttpServletRequest(httpServletRequest);
wrappedResponse = new MultiReadHttpServletResponse(httpServletResponse);
extracted(wrappedRequest);
// excel导出使用原始response,不对响应做包装
if (requestURI.equals("/api/account/exportAccountsToExcel")) {
filterChain.doFilter(httpServletRequest, httpServletResponse); // 不包装
} else {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
throw e;
} finally {
stopWatch.stop();
}
} else {
//先清空当前线程变量,防止上一个线程遗留
UserContext.delete();
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
private Boolean calculateUrl(String requestURI) {
String filterUrl = FILTER_URL.stream().filter(url -> requestURI.contains(url)).findFirst().orElse(null);
return null == filterUrl ? Boolean.TRUE : Boolean.FALSE;
}
private boolean hasAuthorizationToken(HttpServletRequest request) {
String authorizationHeader = request.getHeader("Authorization");
return authorizationHeader != null && authorizationHeader.startsWith("Bearer");
}
private void extracted(HttpServletRequest request) {
String jwtToken = request.getHeader(properties.getJwtTokenHeader());
// log.debug("后台检查令牌:{}", jwtToken);
if (StrUtil.isBlank(jwtToken)) {
String ipAddress = RequestInfoUtil.getIpAddress(request);
log.info("本次请求的ip为 " + ipAddress);
// throw new RuntimeException("请传入token");
throw new TokenMissingOrExpiredException("请传入token");
}
if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
//写死 暂时放行
return;
}
// 检查token
boolean validate = jwtTokenHelper.validateToken(jwtToken);
if (validate) {
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(jwtToken);
if (principal == null) {
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(token without userInfo)");
}
//先清空当前线程变量,防止上一个线程遗留
UserContext.delete();
//存取用户信息到缓存
UserContext.setUserHolder(principal);
// 校验 token先查本地缓存再查 Redis保证服务重启后仍然有效
String userIdStr = String.valueOf(principal.getId());
String cacheToken = LocalCacheUtils.getTokenCache(userIdStr);
if (StringUtils.isEmpty(cacheToken)) {
// 本地缓存为空时,尝试从 Redis 读取
cacheToken = redisUtil.getLoginToken(principal.getId());
if (StringUtils.isEmpty(cacheToken)) {
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(cache & redis empty)");
}
// 将 Redis 中的 token 回填到本地缓存,减少后续 Redis 访问
LocalCacheUtils.setTokenCache(userIdStr, cacheToken);
}
if(!cacheToken.equals(jwtToken) ){
// throw new RuntimeException("TOKEN已过期请重新登录");
throw new TokenMissingOrExpiredException("TOKEN已过期请重新登录(token not match local cache)");
}
// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(null, null);
// SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ public class AccountTask {
accountService.refreshCreditsMonthly();
}
@Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void getPaidUser() {
// 获取code-create 表中 指定日期之后 订单状态为wc-processing的订单
accountService.extendValidityForCC();

View File

@@ -45,7 +45,7 @@ public class PaymentTask {
@Resource
private PayPalCheckoutService payPalCheckoutService;
@Scheduled(cron = "0/30 * * * * ?")
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirmForPaypal() throws SerializeException {
// log.info("PayPal orderConfirm 被执行......");
@@ -97,7 +97,7 @@ 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();
@@ -109,7 +109,7 @@ public class PaymentTask {
affiliateService.syncLinkViewCountToDB();
}
@Scheduled(cron = "0 0 8 28-31 * ?")
// @Scheduled(cron = "0 0 8 28-31 * ?")
public void commissionSummaryReminder(){
// 每个月末的最后一天的早上八点执行
LocalDate today = LocalDate.now();

View File

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

View File

@@ -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,7 +34,8 @@ public class MailUtil {
* 发送邮件 - 默认发件人
*
* @param basicEmailParamDTO 发送邮件所需参数
* @param inputStreamSource 附件(如果有)
* @param fileName 附件(如果有)
* @param inputStreamSource 附件(如果有)
*/
public int sendMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {
MimeMessage mimeMessage = createSimpleMail(basicEmailParamDTO, fileName, inputStreamSource);
@@ -62,36 +62,29 @@ 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;
}
return 0;
}
/**
* 创建一封邮件
*
* @param basicEmailParamDTO 创建邮件需要的参数
* @param inputStreamSource 附件(如果有)
* @param inputStreamSource 附件(如果有)
* @return 一封邮件
*/
private MimeMessage createSimpleMail(BasicEmailParamDTO basicEmailParamDTO, String fileName, InputStreamSource inputStreamSource) throws MessagingException {

View File

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

View File

@@ -767,7 +767,7 @@ public class SendEmailUtil {
try {
String merchantEmail = "kimwong@code-create.com.hk";
String developer = "xupei3360@163.com";
String[] receiverEmail = {merchantEmail, developer};
String[] receiverEmail = {/*merchantEmail,*/ developer};
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
@@ -968,7 +968,7 @@ public class SendEmailUtil {
req.setFromEmailAddress(SEND_ADDRESS);
String merchantEmail = "kimwong@code-create.com.hk";
String developerEmail = "xupei@code-create.com.hk";
req.setDestination(new String[]{merchantEmail, developerEmail});
req.setDestination(new String[]{/*merchantEmail,*/ developerEmail});
Template template = new Template();
req.setSubject("New Credit Purchase Order");
template.setTemplateID(CREDITS_PURCHASE_MERCHANT);

View File

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

View File

@@ -5,7 +5,6 @@ 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.model.vo.PageVisitCountVO;
import com.ai.da.service.GlobalAwardService;
import com.ai.da.service.upload.UploadService;
import com.ai.da.service.upload.UploadTask;
@@ -199,19 +198,6 @@ public class GlobalAwardController {
return Response.success(globalAwardService.getContestantCount());
}
@PostMapping("/page/visit")
@ApiOperation(value = "记录比赛页面访问量", notes = "记录比赛页面的访问量,包含两种统计方式:每次访问/刷新计一次以及5秒内刷新只计一次")
public Response<Void> recordPageVisit(@ApiParam(value = "会话ID用于5秒内去重判断", required = false) @RequestParam(value = "sessionId", required = false) String sessionId) {
globalAwardService.recordPageVisit(sessionId);
return Response.success();
}
@GetMapping("/page/visit/count")
@ApiOperation(value = "获取比赛页面访问量", notes = "获取比赛页面的两种访问量rawVisitCount每次访问/刷新计一次)和 uniqueVisitCount5秒内刷新只计一次")
public Response<PageVisitCountVO> getPageVisitCount() {
return Response.success(globalAwardService.getPageVisitCount());
}
}

View File

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

View File

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

View File

@@ -1,23 +0,0 @@
package com.ai.da.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageVisitCountVO {
/**
* 每次访问或刷新都计一次(不去重)
*/
private Long rawVisitCount;
/**
* 5秒内刷新只算一次去重后的真实访客数
*/
private Long uniqueVisitCount;
}

View File

@@ -34,8 +34,4 @@ public class QueryUserConditionsVO extends PageQueryBaseVo {
private Integer systemUser;
private Long subscriptionPlanId;
private Long organizationId;
}

View File

@@ -2912,71 +2912,72 @@ public class PythonService {
private PrintToPython resolveDesignSinglePrint(List<DesignSinglePrint> printObject, String partialDesign) {
PrintToPython printToPython = new PrintToPython();
// DesignPythonItemPrint printSingle = new DesignPythonItemPrint();
DesignPythonItemPrint printOverall = new DesignPythonItemPrint();
// printToPython.setSingle(printSingle);
printToPython.setOverall(printOverall);
printToPython.setPartial(StringUtil.isNullOrEmpty(partialDesign) ? null : partialDesign);
if (Objects.isNull(printObject) || printObject.isEmpty()) {
return printToPython;
}
// 1. 先对 printObject 按 priority 排序(升序)
List<DesignSinglePrint> sortedPrints = printObject.stream()
.sorted(Comparator.comparingInt(DesignSinglePrint::getPriority))
.toList();
// 没有印花时的参数设置
// if (printObject.getIfSingle().equals(Boolean.FALSE) && CollectionUtil.isEmpty(printObject.getPrints())) {
// return new DesignPythonItemPrint(new ArrayList<>(), false);
// }
// DesignPythonItemPrint print = CopyUtil.copyObject(printObject, DesignPythonItemPrint.class);
// 2. 分别收集单印和非单印的数据
List<DesignSinglePrint> singlePrints = sortedPrints.stream()
.filter(DesignSinglePrint::getIfSingle)
.toList();
int size = printObject.size();
// 占位符填充数组
List<List<Float>> locationS = new ArrayList<>(Collections.nCopies(size, null));
List<List<Float>> scaleS = new ArrayList<>(Collections.nCopies(size, null));
List<Double> angleS = new ArrayList<>(Collections.nCopies(size, null));
ArrayList<String> pathsS = new ArrayList<>(Collections.nCopies(size, null));
List<DesignSinglePrint> overallPrints = sortedPrints.stream()
.filter(p -> !p.getIfSingle())
.toList();
List<List<Float>> locationO = new ArrayList<>(Collections.nCopies(size, null));
List<List<Float>> scaleO = new ArrayList<>(Collections.nCopies(size, null));
List<Double> angleO = new ArrayList<>(Collections.nCopies(size, null));
ArrayList<String> pathsO = new ArrayList<>(Collections.nCopies(size, null));
// 3. 处理单印数据
if (!singlePrints.isEmpty()) {
List<List<Float>> locationS = new ArrayList<>();
List<List<Float>> scaleS = new ArrayList<>();
List<Double> angleS = new ArrayList<>();
List<String> pathsS = new ArrayList<>();
for (DesignSinglePrint p : singlePrints) {
setUriToMinioPath(p);
locationS.add(p.getLocation());
scaleS.add(p.getScale());
angleS.add(p.getAngle());
pathsS.add(p.getMinIOPath());
// 设置印花的位置、大小、旋转角度
// 优先级越大越靠近顶层在传输给python的数组中越靠前
// List<DesignSinglePrint> prints = printObject.getPrints();
printObject.forEach(p -> {
p.getLocation().set(0, p.getLocation().get(0));
p.getLocation().set(1, p.getLocation().get(1));
Integer priority = p.getPriority();
setUriToMinioPath(p);
// todo 下标越界问题
if (p.getIfSingle()) {
locationS.set(priority - 1, p.getLocation());
scaleS.set(priority - 1, p.getScale());
angleS.set(priority - 1, p.getAngle());
pathsS.set(priority - 1, p.getMinIOPath());
} else {
locationO.set(priority - 1, p.getLocation());
scaleO.set(priority - 1, p.getScale());
angleO.set(priority - 1, p.getAngle());
pathsO.set(priority - 1, p.getMinIOPath());
}
// log.info("本次print打点locations###{}###fileVO{}", p.getLocation(), JSON.toJSONString(fileVO));
});
/*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);*/
// 注意:如果 printOverall 中需要设置单印数据,请在这里添加相应的 setter
// 根据您的原始代码,似乎只设置了 overall非单印的数据
// 如果需要设置单印,请取消下面的注释并添加对应的字段
// printOverall.setSingleLocation(locationS);
// printOverall.setSingleScale(scaleS);
// printOverall.setSingleAngle(angleS);
// printOverall.setSinglePath(pathsS);
}
// 4. 处理非单印数据(整体印花)
if (!overallPrints.isEmpty()) {
List<List<Float>> locationO = new ArrayList<>();
List<List<Float>> scaleO = new ArrayList<>();
List<Double> angleO = new ArrayList<>();
List<String> pathsO = new ArrayList<>();
for (DesignSinglePrint p : overallPrints) {
setUriToMinioPath(p);
locationO.add(p.getLocation());
scaleO.add(p.getScale());
angleO.add(p.getAngle());
pathsO.add(p.getMinIOPath());
}
printOverall.setLocation(locationO);
printOverall.setPrint_scale_list(scaleO);
printOverall.setPrint_angle_list(angleO);
printOverall.setPrint_path_list(pathsO);
}
locationO.removeAll(Collections.singleton(null));
scaleO.removeAll(Collections.singleton(null));
angleO.removeAll(Collections.singleton(null));
pathsO.removeAll(Collections.singleton(null));
printOverall.setLocation(locationO);
printOverall.setPrint_scale_list(scaleO);
printOverall.setPrint_angle_list(angleO);
printOverall.setPrint_path_list(pathsO);
return printToPython;
}

View File

@@ -3,7 +3,6 @@ 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 com.ai.da.model.vo.PageVisitCountVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
@@ -47,22 +46,6 @@ public interface GlobalAwardService {
* @return 参赛者数量和最大参赛者编号
*/
ContestantCountVO getContestantCount();
/**
* 记录比赛页面的访问量
* <ul>
* <li>rawVisitCount: 每次访问或刷新都计一次(不去重)</li>
* <li>uniqueVisitCount: 5秒内刷新只算一次基于会话去重</li>
* </ul>
* @param sessionId 会话ID用于5秒去重判断
*/
void recordPageVisit(String sessionId);
/**
* 获取比赛页面的两种访问量
* @return 原始访问量和去重访问量
*/
PageVisitCountVO getPageVisitCount();
}

View File

@@ -10,7 +10,9 @@ import com.ai.da.common.enums.LoginTypeEnum;
import com.ai.da.common.enums.ProductEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.security.jwt.JWTTokenHelper;
import com.ai.da.common.utils.TokenGenerateUtils;
import com.ai.da.feign.gateway.GatewayFeignClient;
import com.ai.da.feign.seller.SellerFeignClient;
import com.ai.da.common.utils.*;
import com.ai.da.mapper.primary.*;
import com.ai.da.mapper.primary.entity.*;
@@ -94,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;
@@ -136,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;
@@ -353,11 +358,11 @@ 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);
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;
}
@@ -614,11 +619,23 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Override
public Boolean logout(AccountLogoutDTO accountLogoutDTO) {
// jwt 本身失效比较难做,统一用缓存实现:删除缓存即失效
// 1. 删除本地缓存(保留,防止 Gateway 未启动时还能本地验证)
String userIdStr = String.valueOf(accountLogoutDTO.getUserId());
LocalCacheUtils.delTokenCache(userIdStr);
// 同时删除 Redis 中的 token,防止服务重启后仍然有效
// 2. 删除 Redis 中的 tokenGateway 黑名单会接力生效)
redisUtil.deleteLoginToken(accountLogoutDTO.getUserId());
// 3. 调用 Gateway 黑名单接口,将 token 加入 Redis 黑名单
try {
gatewayFeignClient.logout(accountLogoutDTO.getUserId());
} catch (Exception e) {
log.warn("调用 Gateway 黑名单接口失败userId={}, error={}", accountLogoutDTO.getUserId(), e.getMessage());
}
// 4. 同步调用 seller 清除本地缓存(兼容未接入 Gateway 的节点)
try {
sellerFeignClient.clearTokenCache(accountLogoutDTO.getUserId());
} catch (Exception e) {
log.warn("调用 seller 清理缓存失败userId={}, error={}", accountLogoutDTO.getUserId(), e.getMessage());
}
return Boolean.TRUE;
}
@@ -2153,7 +2170,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;
// 向新邮箱发送邮件,邮件附带激活链接,点击链接进行验证
@@ -2163,7 +2180,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);

View File

@@ -82,7 +82,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
// 邮件通知审批者
String merchantEmail = "kimwong@code-create.com.hk";
String developer = "xupei3360@163.com";
String[] receiverEmail = {merchantEmail, developer};
String[] receiverEmail = {/*merchantEmail,*/ developer};
SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
}else {
@@ -442,7 +442,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
String merchantEmail = "kimwong@code-create.com.hk";
String developer = "xupei3360@163.com";
String[] receiverEmail = {merchantEmail, developer};
String[] receiverEmail = {/*merchantEmail,*/ developer};
// 邮件通知
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), affiliateEmailParamsDTO, "summary");

View File

@@ -787,14 +787,6 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl<QuestionnaireMappe
queryWrapper.lt("create_date", queryUserConditionsVO.getEndTime());
}
if (!Objects.isNull(queryUserConditionsVO.getSubscriptionPlanId())) {
queryWrapper.eq("subscription_plan_id", queryUserConditionsVO.getSubscriptionPlanId());
}
if (!Objects.isNull(queryUserConditionsVO.getOrganizationId())) {
queryWrapper.eq("organization_id", queryUserConditionsVO.getOrganizationId());
}
// 排序
if (!StringUtils.isNullOrEmpty(queryUserConditionsVO.getOrder()) && !StringUtils.isNullOrEmpty(queryUserConditionsVO.getOrderBy())) {
String orderBy = "id";

View File

@@ -587,7 +587,7 @@ public class EmailServiceImpl implements EmailService {
try {
String merchantEmail = "kimwong@code-create.com.hk";
String developer = "xupei3360@163.com";
List<String> merchantReceiver = Arrays.asList(merchantEmail, developer);
List<String> merchantReceiver = Arrays.asList(/*merchantEmail,*/ developer);
String merchantSubject = null;
String merchantTemplate = null;
@@ -731,7 +731,7 @@ public class EmailServiceImpl implements EmailService {
jsonObject.put("quantity", quantity);
jsonObject.put("totalFee", amount);
sendEmail(Arrays.asList(merchantEmail, developerEmail), jsonObject, CREDITS_PURCHASE_MERCHANT, "New Credit Purchase Order", null, null);
sendEmail(Arrays.asList(/*merchantEmail,*/ developerEmail), jsonObject, CREDITS_PURCHASE_MERCHANT, "New Credit Purchase Order", null, null);
}
private final static String COMMON_EXCEPTION_REMINDER = "135279_common-exception-reminder.html";

View File

@@ -1553,11 +1553,11 @@ 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)) {
if (useModel.equals(ModelConstants.PRINTBOARD_HIGH_I2I)) {
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的常量做替代
//由于PRINTBOARD_HIGH_I2I与PRINTBOARD_ADVANCED_I2I使用模型一致为了区别积分扣除PRINTBOARD_HIGH_I2I加入了-fast但传入模型时需要去掉-fast用PRINTBOARD_ADVANCED_I2I的常量做替代
requestBuilder.model(ModelConstants.PRINTBOARD_ADVANCED_I2I);
}
@@ -3934,48 +3934,11 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
}
public byte[] downloadVideoOrImage(String url) {
int maxRetries = 3;
int retryDelayMs = 1000;
IOException lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return downloadWithTimeout(url, 30000, 60000);
} catch (IOException e) {
lastException = e;
log.warn("下载失败 (尝试 {}/{}): {}", attempt, maxRetries, e.getMessage());
if (attempt < maxRetries) {
try {
Thread.sleep((long) retryDelayMs * attempt); // 递增延迟
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
}
throw new RuntimeException("下载失败,已重试 " + maxRetries + "", lastException);
}
private byte[] downloadWithTimeout(String url, int connectTimeout, int socketTimeout) throws IOException {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(connectTimeout)
.setSocketTimeout(socketTimeout)
.setConnectionRequestTimeout(connectTimeout)
.build();
try (CloseableHttpClient client = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
CloseableHttpResponse response = client.execute(new HttpGet(url))) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200) {
throw new IOException("HTTP Error: " + statusCode);
}
return IOUtils.toByteArray(response.getEntity().getContent());
try (CloseableHttpClient client = HttpClients.createDefault();
InputStream in = client.execute(new HttpGet(url)).getEntity().getContent()) {
return IOUtils.toByteArray(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

View File

@@ -13,7 +13,6 @@ 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.model.vo.PageVisitCountVO;
import com.ai.da.service.GlobalAwardService;
import com.ai.da.service.MessageCenterService;
import com.alibaba.fastjson.JSON;
@@ -620,37 +619,6 @@ public class GlobalAwardServiceImpl implements GlobalAwardService {
private String nullSafe(String value) {
return value != null ? value : "N/A";
}
private static final String RAW_VISIT_COUNT_KEY = "GLOBAL_AWARD:visit:raw";
private static final String UNIQUE_VISIT_SET_KEY = "GLOBAL_AWARD:visit:unique";
private static final String SESSION_VISIT_KEY_PREFIX = "GLOBAL_AWARD:visit:session:";
private static final long SESSION_DEDUP_SECONDS = 5L;
@Override
public void recordPageVisit(String sessionId) {
redisUtil.increaseCount(RAW_VISIT_COUNT_KEY);
if (StringUtils.isNotBlank(sessionId)) {
String sessionKey = SESSION_VISIT_KEY_PREFIX + sessionId;
if (!redisUtil.hasKey(sessionKey)) {
redisUtil.increaseCount(UNIQUE_VISIT_SET_KEY);
redisUtil.addToString(sessionKey, "1", SESSION_DEDUP_SECONDS);
}
} else {
redisUtil.increaseCount(UNIQUE_VISIT_SET_KEY);
}
}
@Override
public PageVisitCountVO getPageVisitCount() {
Long raw = redisUtil.getIncrementCount(RAW_VISIT_COUNT_KEY);
Long unique = redisUtil.getIncrementCount(UNIQUE_VISIT_SET_KEY);
return PageVisitCountVO.builder()
.rawVisitCount(raw != null ? raw : 0L)
.uniqueVisitCount(unique != null ? unique : 0L)
.build();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
# ============================================================
# aida-back - Bootstrap
# 通过 NACOS_NAMESPACE 环境变量切换命名空间dev / test / prod
# 示例docker run -e NACOS_NAMESPACE=prod ...
# ============================================================
spring:
application:
name: aida-back
config:
import: optional:nacos:aida-public-${NACOS_NAMESPACE:test}.yml
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:test}
config:
server-addr: ${NACOS_HOST:127.0.0.1:8848}
namespace: ${NACOS_NAMESPACE:test}
group: ${NACOS_GROUP:DEFAULT_GROUP}
file-extension: yaml

View File

@@ -27,9 +27,9 @@ paypal.webhook_id=1D107312EX592781K
##### Stripe
# developer
#stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
# dev 端点
#stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
# local 端点
#stripe.webhook-sign-secret=whsec_TJcMSnAkh4uktrNY1M6Iy8XaVze4Rzqm
@@ -43,8 +43,8 @@ paypal.webhook_id=1D107312EX592781K
#stripe.webhook-sign-secret=whsec_pX0pPMQm85PaUSWnFMEzoccb3MGNkjoL
# kim - live
stripe.private-key=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
#stripe.private-key=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
# prod 端点
stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11
#stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11
# dev 端点
#stripe.webhook-sign-secret=whsec_cFUtjUOo8wnrIKZmt4GNvt7ZY1bOfrYr