swagger 推广后台 优化对战百佳历史接口

This commit is contained in:
2026-09-24 14:55:58 +08:00
parent 4be9ce4e85
commit 43286f10ef
160 changed files with 1710 additions and 1719 deletions
@@ -5,14 +5,12 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
@@ -54,11 +52,6 @@ public class ResourcesConfig implements WebMvcConfigurer
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + RuoYiConfig.getProfile() + "/upload/");
/** swagger配置 */
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
}
/**
@@ -36,6 +36,7 @@ import java.util.function.Supplier;
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {
@Qualifier("apiUserDetailsServiceImpl")
@Autowired
private UserDetailsService apiUserDetailsService;
@@ -101,13 +102,21 @@ public class SecurityConfig {
// 静态资源
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html",
"/**/*.css", "/**/*.js", "/profile/**", "/uploads/**", "/upload/**").permitAll()
// Swagger & Druid
.requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// Druid 监控台:生产已在 application-pro.yml 关闭 statViewServlet(enabled=false)
// 并限制 allow=127.0.0.1 + 非默认口令;这里保留放行是为了本地开发能打开控制台
.requestMatchers("/druid/**").permitAll()
// WebSocket
.requestMatchers("/api/websocket/skins").permitAll()
.requestMatchers("/ws/**").permitAll()
// 后台接口:只认「后台 sys_user 账号」的 token(详见 checkAdminUser)
.requestMatchers("/admin/**").access(this::checkAdminUser)
// 后台接口(游戏管理后台 + 框架自带系统管理/监控/代码生成/测试接口):
// 只认「后台 sys_user 账号」的 token,推广 / 玩家 token 一律 403(详见 checkAdminUser)
// /common/** 是文件上传下载(只有后台 ruoyi-ui 在用),不放进三端前缀里会对所有端开放
.requestMatchers("/admin/**", "/system/**", "/monitor/**", "/tool/**", "/test/**", "/common/**")
.access(this::checkAdminUser)
// 推广接口:只认推广身份的 token,玩家(00)与后台账号一律 403(详见 checkPromoUser)
.requestMatchers("/promo/**").access(this::checkPromoUser)
// 玩家接口:只认玩家端身份的 token,推广超管与后台账号一律 403(详见 checkPlayerUser)
.requestMatchers("/api/**").access(this::checkPlayerUser)
// 其他所有请求都需要认证
.anyRequest().authenticated();
})
@@ -138,8 +147,8 @@ public class SecurityConfig {
* → LoginUser.userData 是 UserData,LoginUser.user 为 null → 拒绝(403)。
* 这样即使某个后台 Controller 忘了标 @PreAuthorize,也不会被玩家 token 调用。
*/
private AuthorizationDecision checkAdminUser(Supplier<? extends Authentication> authentication,
RequestAuthorizationContext context) {
AuthorizationDecision checkAdminUser(Supplier<? extends Authentication> authentication,
RequestAuthorizationContext context) {
Authentication auth = authentication.get();
if (auth == null || !auth.isAuthenticated()) {
return new AuthorizationDecision(false);
@@ -150,6 +159,48 @@ public class SecurityConfig {
}
/**
* /promo/** 的访问判定:只允许「推广后台签发」的 token(loginScope = promo)。
*
* 同一个 tt_user(主播 01 / 招商 04 / 卡商 05)既能登录玩家端也能登录推广后台,
* 两端拿到的 LoginUser(userId + userData)完全一样,光看 userType 区分不了,
* 所以这里必须按登录端标记卡:玩家端签发的 token 即使账号是主播,也进不来推广接口。
*/
AuthorizationDecision checkPromoUser(Supplier<? extends Authentication> authentication,
RequestAuthorizationContext context) {
LoginUser loginUser = loginUserOf(authentication);
if (loginUser == null || loginUser.getUserData() == null) {
return new AuthorizationDecision(false);
}
return new AuthorizationDecision(LoginUser.SCOPE_PROMO.equals(loginUser.getLoginScope()));
}
/**
* /api/** 的访问判定:只允许「玩家端签发」的 token(loginScope = player)。
*
* 推广后台 token(哪怕底层账号就是 tt_user)一律拒绝;后台账号(sys_user)没有 userData,也进不来。
*/
AuthorizationDecision checkPlayerUser(Supplier<? extends Authentication> authentication,
RequestAuthorizationContext context) {
LoginUser loginUser = loginUserOf(authentication);
if (loginUser == null || loginUser.getUserData() == null) {
return new AuthorizationDecision(false);
}
return new AuthorizationDecision(LoginUser.SCOPE_PLAYER.equals(loginUser.getLoginScope()));
}
/**
* 取出已认证的 LoginUser,其余情况(匿名 / 未认证 / 非本框架主体)返回 null
*/
private LoginUser loginUserOf(Supplier<? extends Authentication> authentication) {
Authentication auth = authentication.get();
if (auth == null || !auth.isAuthenticated()) {
return null;
}
return auth.getPrincipal() instanceof LoginUser loginUser ? loginUser : null;
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
@@ -61,6 +61,8 @@ public class UserDetailsServiceImpl implements UserDetailsService
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
loginUser.setLoginScope(LoginUser.SCOPE_ADMIN);
return loginUser;
}
}
@@ -45,6 +45,11 @@ public class WsTokenService {
if (loginUser == null) {
return null;
}
// 只认玩家端 token:推广/后台 token 即使 userId 撞上某个 tt_user 的 id 也不能连玩家 WS
// (与 SecurityConfig.checkPlayerUser 口径一致;本次改造前签发的老 token 无端标记,同样要重新登录)
if (!LoginUser.SCOPE_PLAYER.equals(loginUser.getLoginScope())) {
return null;
}
return loginUser.getUserId().equals(expectedUserId) ? uuid : null;
} catch (Exception e) {
log.warn("WS Token 校验异常, userId={}", expectedUserId, e);