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
-6
View File
@@ -29,12 +29,6 @@
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- spring-doc -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
@@ -4,8 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -33,7 +31,6 @@ import com.ruoyi.system.service.ISysDictTypeService;
*
* @author ruoyi
*/
@Api(tags = "管理端 字段值管理")
@RestController
@RequestMapping("/system/dict/data")
public class SysDictDataController extends BaseController
@@ -44,7 +41,6 @@ public class SysDictDataController extends BaseController
@Autowired
private ISysDictTypeService dictTypeService;
@ApiOperation("获取字典值")
@PreAuthorize("@ss.hasPermi('system:dict:list')")
@GetMapping("/list")
public TableDataInfo list(SysDictData dictData)
@@ -91,7 +87,6 @@ public class SysDictDataController extends BaseController
/**
* 新增字典类型
*/
@ApiOperation("新增字典值")
@PreAuthorize("@ss.hasPermi('system:dict:add')")
@Log(title = "字典数据", businessType = BusinessType.INSERT)
@PostMapping
@@ -104,7 +99,6 @@ public class SysDictDataController extends BaseController
/**
* 修改保存字典类型
*/
@ApiOperation("修改字典值")
@PreAuthorize("@ss.hasPermi('system:dict:edit')")
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
@PutMapping
@@ -3,7 +3,6 @@ package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.Set;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -24,7 +23,6 @@ import com.ruoyi.system.service.ISysMenuService;
*
* @author ruoyi
*/
@Api(tags = "管理端认证api")
@RestController
public class SysLoginController
{
@@ -11,11 +11,10 @@ import java.util.List;
import java.util.Map;
/**
* swagger 用户测试方法
* 用户测试方法(若依自带示例,未对外使用)
*
* @author ruoyi
*/
//@Api(tags = "用户信息管理")
@RestController
@RequestMapping("/test/user")
public class TestController extends BaseController
@@ -26,7 +25,6 @@ public class TestController extends BaseController
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
}
// @ApiOperation("获取用户列表")
@GetMapping("/list")
public R<List<UserEntity>> userList()
{
@@ -34,8 +32,6 @@ public class TestController extends BaseController
return R.ok(userList);
}
// @ApiOperation("获取用户详细")
// @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@GetMapping("/{userId}")
public R<UserEntity> getUser(@PathVariable Integer userId)
{
@@ -49,13 +45,6 @@ public class TestController extends BaseController
}
}
// @ApiOperation("新增用户")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
// @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
// @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
// @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
// })
@PostMapping("/save")
public R<String> save(UserEntity user)
{
@@ -67,7 +56,6 @@ public class TestController extends BaseController
return R.ok();
}
// @ApiOperation("更新用户")
@PutMapping("/update")
public R<String> update(@RequestBody UserEntity user)
{
@@ -84,8 +72,6 @@ public class TestController extends BaseController
return R.ok();
}
// @ApiOperation("删除用户信息")
// @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
@DeleteMapping("/{userId}")
public R<String> delete(@PathVariable Integer userId)
{
@@ -101,19 +87,14 @@ public class TestController extends BaseController
}
}
//@ApiModel(value = "UserEntity", description = "用户实体")
class UserEntity
{
// @ApiModelProperty("用户ID")
private Integer userId;
// @ApiModelProperty("用户名称")
private String username;
// @ApiModelProperty("用户密码")
private String password;
// @ApiModelProperty("用户手机")
private String mobile;
public UserEntity()
@@ -1,170 +0,0 @@
package com.ruoyi.web.core.config;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class SwaggerConfig {
/**
* 后台接口分组
*/
// @Bean
public Docket adminDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
// 扫描注解
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.admin.controller"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描所有.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.groupName("admin")
// 设置安全模式,访问token
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 用户业务接口分组
*/
@Bean
public Docket userDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.user.controller"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.groupName("user")
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 第三方业务接口分组
*/
@Bean
public Docket thirdPartyDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.thirdparty"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.groupName("thirdParty")
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 玩法业务接口分组
*/
@Bean
public Docket playingMethodDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.playingmethod.controller"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.groupName("playingMethod")
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 糖果玩法业务接口分组
*/
@Bean
public Docket gameSugarMethodDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.game.sugar.controller"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.groupName("gameSugarMethod")
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 任务业务接口分组
*/
@Bean
public Docket taskDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.task.controller"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.groupName("task")
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 推广后台接口分组
*/
@Bean
public Docket promoDocket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.promo.controller"))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.groupName("promo")
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 安全模式,这里指定token通过Authorization请求头传递
*/
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
return apiKeyList;
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
.build());
return securityContexts;
}
/**
* 默认的安全引用
*/
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
}
@@ -70,12 +70,12 @@ spring:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
# 白名单:只允许本机访问(原来留空 = 任意 IP 都能打开控制台)
allow: 127.0.0.1
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
# 控制台管理用户名和密码(河南单独一套,勿用常州/默认值)
login-username: druidhn
login-password: Hn@Druid#7t4Wq9
filter:
stat:
enabled: true
@@ -69,13 +69,15 @@ spring:
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
# 生产关闭 Druid 控制台:/druid/** 是匿名放行的,开着等于把全部 SQL 语句、URI 统计、连接信息挂到公网
# 需要临时排障时改成 true,并保持 allow 白名单 + 非默认口令,用完立刻关回
enabled: false
# 白名单:只允许本机访问(原来留空 = 任意 IP)
allow: 127.0.0.1
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
# 控制台管理用户名和密码(河南单独一套,临时开启时使用,勿改回默认值)
login-username: druidhn
login-password: Hn@Druid#7t4Wq9
filter:
stat:
enabled: true
@@ -0,0 +1,125 @@
package com.ruoyi.framework.config;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.entity.UserData;
import com.ruoyi.common.core.domain.model.LoginUser;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import java.util.Collections;
import java.util.function.Supplier;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 三端 token 隔离规则验证(不启动 Spring,直接测 SecurityConfig 里的三个判定方法):
* <ul>
* <li>后台面 /admin|/system|/monitor|/tool|/test → 只认后台 sys_user 账号签发的 token</li>
* <li>推广面 /promo/** → 只认推广后台签发的 token(loginScope = promo)</li>
* <li>玩家面 /api/** → 只认玩家端签发的 token(loginScope = player)</li>
* </ul>
* 关键点:主播(01)/招商(04)/卡商(05) 是同一张 tt_user 表、两端都能登录,LoginUser 长得一模一样,
* 只有登录端标记能区分,所以「主播的玩家端 token」不能进推广接口,「主播的推广 token」也不能进玩家接口。
*/
public class SecurityRulesTest {
private final SecurityConfig config = new SecurityConfig();
/** 后台账号登录(sys_user) */
private static LoginUser adminLogin() {
LoginUser loginUser = new LoginUser();
loginUser.setUser(new SysUser());
loginUser.setLoginScope(LoginUser.SCOPE_ADMIN);
return loginUser;
}
/** 推广后台登录(超管 99 / 主播 01 / 招商 04 / 卡商 05) */
private static LoginUser promoLogin(String userType) {
LoginUser loginUser = new LoginUser();
loginUser.setUserData(UserData.builder().userType(userType).build());
loginUser.setLoginScope(LoginUser.SCOPE_PROMO);
return loginUser;
}
/** 玩家端登录(通用用户 00 / 主播 01 / 招商 04 / 卡商 05) */
private static LoginUser playerLogin(String userType) {
LoginUser loginUser = new LoginUser();
loginUser.setUserData(UserData.builder().userType(userType).build());
loginUser.setLoginScope(LoginUser.SCOPE_PLAYER);
return loginUser;
}
/** 本次改造前签发的老 token:没有登录端标记 */
private static LoginUser legacyLogin(String userType) {
LoginUser loginUser = new LoginUser();
loginUser.setUserData(UserData.builder().userType(userType).build());
return loginUser;
}
/** 已认证的令牌:与 JwtAuthenticationTokenFilter 一致,用三参构造(两参构造是"未认证"状态) */
private static Supplier<Authentication> auth(LoginUser principal) {
return principal == null ? () -> null
: () -> new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList());
}
/** 未认证的令牌:两参构造,isAuthenticated() 为 false */
private static Supplier<Authentication> unauthenticated(LoginUser principal) {
return () -> new UsernamePasswordAuthenticationToken(principal, null);
}
private boolean adminAllowed(LoginUser principal) {
return config.checkAdminUser(auth(principal), null).isGranted();
}
private boolean promoAllowed(LoginUser principal) {
return config.checkPromoUser(auth(principal), null).isGranted();
}
private boolean playerAllowed(LoginUser principal) {
return config.checkPlayerUser(auth(principal), null).isGranted();
}
@Test
public void adminEndOnlyAcceptsBackendAccount() {
assertTrue(adminAllowed(adminLogin()), "后台账号应能访问后台接口");
assertFalse(adminAllowed(promoLogin("99")), "推广超管 token 不能访问后台接口");
assertFalse(adminAllowed(promoLogin("01")), "推广 token 不能访问后台接口");
assertFalse(adminAllowed(playerLogin("00")), "玩家 token 不能访问后台接口");
assertFalse(adminAllowed(playerLogin("01")), "主播的玩家端 token 不能访问后台接口");
assertFalse(adminAllowed(null), "未登录不能访问后台接口");
}
@Test
public void promoEndOnlyAcceptsPromoToken() {
for (String userType : new String[]{"99", "04", "01", "05"}) {
assertTrue(promoAllowed(promoLogin(userType)), "推广后台 token(" + userType + ")应能访问推广接口");
}
assertFalse(promoAllowed(playerLogin("01")), "主播的玩家端 token 不能访问推广接口");
assertFalse(promoAllowed(playerLogin("00")), "玩家 token 不能访问推广接口");
assertFalse(promoAllowed(adminLogin()), "后台 token 不能访问推广接口");
assertFalse(promoAllowed(legacyLogin("01")), "本次改造前的老 token 没有端标记,必须重新登录");
assertFalse(promoAllowed(null), "未登录不能访问推广接口");
}
@Test
public void playerEndOnlyAcceptsPlayerToken() {
for (String userType : new String[]{"00", "01", "04", "05"}) {
assertTrue(playerAllowed(playerLogin(userType)), "玩家端 token(" + userType + ")应能访问 /api/**");
}
assertFalse(playerAllowed(promoLogin("01")), "主播的推广 token 不能访问 /api/**");
assertFalse(playerAllowed(promoLogin("99")), "推广超管 token 不能访问 /api/**");
assertFalse(playerAllowed(adminLogin()), "后台 token 不能访问 /api/**");
assertFalse(playerAllowed(legacyLogin("00")), "本次改造前的老 token 没有端标记,必须重新登录");
assertFalse(playerAllowed(null), "未登录不能访问 /api/**");
}
@Test
public void unauthenticatedTokenIsDeniedEverywhere() {
Supplier<Authentication> notAuthenticated = unauthenticated(adminLogin());
assertFalse(config.checkAdminUser(notAuthenticated, null).isGranted());
assertFalse(config.checkPromoUser(notAuthenticated, null).isGranted());
assertFalse(config.checkPlayerUser(notAuthenticated, null).isGranted());
}
}