fix: CORS 配置简化并启用 Spring Security CORS 支持;LeaiSync 作品同步封面图逻辑修复

- CorsConfig 改回允许所有来源(*),移除配置文件域名列表注入
- SecurityConfig 添加 .cors(Customizer.withDefaults()) 使 CorsFilter Bean 生效
- LeaiSyncService 修复 originalImageUrl 同时同步到 coverUrl,单独 coverUrl 优先覆盖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
En 2026-04-09 13:50:02 +08:00
parent f03991819d
commit 87ac3b5ed9
3 changed files with 34 additions and 19 deletions

View File

@ -1,41 +1,33 @@
package com.competition.common.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.List;
/**
* 跨域配置
* 从配置文件注入允许的域名列表避免使用通配符 * 导致的安全风险
* 允许所有来源访问方便前后端分离开发部署
*/
@Slf4j
@Configuration
public class CorsConfig {
@Value("${cors.allowed-origins:http://localhost:3000,http://localhost:5173}")
private List<String> allowedOrigins;
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 使用配置的域名列表替代通配符 *
for (String origin : allowedOrigins) {
config.addAllowedOriginPattern(origin.trim());
}
// 允许所有来源
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addExposedHeader("X-Trace-Id");
log.info("CORS 允许的域名:{}", allowedOrigins);
log.info("CORS 配置:允许所有来源(*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

View File

@ -116,16 +116,19 @@ public class LeaiSyncService implements ILeaiSyncService {
work.setCreateTime(LocalDateTime.now());
work.setModifyTime(LocalDateTime.now());
// 设置封面图
// 设置原始图片同时同步到封面图
Object originalImageUrl = remoteData.get("originalImageUrl");
if (originalImageUrl == null) originalImageUrl = remoteData.get("original_image_url");
if (originalImageUrl != null) {
work.setOriginalImageUrl(originalImageUrl.toString());
work.setCoverUrl(originalImageUrl.toString());
}
// 如果远程数据单独提供了封面图优先使用
Object coverUrl = remoteData.get("coverUrl");
if (coverUrl == null) coverUrl = remoteData.get("cover_url");
if (coverUrl != null) work.setCoverUrl(coverUrl.toString());
// 设置原始图片
Object originalImageUrl = remoteData.get("originalImageUrl");
if (originalImageUrl == null) originalImageUrl = remoteData.get("original_image_url");
if (originalImageUrl != null) work.setOriginalImageUrl(originalImageUrl.toString());
// 通过手机号查找用户ID多租户场景
if (phone != null && work.getUserId() == null) {
Long userId = findUserIdByPhone(phone);
@ -172,7 +175,15 @@ public class LeaiSyncService implements ILeaiSyncService {
if (remoteData.containsKey("progressMessage")) {
wrapper.set(UgcWork::getProgressMessage, LeaiUtil.toString(remoteData.get("progressMessage"), null));
}
// 同步封面图AI创作过程中可能推送 coverUrl
// 同步原始图片同时更新封面图
Object originalImageUrl = remoteData.get("originalImageUrl");
if (originalImageUrl == null) originalImageUrl = remoteData.get("original_image_url");
if (originalImageUrl != null) {
String imageUrl = originalImageUrl.toString();
wrapper.set(UgcWork::getOriginalImageUrl, imageUrl);
wrapper.set(UgcWork::getCoverUrl, imageUrl);
}
// 如果远程数据单独提供了封面图优先使用覆盖 originalImageUrl 的值
Object coverUrl = remoteData.get("coverUrl");
if (coverUrl == null) coverUrl = remoteData.get("cover_url");
if (coverUrl != null) {
@ -212,6 +223,15 @@ public class LeaiSyncService implements ILeaiSyncService {
if (remoteData.containsKey("failReason")) {
wrapper.set(UgcWork::getFailReason, LeaiUtil.toString(remoteData.get("failReason"), null));
}
// 同步原始图片同时更新封面图
Object originalImageUrl = remoteData.get("originalImageUrl");
if (originalImageUrl == null) originalImageUrl = remoteData.get("original_image_url");
if (originalImageUrl != null) {
String imageUrl = originalImageUrl.toString();
wrapper.set(UgcWork::getOriginalImageUrl, imageUrl);
wrapper.set(UgcWork::getCoverUrl, imageUrl);
}
// 如果远程数据单独提供了封面图优先使用覆盖 originalImageUrl 的值
Object coverUrl = remoteData.get("coverUrl");
if (coverUrl == null) coverUrl = remoteData.get("cover_url");
if (coverUrl != null) {

View File

@ -17,6 +17,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.Customizer;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
@ -38,6 +39,8 @@ public class SecurityConfig {
http
// 禁用 CSRF无状态 JWT 不需要
.csrf(AbstractHttpConfigurer::disable)
// 启用 CORS使用 CorsFilter Bean 的配置
.cors(Customizer.withDefaults())
// 无状态会话
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 异常处理