Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db517a2da8 | ||
|
|
5237dfc1cb | ||
|
|
981d8ef211 | ||
|
|
244c599a82 | ||
|
|
c822719f51 | ||
|
|
9753d7ebf5 | ||
|
|
5c5bc6419a | ||
|
|
47e9a65497 | ||
|
|
1a58ee63d2 | ||
|
|
0e7918b31e | ||
|
|
0e73bd4520 | ||
|
|
f66ff5c8f8 | ||
|
|
005c09c99c |
@@ -5,7 +5,7 @@ test.describe('认证和授权测试', () => {
|
||||
let userId: number;
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
const response = await request.post('http://localhost:8080/api/auth/login', {
|
||||
const response = await request.post('http://localhost:8080/api/admin/auth/login', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
@@ -28,7 +28,7 @@ test.describe('认证和授权测试', () => {
|
||||
});
|
||||
|
||||
await test.step('发送登录请求', async () => {
|
||||
const response = await page.request.post('http://localhost:8080/api/auth/login', {
|
||||
const response = await page.request.post('http://localhost:8080/api/admin/auth/login', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
@@ -78,7 +78,7 @@ test.describe('认证和授权测试', () => {
|
||||
});
|
||||
|
||||
await test.step('查询指定用户信息', async () => {
|
||||
const response = await page.request.get(`http://localhost:8080/api/users/${userId}`, {
|
||||
const response = await page.request.get(`http://localhost:8080/api/admin/users/${userId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
@@ -98,10 +98,10 @@ test.describe('认证和授权测试', () => {
|
||||
test('权限验证测试', async ({ page }) => {
|
||||
await test.step('测试访问受保护的API', async () => {
|
||||
const protectedEndpoints = [
|
||||
'/api/users',
|
||||
'/api/roles',
|
||||
'/api/menus',
|
||||
'/api/config'
|
||||
'/api/admin/users',
|
||||
'/api/admin/roles',
|
||||
'/api/admin/menus',
|
||||
'/api/admin/config'
|
||||
];
|
||||
|
||||
for (const endpoint of protectedEndpoints) {
|
||||
|
||||
@@ -4,7 +4,7 @@ test.describe('参数配置功能测试', () => {
|
||||
let authToken: string;
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
const response = await request.post('http://localhost:8080/api/auth/login', {
|
||||
const response = await request.post('http://localhost:8080/api/admin/auth/login', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ test.describe('字典管理功能测试', () => {
|
||||
let authToken: string;
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
const response = await request.post('http://localhost:8080/api/auth/login', {
|
||||
const response = await request.post('http://localhost:8080/api/admin/auth/login', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
||||
@@ -269,7 +269,7 @@ async function verifyAllServices(): Promise<void> {
|
||||
|
||||
console.log(' 验证网关到后端的连通性...');
|
||||
try {
|
||||
const response = await fetch('http://localhost:8080/api/auth/login', {
|
||||
const response = await fetch('http://localhost:8080/api/admin/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'Test@123' }),
|
||||
@@ -316,7 +316,7 @@ async function waitForBackendReady(): Promise<void> {
|
||||
console.log(`✅ 后端服务健康检查通过 (尝试 ${i + 1}/${maxRetries})`);
|
||||
|
||||
try {
|
||||
const loginTest = await fetch('http://localhost:8084/api/auth/login', {
|
||||
const loginTest = await fetch('http://localhost:8084/api/admin/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'Test@123' }),
|
||||
@@ -364,7 +364,7 @@ async function waitForGatewayReady(): Promise<void> {
|
||||
console.log(`✅ 网关服务健康检查通过 (尝试 ${i + 1}/${maxRetries})`);
|
||||
|
||||
try {
|
||||
const loginTest = await fetch('http://localhost:8080/api/auth/login', {
|
||||
const loginTest = await fetch('http://localhost:8080/api/admin/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: 'admin', password: 'Test@123' }),
|
||||
@@ -425,7 +425,7 @@ async function waitForFrontendReady(): Promise<void> {
|
||||
async function cleanupTestData(): Promise<void> {
|
||||
try {
|
||||
// 登录获取token(通过网关)
|
||||
const loginResponse = await fetch('http://localhost:8080/api/auth/login', {
|
||||
const loginResponse = await fetch('http://localhost:8080/api/admin/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -458,7 +458,7 @@ async function cleanupTestData(): Promise<void> {
|
||||
for (const user of users) {
|
||||
if (user.id > 10) {
|
||||
try {
|
||||
await fetch(`http://localhost:8080/api/users/${user.id}`, {
|
||||
await fetch(`http://localhost:8080/api/admin/users/${user.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
@@ -486,7 +486,7 @@ async function cleanupTestData(): Promise<void> {
|
||||
for (const role of roles) {
|
||||
if (role.id > 4) {
|
||||
try {
|
||||
await fetch(`http://localhost:8080/api/roles/${role.id}`, {
|
||||
await fetch(`http://localhost:8080/api/admin/roles/${role.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
|
||||
@@ -50,7 +50,7 @@ test.describe('管理员完整工作流', () => {
|
||||
await test.step('提交表单', async () => {
|
||||
const [response] = await Promise.all([
|
||||
page.waitForResponse(resp =>
|
||||
resp.url().includes('/api/roles') && resp.request().method() === 'POST',
|
||||
resp.url().includes('/api/admin/roles') && resp.request().method() === 'POST',
|
||||
{ timeout: 10000 }
|
||||
).catch(() => null),
|
||||
page.locator('.el-dialog button:has-text("确定")').click()
|
||||
|
||||
@@ -112,7 +112,7 @@ test.describe('用户权限边界验证', () => {
|
||||
});
|
||||
|
||||
await test.step('尝试访问受限API', async () => {
|
||||
const response = await page.request.get('/api/users?page=0&size=10');
|
||||
const response = await page.request.get('/api/admin/users?page=0&size=10');
|
||||
expect([200, 401, 403]).toContain(response.status());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ test.describe('菜单管理功能测试', () => {
|
||||
let authToken: string;
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
const response = await request.post('http://localhost:8080/api/auth/login', {
|
||||
const response = await request.post('http://localhost:8080/api/admin/auth/login', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<{ token: string; userId: number }> {
|
||||
const response = await this.request.post(`${this.baseURL}/api/auth/login`, {
|
||||
const response = await this.request.post(`${this.baseURL}/api/admin/auth/login`, {
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
@@ -29,7 +29,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async logout(token: string): Promise<void> {
|
||||
await this.request.post(`${this.baseURL}/api/auth/logout`, {
|
||||
await this.request.post(`${this.baseURL}/api/admin/auth/logout`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -37,7 +37,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async getUsers(token: string): Promise<any[]> {
|
||||
const response = await this.request.get(`${this.baseURL}/api/users`, {
|
||||
const response = await this.request.get(`${this.baseURL}/api/admin/users`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -51,7 +51,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async createUser(token: string, userData: any): Promise<any> {
|
||||
const response = await this.request.post(`${this.baseURL}/api/users`, {
|
||||
const response = await this.request.post(`${this.baseURL}/api/admin/users`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -66,7 +66,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async updateUser(token: string, userId: number, userData: any): Promise<any> {
|
||||
const response = await this.request.put(`${this.baseURL}/api/users/${userId}`, {
|
||||
const response = await this.request.put(`${this.baseURL}/api/admin/users/${userId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async deleteUser(token: string, userId: number): Promise<void> {
|
||||
const response = await this.request.delete(`${this.baseURL}/api/users/${userId}`, {
|
||||
const response = await this.request.delete(`${this.baseURL}/api/admin/users/${userId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -93,7 +93,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async getRoles(token: string): Promise<any[]> {
|
||||
const response = await this.request.get(`${this.baseURL}/api/roles`, {
|
||||
const response = await this.request.get(`${this.baseURL}/api/admin/roles`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -107,7 +107,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async createRole(token: string, roleData: any): Promise<any> {
|
||||
const response = await this.request.post(`${this.baseURL}/api/roles`, {
|
||||
const response = await this.request.post(`${this.baseURL}/api/admin/roles`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -122,7 +122,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async deleteRole(token: string, roleId: number): Promise<void> {
|
||||
const response = await this.request.delete(`${this.baseURL}/api/roles/${roleId}`, {
|
||||
const response = await this.request.delete(`${this.baseURL}/api/admin/roles/${roleId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
@@ -134,7 +134,7 @@ export class ApiClient {
|
||||
}
|
||||
|
||||
async getMenus(token: string): Promise<any[]> {
|
||||
const response = await this.request.get(`${this.baseURL}/api/menus`, {
|
||||
const response = await this.request.get(`${this.baseURL}/api/admin/menus`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
|
||||
@@ -55,7 +55,7 @@ export class TestDataManager {
|
||||
}
|
||||
|
||||
static async createTestUser(request: APIRequestContext, userData: TestUser): Promise<any> {
|
||||
const response = await request.post(`${this.apiBaseUrl}/api/users`, {
|
||||
const response = await request.post(`${this.apiBaseUrl}/api/admin/users`, {
|
||||
data: userData,
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ export class TestDataManager {
|
||||
}
|
||||
|
||||
static async createTestRole(request: APIRequestContext, roleData: TestRole): Promise<any> {
|
||||
const response = await request.post(`${this.apiBaseUrl}/api/roles`, {
|
||||
const response = await request.post(`${this.apiBaseUrl}/api/admin/roles`, {
|
||||
data: roleData,
|
||||
});
|
||||
|
||||
@@ -100,7 +100,7 @@ export class TestDataManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await request.delete(`${this.apiBaseUrl}/api/users/${userData.id}`);
|
||||
const response = await request.delete(`${this.apiBaseUrl}/api/admin/users/${userData.id}`);
|
||||
if (!response.ok()) {
|
||||
console.warn(`Failed to delete test user ${username}: ${await response.text()}`);
|
||||
}
|
||||
@@ -114,7 +114,7 @@ export class TestDataManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await request.delete(`${this.apiBaseUrl}/api/roles/${roleData.id}`);
|
||||
const response = await request.delete(`${this.apiBaseUrl}/api/admin/roles/${roleData.id}`);
|
||||
if (!response.ok()) {
|
||||
console.warn(`Failed to delete test role ${roleKey}: ${await response.text()}`);
|
||||
}
|
||||
|
||||
+9
-9
@@ -43,7 +43,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "获取会员信息", description = "根据当前登录用户获取会员基本信息")
|
||||
public Mono<ServerResponse> getMemberInfo(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
Long memberId = authUtil.getMemberUserIdOrThrow(request);
|
||||
|
||||
log.info("获取会员信息, memberId: {}", memberId);
|
||||
|
||||
@@ -56,7 +56,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "更新会员信息", description = "更新会员昵称、性别、生日、头像、地址等信息")
|
||||
public Mono<ServerResponse> updateMemberInfo(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
Long memberId = authUtil.getMemberUserIdOrThrow(request);
|
||||
|
||||
log.info("更新会员信息, memberId: {}", memberId);
|
||||
|
||||
@@ -70,7 +70,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "绑定手机号", description = "通过微信小程序手机号code绑定会员手机号")
|
||||
public Mono<ServerResponse> bindPhone(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
Long memberId = authUtil.getMemberUserIdOrThrow(request);
|
||||
|
||||
String phoneCode = request.queryParam("phoneCode").orElse("");
|
||||
|
||||
@@ -87,7 +87,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "查询服务号关注状态", description = "查询会员是否关注微信服务号")
|
||||
public Mono<ServerResponse> checkSubscribeStatus(ServerRequest request) {
|
||||
|
||||
Long memberId = authUtil.getMemberIdOrThrow(request);
|
||||
Long memberId = authUtil.getMemberUserIdOrThrow(request);
|
||||
|
||||
log.info("查询服务号关注状态, memberId: {}", memberId);
|
||||
|
||||
@@ -102,7 +102,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "管理员更新手机号", description = "后台管理员为会员更新手机号")
|
||||
public Mono<ServerResponse> adminUpdatePhone(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
Long adminId = authUtil.getAdminUserIdOrThrow(request);
|
||||
|
||||
String memberIdStr = request.pathVariable("id");
|
||||
long memberId = NumberUtils.toLong(memberIdStr, 0L);
|
||||
@@ -134,7 +134,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "管理员查看会员详情", description = "后台管理员查看指定会员的详细信息")
|
||||
public Mono<ServerResponse> adminGetMemberInfo(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
Long adminId = authUtil.getAdminUserIdOrThrow(request);
|
||||
|
||||
String memberIdStr = request.pathVariable("id");
|
||||
long memberId = NumberUtils.toLong(memberIdStr, 0L);
|
||||
@@ -162,7 +162,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "管理员编辑会员信息", description = "后台管理员编辑会员信息")
|
||||
public Mono<ServerResponse> adminUpdateMemberInfo(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
Long adminId = authUtil.getAdminUserIdOrThrow(request);
|
||||
|
||||
String memberIdStr = request.pathVariable("id");
|
||||
long memberId = NumberUtils.toLong(memberIdStr, 0L);
|
||||
@@ -181,7 +181,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "搜索会员列表", description = "后台管理员按关键词搜索会员,支持性别筛选和分页")
|
||||
public Mono<ServerResponse> searchMembers(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
Long adminId = authUtil.getAdminUserIdOrThrow(request);
|
||||
|
||||
String keyword = request.queryParam("searchValue").orElse(null);
|
||||
Integer pageNum = NumberUtils.toInt(request.queryParam("pageNum").orElse("1"), 1);
|
||||
@@ -212,7 +212,7 @@ public class MemberHandler {
|
||||
@Operation(summary = "查看会员列表", description = "后台管理员分页查看所有会员列表")
|
||||
public Mono<ServerResponse> getAllMembers(ServerRequest request) {
|
||||
|
||||
Long adminId = authUtil.getMemberIdOrThrow(request);
|
||||
Long adminId = authUtil.getAdminUserIdOrThrow(request);
|
||||
|
||||
int pageNum = NumberUtils.toInt(request.queryParam("pageNum").orElse("1"), 1);
|
||||
int pageSize = NumberUtils.toInt(request.queryParam("pageSize").orElse("10"), 10);
|
||||
|
||||
+3
-2
@@ -303,9 +303,9 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
}
|
||||
|
||||
List<String> roles = new ArrayList<>();
|
||||
String accessToken = jwtTokenProvider.generateToken(String.valueOf(member.getId()), member.getId(), roles);
|
||||
String accessToken = jwtTokenProvider.generateToken(String.valueOf(member.getId()), member.getId(), roles, "MEMBER");
|
||||
|
||||
log.info("JWT Token 生成成功, memberId: {}", member.getId());
|
||||
log.info("JWT Token 生成成功, memberId: {}, userType=MEMBER", member.getId());
|
||||
|
||||
int expiresIn = 86400;
|
||||
|
||||
@@ -316,6 +316,7 @@ public class WechatAuthServiceImpl implements WechatAuthService {
|
||||
.expiresIn(expiresIn)
|
||||
.isNewUser(isNewUser)
|
||||
.needCompleteInfo(needCompleteInfo)
|
||||
.userType("MEMBER")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -35,4 +35,7 @@ public class WechatLoginVO {
|
||||
|
||||
// 是否需要补全信息(昵称、手机号等)
|
||||
private Boolean needCompleteInfo;
|
||||
|
||||
// 用户类型(MEMBER)
|
||||
private String userType;
|
||||
}
|
||||
|
||||
+149
-149
@@ -34,7 +34,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
|
||||
*
|
||||
* 文件定义:配置WebFlux函数式路由,将HTTP请求映射到对应的Handler方法
|
||||
* 涉及业务:用户、角色、字典、菜单、公告、文件等所有RESTful API路由
|
||||
* 算法:使用RouterFunctions.route()构建函数式路由规则
|
||||
* 路由规范:后台管理 API 统一前缀 /api/admin/**,前台会员 API 统一前缀 /api/member/**
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-13
|
||||
@@ -67,151 +67,151 @@ public class SystemRouter {
|
||||
CheckInHandler checkInHandler) {
|
||||
|
||||
return route()
|
||||
// ========== 诊断路由 ==========
|
||||
.GET("/api/diagnostic/password", passwordDiagnosticHandler::diagnose)
|
||||
// ========== 诊断路由(管理端) ==========
|
||||
.GET("/api/admin/diagnostic/password", passwordDiagnosticHandler::diagnose)
|
||||
|
||||
// ========== 字典路由 ==========
|
||||
.GET("/api/dictionaries", dictionaryHandler::getAllDictionaries)
|
||||
.GET("/api/dictionaries/{id}", dictionaryHandler::getDictionaryById)
|
||||
.GET("/api/dictionaries/type/{type}", dictionaryHandler::getDictionariesByType)
|
||||
.GET("/api/dictionaries/check/exists", dictionaryHandler::checkTypeAndCodeExists)
|
||||
.POST("/api/dictionaries", dictionaryHandler::createDictionary)
|
||||
.PUT("/api/dictionaries/{id}", dictionaryHandler::updateDictionary)
|
||||
.DELETE("/api/dictionaries/{id}", dictionaryHandler::deleteDictionary)
|
||||
// ========== 字典路由(管理端) ==========
|
||||
.GET("/api/admin/dictionaries", dictionaryHandler::getAllDictionaries)
|
||||
.GET("/api/admin/dictionaries/{id}", dictionaryHandler::getDictionaryById)
|
||||
.GET("/api/admin/dictionaries/type/{type}", dictionaryHandler::getDictionariesByType)
|
||||
.GET("/api/admin/dictionaries/check/exists", dictionaryHandler::checkTypeAndCodeExists)
|
||||
.POST("/api/admin/dictionaries", dictionaryHandler::createDictionary)
|
||||
.PUT("/api/admin/dictionaries/{id}", dictionaryHandler::updateDictionary)
|
||||
.DELETE("/api/admin/dictionaries/{id}", dictionaryHandler::deleteDictionary)
|
||||
|
||||
// ========== 用户路由 ==========
|
||||
.GET("/api/users", userHandler::getAllUsers)
|
||||
.GET("/api/users/page", userHandler::getUsersByPage)
|
||||
.GET("/api/users/count", userHandler::getUserCount)
|
||||
.GET("/api/users/username/{username}", userHandler::getUserByUsername)
|
||||
.GET("/api/users/check/username", userHandler::checkUsernameExists)
|
||||
.GET("/api/users/check/email", userHandler::checkEmailExists)
|
||||
.POST("/api/users", userHandler::createUser)
|
||||
.GET("/api/users/{id}", userHandler::getUserById)
|
||||
.PUT("/api/users/{id}", userHandler::updateUser)
|
||||
.DELETE("/api/users/{id}", userHandler::deleteUser)
|
||||
.POST("/api/users/{id}/action/change-password", userHandler::changePassword)
|
||||
.POST("/api/users/{id}/action/logical-delete", userHandler::logicalDeleteUser)
|
||||
.POST("/api/users/logical-delete", userHandler::logicalDeleteUsers)
|
||||
.POST("/api/users/action/restore", userHandler::restoreUsers)
|
||||
.POST("/api/users/{id}/action/restore", userHandler::restoreUser)
|
||||
.GET("/api/users/{id}/roles", userHandler::getUserRoles)
|
||||
.POST("/api/users/{id}/roles", userHandler::assignRoles)
|
||||
// ========== 用户路由(管理端) ==========
|
||||
.GET("/api/admin/users", userHandler::getAllUsers)
|
||||
.GET("/api/admin/users/page", userHandler::getUsersByPage)
|
||||
.GET("/api/admin/users/count", userHandler::getUserCount)
|
||||
.GET("/api/admin/users/username/{username}", userHandler::getUserByUsername)
|
||||
.GET("/api/admin/users/check/username", userHandler::checkUsernameExists)
|
||||
.GET("/api/admin/users/check/email", userHandler::checkEmailExists)
|
||||
.POST("/api/admin/users", userHandler::createUser)
|
||||
.GET("/api/admin/users/{id}", userHandler::getUserById)
|
||||
.PUT("/api/admin/users/{id}", userHandler::updateUser)
|
||||
.DELETE("/api/admin/users/{id}", userHandler::deleteUser)
|
||||
.POST("/api/admin/users/{id}/action/change-password", userHandler::changePassword)
|
||||
.POST("/api/admin/users/{id}/action/logical-delete", userHandler::logicalDeleteUser)
|
||||
.POST("/api/admin/users/logical-delete", userHandler::logicalDeleteUsers)
|
||||
.POST("/api/admin/users/action/restore", userHandler::restoreUsers)
|
||||
.POST("/api/admin/users/{id}/action/restore", userHandler::restoreUser)
|
||||
.GET("/api/admin/users/{id}/roles", userHandler::getUserRoles)
|
||||
.POST("/api/admin/users/{id}/roles", userHandler::assignRoles)
|
||||
|
||||
// ========== 菜单路由 ==========
|
||||
.GET("/api/menus", menuHandler::getAllMenus)
|
||||
.GET("/api/menus/tree", menuHandler::getMenuTree)
|
||||
.GET("/api/menus/{id}", menuHandler::getMenuById)
|
||||
.POST("/api/menus", menuHandler::createMenu)
|
||||
.PUT("/api/menus/{id}", menuHandler::updateMenu)
|
||||
.DELETE("/api/menus/{id}", menuHandler::deleteMenu)
|
||||
// ========== 菜单路由(管理端) ==========
|
||||
.GET("/api/admin/menus", menuHandler::getAllMenus)
|
||||
.GET("/api/admin/menus/tree", menuHandler::getMenuTree)
|
||||
.GET("/api/admin/menus/{id}", menuHandler::getMenuById)
|
||||
.POST("/api/admin/menus", menuHandler::createMenu)
|
||||
.PUT("/api/admin/menus/{id}", menuHandler::updateMenu)
|
||||
.DELETE("/api/admin/menus/{id}", menuHandler::deleteMenu)
|
||||
|
||||
// ========== 角色路由 ==========
|
||||
.GET("/api/roles", roleHandler::getAllRoles)
|
||||
.GET("/api/roles/page", roleHandler::getRolesByPage)
|
||||
.GET("/api/roles/count", roleHandler::getRoleCount)
|
||||
.GET("/api/roles/name/{roleName}", roleHandler::getRoleByName)
|
||||
.GET("/api/roles/check-name", roleHandler::checkNameExists)
|
||||
.GET("/api/roles/{id}", roleHandler::getRoleById)
|
||||
.POST("/api/roles", roleHandler::createRole)
|
||||
.PUT("/api/roles/{id}", roleHandler::updateRole)
|
||||
.DELETE("/api/roles/{id}", roleHandler::deleteRole)
|
||||
.POST("/api/roles/{id}/restore", roleHandler::restoreRole)
|
||||
.GET("/api/roles/{id}/permissions", permissionHandler::getPermissionsByRoleId)
|
||||
.POST("/api/roles/{id}/permissions", permissionHandler::assignPermissionsToRole)
|
||||
// ========== 角色路由(管理端) ==========
|
||||
.GET("/api/admin/roles", roleHandler::getAllRoles)
|
||||
.GET("/api/admin/roles/page", roleHandler::getRolesByPage)
|
||||
.GET("/api/admin/roles/count", roleHandler::getRoleCount)
|
||||
.GET("/api/admin/roles/name/{roleName}", roleHandler::getRoleByName)
|
||||
.GET("/api/admin/roles/check-name", roleHandler::checkNameExists)
|
||||
.GET("/api/admin/roles/{id}", roleHandler::getRoleById)
|
||||
.POST("/api/admin/roles", roleHandler::createRole)
|
||||
.PUT("/api/admin/roles/{id}", roleHandler::updateRole)
|
||||
.DELETE("/api/admin/roles/{id}", roleHandler::deleteRole)
|
||||
.POST("/api/admin/roles/{id}/restore", roleHandler::restoreRole)
|
||||
.GET("/api/admin/roles/{id}/permissions", permissionHandler::getPermissionsByRoleId)
|
||||
.POST("/api/admin/roles/{id}/permissions", permissionHandler::assignPermissionsToRole)
|
||||
|
||||
// ========== 配置路由 ==========
|
||||
.GET("/api/config", configHandler::getAllConfigs)
|
||||
.GET("/api/config/{id}", configHandler::getConfigById)
|
||||
.GET("/api/config/key/{configKey}", configHandler::getConfigByKey)
|
||||
.POST("/api/config", configHandler::createConfig)
|
||||
.PUT("/api/config/{id}", configHandler::updateConfig)
|
||||
.DELETE("/api/config/{id}", configHandler::deleteConfig)
|
||||
// ========== 配置路由(管理端) ==========
|
||||
.GET("/api/admin/config", configHandler::getAllConfigs)
|
||||
.GET("/api/admin/config/{id}", configHandler::getConfigById)
|
||||
.GET("/api/admin/config/key/{configKey}", configHandler::getConfigByKey)
|
||||
.POST("/api/admin/config", configHandler::createConfig)
|
||||
.PUT("/api/admin/config/{id}", configHandler::updateConfig)
|
||||
.DELETE("/api/admin/config/{id}", configHandler::deleteConfig)
|
||||
|
||||
// ========== 日志路由 ==========
|
||||
.GET("/api/logs/login", logHandler::getAllLoginLogs)
|
||||
.GET("/api/logs/login/page", logHandler::getLoginLogsByPage)
|
||||
.GET("/api/logs/login/count", logHandler::getLoginLogCount)
|
||||
.GET("/api/logs/login/today/count", logHandler::getTodayLoginCount)
|
||||
.GET("/api/logs/login/recent", logHandler::getRecentLoginLogs)
|
||||
.GET("/api/logs/login/{id}", logHandler::getLoginLogById)
|
||||
.POST("/api/logs/login", logHandler::createLoginLog)
|
||||
.GET("/api/logs/exception", logHandler::getAllExceptionLogs)
|
||||
.GET("/api/logs/exception/page", logHandler::getExceptionLogsByPage)
|
||||
.GET("/api/logs/exception/count", logHandler::getExceptionLogCount)
|
||||
.GET("/api/logs/exception/{id}", logHandler::getExceptionLogById)
|
||||
.POST("/api/logs/exception", logHandler::createExceptionLog)
|
||||
.GET("/api/logs/operation", operationLogHandler::getAllOperationLogs)
|
||||
.GET("/api/logs/operation/export", operationLogHandler::exportOperationLogs)
|
||||
.GET("/api/logs/operation/page", operationLogHandler::getOperationLogsByPage)
|
||||
.GET("/api/logs/operation/count", operationLogHandler::getOperationLogCount)
|
||||
.GET("/api/logs/operation/{id}", operationLogHandler::getOperationLogById)
|
||||
.POST("/api/logs/operation", operationLogHandler::createOperationLog)
|
||||
// ========== 日志路由(管理端) ==========
|
||||
.GET("/api/admin/logs/login", logHandler::getAllLoginLogs)
|
||||
.GET("/api/admin/logs/login/page", logHandler::getLoginLogsByPage)
|
||||
.GET("/api/admin/logs/login/count", logHandler::getLoginLogCount)
|
||||
.GET("/api/admin/logs/login/today/count", logHandler::getTodayLoginCount)
|
||||
.GET("/api/admin/logs/login/recent", logHandler::getRecentLoginLogs)
|
||||
.GET("/api/admin/logs/login/{id}", logHandler::getLoginLogById)
|
||||
.POST("/api/admin/logs/login", logHandler::createLoginLog)
|
||||
.GET("/api/admin/logs/exception", logHandler::getAllExceptionLogs)
|
||||
.GET("/api/admin/logs/exception/page", logHandler::getExceptionLogsByPage)
|
||||
.GET("/api/admin/logs/exception/count", logHandler::getExceptionLogCount)
|
||||
.GET("/api/admin/logs/exception/{id}", logHandler::getExceptionLogById)
|
||||
.POST("/api/admin/logs/exception", logHandler::createExceptionLog)
|
||||
.GET("/api/admin/logs/operation", operationLogHandler::getAllOperationLogs)
|
||||
.GET("/api/admin/logs/operation/export", operationLogHandler::exportOperationLogs)
|
||||
.GET("/api/admin/logs/operation/page", operationLogHandler::getOperationLogsByPage)
|
||||
.GET("/api/admin/logs/operation/count", operationLogHandler::getOperationLogCount)
|
||||
.GET("/api/admin/logs/operation/{id}", operationLogHandler::getOperationLogById)
|
||||
.POST("/api/admin/logs/operation", operationLogHandler::createOperationLog)
|
||||
|
||||
// ========== 认证路由 ==========
|
||||
.POST("/api/auth/login", authHandler::login)
|
||||
.POST("/api/auth/register", authHandler::register)
|
||||
.POST("/api/auth/logout", authHandler::logout)
|
||||
// ========== 认证路由(管理端) ==========
|
||||
.POST("/api/admin/auth/login", authHandler::login)
|
||||
.POST("/api/admin/auth/register", authHandler::register)
|
||||
.POST("/api/admin/auth/logout", authHandler::logout)
|
||||
|
||||
// ========== 统计路由 ==========
|
||||
.GET("/api/stats/overview", statsHandler::getOverview)
|
||||
// ========== 统计路由(管理端) ==========
|
||||
.GET("/api/admin/stats/overview", statsHandler::getOverview)
|
||||
|
||||
// ========== 数据字典路由 ==========
|
||||
.GET("/api/dict/types", dictHandler::getAllDictTypes)
|
||||
.GET("/api/dict/types/{id}", dictHandler::getDictTypeById)
|
||||
.GET("/api/dict/types/type/{dictType}", dictHandler::getDictTypeByType)
|
||||
.POST("/api/dict/types", dictHandler::createDictType)
|
||||
.PUT("/api/dict/types/{id}", dictHandler::updateDictType)
|
||||
.DELETE("/api/dict/types/{id}", dictHandler::deleteDictType)
|
||||
.GET("/api/dict/data", dictHandler::getAllDictData)
|
||||
.GET("/api/dict/data/type/{dictType}", dictHandler::getDictDataByType)
|
||||
.GET("/api/dict/data/{id}", dictHandler::getDictDataById)
|
||||
.POST("/api/dict/data", dictHandler::createDictData)
|
||||
.PUT("/api/dict/data/{id}", dictHandler::updateDictData)
|
||||
.DELETE("/api/dict/data/{id}", dictHandler::deleteDictData)
|
||||
// ========== 数据字典路由(管理端) ==========
|
||||
.GET("/api/admin/dict/types", dictHandler::getAllDictTypes)
|
||||
.GET("/api/admin/dict/types/{id}", dictHandler::getDictTypeById)
|
||||
.GET("/api/admin/dict/types/type/{dictType}", dictHandler::getDictTypeByType)
|
||||
.POST("/api/admin/dict/types", dictHandler::createDictType)
|
||||
.PUT("/api/admin/dict/types/{id}", dictHandler::updateDictType)
|
||||
.DELETE("/api/admin/dict/types/{id}", dictHandler::deleteDictType)
|
||||
.GET("/api/admin/dict/data", dictHandler::getAllDictData)
|
||||
.GET("/api/admin/dict/data/type/{dictType}", dictHandler::getDictDataByType)
|
||||
.GET("/api/admin/dict/data/{id}", dictHandler::getDictDataById)
|
||||
.POST("/api/admin/dict/data", dictHandler::createDictData)
|
||||
.PUT("/api/admin/dict/data/{id}", dictHandler::updateDictData)
|
||||
.DELETE("/api/admin/dict/data/{id}", dictHandler::deleteDictData)
|
||||
|
||||
// ========== 公告路由 ==========
|
||||
.GET("/api/notices", noticeHandler::getAllNotices)
|
||||
.GET("/api/notices/{id}", noticeHandler::getNoticeById)
|
||||
.GET("/api/notices/status/{status}", noticeHandler::getNoticesByStatus)
|
||||
.POST("/api/notices", noticeHandler::createNotice)
|
||||
.PUT("/api/notices/{id}", noticeHandler::updateNotice)
|
||||
.DELETE("/api/notices/{id}", noticeHandler::deleteNotice)
|
||||
// ========== 公告路由(管理端) ==========
|
||||
.GET("/api/admin/notices", noticeHandler::getAllNotices)
|
||||
.GET("/api/admin/notices/{id}", noticeHandler::getNoticeById)
|
||||
.GET("/api/admin/notices/status/{status}", noticeHandler::getNoticesByStatus)
|
||||
.POST("/api/admin/notices", noticeHandler::createNotice)
|
||||
.PUT("/api/admin/notices/{id}", noticeHandler::updateNotice)
|
||||
.DELETE("/api/admin/notices/{id}", noticeHandler::deleteNotice)
|
||||
|
||||
// ========== 消息路由 ==========
|
||||
.GET("/api/messages/user/{userId}", messageHandler::getMessagesByUser)
|
||||
.GET("/api/messages/user/{userId}/unread", messageHandler::getUnreadCount)
|
||||
.GET("/api/messages/user/{userId}/unread/list", messageHandler::getUnreadList)
|
||||
.POST("/api/messages", messageHandler::createMessage)
|
||||
.PUT("/api/messages/{id}/read", messageHandler::markAsRead)
|
||||
.DELETE("/api/messages/{id}", messageHandler::deleteMessage)
|
||||
// ========== 消息路由(管理端) ==========
|
||||
.GET("/api/admin/messages/user/{userId}", messageHandler::getMessagesByUser)
|
||||
.GET("/api/admin/messages/user/{userId}/unread", messageHandler::getUnreadCount)
|
||||
.GET("/api/admin/messages/user/{userId}/unread/list", messageHandler::getUnreadList)
|
||||
.POST("/api/admin/messages", messageHandler::createMessage)
|
||||
.PUT("/api/admin/messages/{id}/read", messageHandler::markAsRead)
|
||||
.DELETE("/api/admin/messages/{id}", messageHandler::deleteMessage)
|
||||
|
||||
// ========== 文件路由 ==========
|
||||
.GET("/api/files", fileHandler::getAllFiles)
|
||||
.GET("/api/files/{id}", fileHandler::getFileById)
|
||||
.POST("/api/files/upload", fileHandler::uploadFile)
|
||||
.GET("/api/files/{id}/download", fileHandler::downloadFile)
|
||||
.GET("/api/files/download/{fileName}", fileHandler::downloadFileByName)
|
||||
.GET("/api/files/{id}/preview", fileHandler::previewFile)
|
||||
.GET("/api/files/preview/{fileName}", fileHandler::previewFileByName)
|
||||
.DELETE("/api/files/{id}", fileHandler::deleteFile)
|
||||
// ========== 文件路由(管理端) ==========
|
||||
.GET("/api/admin/files", fileHandler::getAllFiles)
|
||||
.GET("/api/admin/files/{id}", fileHandler::getFileById)
|
||||
.POST("/api/admin/files/upload", fileHandler::uploadFile)
|
||||
.GET("/api/admin/files/{id}/download", fileHandler::downloadFile)
|
||||
.GET("/api/admin/files/download/{fileName}", fileHandler::downloadFileByName)
|
||||
.GET("/api/admin/files/{id}/preview", fileHandler::previewFile)
|
||||
.GET("/api/admin/files/preview/{fileName}", fileHandler::previewFileByName)
|
||||
.DELETE("/api/admin/files/{id}", fileHandler::deleteFile)
|
||||
|
||||
// ========== 权限路由 ==========
|
||||
.GET("/api/permissions", permissionHandler::getAllPermissions)
|
||||
.GET("/api/permissions/{id}", permissionHandler::getPermissionById)
|
||||
.GET("/api/permissions/code/{code}", permissionHandler::getPermissionByCode)
|
||||
.GET("/api/permissions/check-code", permissionHandler::checkCodeExists)
|
||||
.GET("/api/permissions/count", permissionHandler::getPermissionCount)
|
||||
.POST("/api/permissions", permissionHandler::createPermission)
|
||||
.PUT("/api/permissions/{id}", permissionHandler::updatePermission)
|
||||
.DELETE("/api/permissions/{id}", permissionHandler::deletePermission)
|
||||
// ========== 权限路由(管理端) ==========
|
||||
.GET("/api/admin/permissions", permissionHandler::getAllPermissions)
|
||||
.GET("/api/admin/permissions/{id}", permissionHandler::getPermissionById)
|
||||
.GET("/api/admin/permissions/code/{code}", permissionHandler::getPermissionByCode)
|
||||
.GET("/api/admin/permissions/check-code", permissionHandler::checkCodeExists)
|
||||
.GET("/api/admin/permissions/count", permissionHandler::getPermissionCount)
|
||||
.POST("/api/admin/permissions", permissionHandler::createPermission)
|
||||
.PUT("/api/admin/permissions/{id}", permissionHandler::updatePermission)
|
||||
.DELETE("/api/admin/permissions/{id}", permissionHandler::deletePermission)
|
||||
|
||||
// ========== 会员模块路由 - 微信认证 ==========
|
||||
// ========== 会员模块路由 - 微信认证(前台公开) ==========
|
||||
.POST("/api/member/auth/miniapp/login", wechatAuthHandler::miniappLogin)
|
||||
.GET("/api/member/auth/mp/callback", wechatAuthHandler::verifyMpSignature)
|
||||
.POST("/api/member/auth/mp/callback", wechatAuthHandler::mpCallback)
|
||||
|
||||
// ========== 会员模块路由 - 会员信息 ==========
|
||||
// ========== 会员模块路由 - 会员信息(前台) ==========
|
||||
.GET("/api/member/info", memberHandler::getMemberInfo)
|
||||
.PUT("/api/member/info", memberHandler::updateMemberInfo)
|
||||
.POST("/api/member/phone/bind", memberHandler::bindPhone)
|
||||
@@ -226,35 +226,35 @@ public class SystemRouter {
|
||||
|
||||
|
||||
// ========================================
|
||||
// ========== 会员卡管理路由 ==============
|
||||
// ========== 会员卡管理路由(管理端) ==============
|
||||
// ========================================
|
||||
|
||||
// ===== 会员卡类型管理 =====
|
||||
.GET("/api/member-cards/active", memberCardHandler::getActiveCards)
|
||||
.GET("/api/member-cards/{memberCardId}", memberCardHandler::getMemberCardById)
|
||||
.POST("/api/member-cards", memberCardHandler::createMemberCard)
|
||||
.GET("/api/admin/member-cards/active", memberCardHandler::getActiveCards)
|
||||
.GET("/api/admin/member-cards/{memberCardId}", memberCardHandler::getMemberCardById)
|
||||
.POST("/api/admin/member-cards", memberCardHandler::createMemberCard)
|
||||
|
||||
// ===== 会员卡记录管理(核心业务)=====
|
||||
.POST("/api/member-card-records/purchase", memberCardRecordHandler::purchaseCard)
|
||||
.POST("/api/member-card-records/{recordId}/renew", memberCardRecordHandler::renewCard)
|
||||
.POST("/api/member-card-records/{recordId}/use", memberCardRecordHandler::useCard)
|
||||
.POST("/api/member-card-records/{recordId}/refund", memberCardRecordHandler::refundCard)
|
||||
.GET("/api/member-card-records/my-cards/{memberId}", memberCardRecordHandler::getMyCards)
|
||||
.GET("/api/member-card-records/{recordId}", memberCardRecordHandler::getMemberCardRecordById)
|
||||
.POST("/api/member-card-records/process-expired", memberCardRecordHandler::processExpiredCards)
|
||||
.POST("/api/admin/member-card-records/purchase", memberCardRecordHandler::purchaseCard)
|
||||
.POST("/api/admin/member-card-records/{recordId}/renew", memberCardRecordHandler::renewCard)
|
||||
.POST("/api/admin/member-card-records/{recordId}/use", memberCardRecordHandler::useCard)
|
||||
.POST("/api/admin/member-card-records/{recordId}/refund", memberCardRecordHandler::refundCard)
|
||||
.GET("/api/admin/member-card-records/my-cards/{memberId}", memberCardRecordHandler::getMyCards)
|
||||
.GET("/api/admin/member-card-records/{recordId}", memberCardRecordHandler::getMemberCardRecordById)
|
||||
.POST("/api/admin/member-card-records/process-expired", memberCardRecordHandler::processExpiredCards)
|
||||
|
||||
// ===== 会员卡交易流水管理 =====
|
||||
.POST("/api/member-card-transactions", memberCardTransactionHandler::insertTransaction)
|
||||
.GET("/api/member-card-transactions", memberCardTransactionHandler::getTransactionsWithConditions)
|
||||
.GET("/api/member-card-transactions/member/{memberId}", memberCardTransactionHandler::getMemberTransactions)
|
||||
.GET("/api/member-card-transactions/card/{cardId}", memberCardTransactionHandler::getTransactionsByCardId)
|
||||
.GET("/api/member-card-transactions/statistics/deduct/{cardId}", memberCardTransactionHandler::getDeductCountByCardId)
|
||||
.GET("/api/member-card-transactions/statistics/renew", memberCardTransactionHandler::getRenewAmountByTimeRange)
|
||||
.GET("/api/member-card-transactions/statistics/purchase/{memberId}", memberCardTransactionHandler::getPurchaseAmountByMember)
|
||||
.POST("/api/admin/member-card-transactions", memberCardTransactionHandler::insertTransaction)
|
||||
.GET("/api/admin/member-card-transactions", memberCardTransactionHandler::getTransactionsWithConditions)
|
||||
.GET("/api/admin/member-card-transactions/member/{memberId}", memberCardTransactionHandler::getMemberTransactions)
|
||||
.GET("/api/admin/member-card-transactions/card/{cardId}", memberCardTransactionHandler::getTransactionsByCardId)
|
||||
.GET("/api/admin/member-card-transactions/statistics/deduct/{cardId}", memberCardTransactionHandler::getDeductCountByCardId)
|
||||
.GET("/api/admin/member-card-transactions/statistics/renew", memberCardTransactionHandler::getRenewAmountByTimeRange)
|
||||
.GET("/api/admin/member-card-transactions/statistics/purchase/{memberId}", memberCardTransactionHandler::getPurchaseAmountByMember)
|
||||
|
||||
// ========= 签到路由 ==========
|
||||
.POST("/api/checkIn", checkInHandler::checkIn )
|
||||
.GET("/api/checkIn/qrcode", checkInHandler::getQRCode)
|
||||
// ========= 签到路由(前台会员) ==========
|
||||
.POST("/api/member/checkIn", checkInHandler::checkIn)
|
||||
.GET("/api/member/checkIn/qrcode", checkInHandler::getQRCode)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package cn.novalon.gym.manage.common.constants;
|
||||
|
||||
/**
|
||||
* 用户类型枚举
|
||||
* 用于区分后台管理用户和前台会员用户
|
||||
*/
|
||||
public enum UserType {
|
||||
ADMIN,
|
||||
MEMBER;
|
||||
|
||||
public static UserType fromString(String value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("userType 不能为空");
|
||||
}
|
||||
for (UserType type : values()) {
|
||||
if (type.name().equalsIgnoreCase(value)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知的用户类型: " + value);
|
||||
}
|
||||
}
|
||||
+27
-5
@@ -42,6 +42,13 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory<JwtAut
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
// 路径-userType 校验:防止越权访问
|
||||
String userType = jwtUtil.getUserTypeFromToken(token);
|
||||
if (!isUserTypeAllowedForPath(path, userType)) {
|
||||
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
String username = jwtUtil.getUsernameFromToken(token);
|
||||
Long userId = jwtUtil.getUserIdFromToken(token);
|
||||
|
||||
@@ -49,19 +56,34 @@ public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory<JwtAut
|
||||
.header("X-User-Id", String.valueOf(userId))
|
||||
.header("X-Member-Id", String.valueOf(userId))
|
||||
.header("X-Username", username)
|
||||
.header("X-User-Type", userType != null ? userType : "UNKNOWN")
|
||||
.build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(modifiedRequest).build());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验路径与 userType 是否匹配
|
||||
* - /api/admin/** 路径只允许 userType=ADMIN
|
||||
* - /api/member/** 路径只允许 userType=MEMBER
|
||||
* - 其他路径不做 userType 校验
|
||||
*/
|
||||
private boolean isUserTypeAllowedForPath(String path, String userType) {
|
||||
if (path.startsWith("/api/admin/")) {
|
||||
return "ADMIN".equals(userType);
|
||||
}
|
||||
if (path.startsWith("/api/member/")) {
|
||||
return "MEMBER".equals(userType);
|
||||
}
|
||||
// 非特定前缀路径不做 userType 校验
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isPublicPath(String path) {
|
||||
return path.startsWith("/api/auth/") ||
|
||||
return path.startsWith("/api/admin/auth/") ||
|
||||
path.equals("/actuator/health") ||
|
||||
path.equals("/api/member/auth/miniapp/login") ||
|
||||
path.equals("/api/member/auth/mp/callback") ||
|
||||
path.equals("/api/auth/login") ||
|
||||
path.startsWith("/api/checkIn/") ||
|
||||
path.startsWith("/api/member/auth/") ||
|
||||
path.startsWith("/actuator/info");
|
||||
}
|
||||
|
||||
|
||||
+11
-1
@@ -28,6 +28,10 @@ public class JwtUtil {
|
||||
}
|
||||
|
||||
public String generateToken(String username, Long userId) {
|
||||
return generateToken(username, userId, "ADMIN");
|
||||
}
|
||||
|
||||
public String generateToken(String username, Long userId, String userType) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration);
|
||||
|
||||
@@ -35,13 +39,14 @@ public class JwtUtil {
|
||||
String token = Jwts.builder()
|
||||
.setSubject(username)
|
||||
.claim("userId", userId)
|
||||
.claim("userType", userType)
|
||||
.claim("keyVersion", jwtKeyService.getCurrentKeyVersion())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
|
||||
logger.debug("Generated JWT token for user: {}, userId: {}", username, userId);
|
||||
logger.debug("Generated JWT token for user: {}, userId: {}, userType: {}", username, userId, userType);
|
||||
return token;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -74,6 +79,11 @@ public class JwtUtil {
|
||||
return claims.get("userId", Long.class);
|
||||
}
|
||||
|
||||
public String getUserTypeFromToken(String token) {
|
||||
Claims claims = parseToken(token);
|
||||
return claims.get("userType", String.class);
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
parseToken(token);
|
||||
|
||||
@@ -64,7 +64,7 @@ signature:
|
||||
max-age-minutes: ${SIGNATURE_MAX_AGE_MINUTES:5}
|
||||
nonce-cache-size: ${SIGNATURE_NONCE_CACHE_SIZE:10000}
|
||||
whitelist:
|
||||
paths: ${SIGNATURE_WHITELIST_PATHS:/actuator/health,/actuator/info,/api/auth/login,/api/auth/register,/api/member/auth/miniapp/login,/api/member/auth/mp/callback}
|
||||
paths: ${SIGNATURE_WHITELIST_PATHS:/actuator/health,/actuator/info,/api/admin/auth/login,/api/admin/auth/register,/api/member/auth/miniapp/login,/api/member/auth/mp/callback}
|
||||
|
||||
resilience:
|
||||
enabled: ${RESILIENCE_ENABLED:true}
|
||||
|
||||
+164
-4
@@ -39,7 +39,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
|
||||
@Test
|
||||
void testPublicPath_AllowAccess() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/login").build();
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/admin/auth/login").build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
@@ -56,7 +56,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
|
||||
@Test
|
||||
void testPublicPath_Register() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/api/auth/register").build();
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/api/admin/auth/register").build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
@@ -105,6 +105,40 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
verify(jwtUtil, never()).validateToken(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPublicPath_MemberAuth() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/api/member/auth/miniapp/login").build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
verify(jwtUtil, never()).validateToken(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPublicPath_AdminAuthPrefix() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.post("/api/admin/auth/refresh").build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
verify(jwtUtil, never()).validateToken(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProtectedPath_NoAuthHeader() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users").build();
|
||||
@@ -152,6 +186,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
|
||||
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
|
||||
when(jwtUtil.getUserTypeFromToken(validToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
@@ -163,6 +198,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
verify(jwtUtil).isTokenExpired(validToken);
|
||||
verify(jwtUtil).getUsernameFromToken(validToken);
|
||||
verify(jwtUtil).getUserIdFromToken(validToken);
|
||||
verify(jwtUtil).getUserTypeFromToken(validToken);
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
@@ -224,6 +260,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
|
||||
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
|
||||
when(jwtUtil.getUserTypeFromToken(validToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
@@ -235,6 +272,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
verify(jwtUtil).isTokenExpired(validToken);
|
||||
verify(jwtUtil).getUsernameFromToken(validToken);
|
||||
verify(jwtUtil).getUserIdFromToken(validToken);
|
||||
verify(jwtUtil).getUserTypeFromToken(validToken);
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
@@ -251,6 +289,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
|
||||
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
|
||||
when(jwtUtil.getUserTypeFromToken(validToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
@@ -263,11 +302,12 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
ServerHttpRequest modifiedRequest = exchangeCaptor.getValue().getRequest();
|
||||
assert modifiedRequest.getHeaders().getFirst("X-User-Id").equals("1");
|
||||
assert modifiedRequest.getHeaders().getFirst("X-Username").equals("testuser");
|
||||
assert modifiedRequest.getHeaders().getFirst("X-User-Type").equals("ADMIN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedPath_AuthPath() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/logout").build();
|
||||
void testMixedPath_AdminAuthPath() {
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/admin/auth/logout").build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
@@ -295,6 +335,7 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
|
||||
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
|
||||
when(jwtUtil.getUserTypeFromToken(validToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
@@ -308,4 +349,123 @@ class GatewayJwtAuthenticationFilterTest {
|
||||
verify(jwtUtil).getUserIdFromToken(validToken);
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
// ========== userType 路径校验测试 ==========
|
||||
|
||||
@Test
|
||||
void testAdminPath_WithAdminToken_ShouldPass() {
|
||||
String adminToken = "admin.jwt.token";
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/admin/users")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
|
||||
.build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
when(jwtUtil.validateToken(adminToken)).thenReturn(true);
|
||||
when(jwtUtil.isTokenExpired(adminToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(adminToken)).thenReturn("admin");
|
||||
when(jwtUtil.getUserIdFromToken(adminToken)).thenReturn(1L);
|
||||
when(jwtUtil.getUserTypeFromToken(adminToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdminPath_WithMemberToken_ShouldBeForbidden() {
|
||||
String memberToken = "member.jwt.token";
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/admin/users")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + memberToken)
|
||||
.build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(jwtUtil.validateToken(memberToken)).thenReturn(true);
|
||||
when(jwtUtil.isTokenExpired(memberToken)).thenReturn(false);
|
||||
when(jwtUtil.getUserTypeFromToken(memberToken)).thenReturn("MEMBER");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
assert exchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
|
||||
verify(chain, never()).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMemberPath_WithMemberToken_ShouldPass() {
|
||||
String memberToken = "member.jwt.token";
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/member/info")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + memberToken)
|
||||
.build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
when(jwtUtil.validateToken(memberToken)).thenReturn(true);
|
||||
when(jwtUtil.isTokenExpired(memberToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(memberToken)).thenReturn("123");
|
||||
when(jwtUtil.getUserIdFromToken(memberToken)).thenReturn(123L);
|
||||
when(jwtUtil.getUserTypeFromToken(memberToken)).thenReturn("MEMBER");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMemberPath_WithAdminToken_ShouldBeForbidden() {
|
||||
String adminToken = "admin.jwt.token";
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/member/info")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
|
||||
.build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(jwtUtil.validateToken(adminToken)).thenReturn(true);
|
||||
when(jwtUtil.isTokenExpired(adminToken)).thenReturn(false);
|
||||
when(jwtUtil.getUserTypeFromToken(adminToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
assert exchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
|
||||
verify(chain, never()).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonPrefixedPath_NoUserTypeCheck() {
|
||||
String adminToken = "admin.jwt.token";
|
||||
// /api/users 不以 /api/admin/ 或 /api/member/ 开头,不做 userType 校验
|
||||
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken)
|
||||
.build();
|
||||
exchange = MockServerWebExchange.from(request);
|
||||
|
||||
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
when(jwtUtil.validateToken(adminToken)).thenReturn(true);
|
||||
when(jwtUtil.isTokenExpired(adminToken)).thenReturn(false);
|
||||
when(jwtUtil.getUsernameFromToken(adminToken)).thenReturn("admin");
|
||||
when(jwtUtil.getUserIdFromToken(adminToken)).thenReturn(1L);
|
||||
when(jwtUtil.getUserTypeFromToken(adminToken)).thenReturn("ADMIN");
|
||||
|
||||
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
|
||||
.filter(exchange, chain);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
||||
verify(chain).filter(any(ServerWebExchange.class));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -47,10 +47,10 @@ public class SecurityConfig {
|
||||
.addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
|
||||
.addFilterAfter(operationLogWebFilter, SecurityWebFiltersOrder.AUTHORIZATION)
|
||||
.authorizeExchange(spec -> {
|
||||
spec.pathMatchers("/api/auth/**").permitAll()
|
||||
spec.pathMatchers("/api/admin/auth/**").permitAll()
|
||||
.pathMatchers("/api/member/auth/**").permitAll()
|
||||
.pathMatchers("/api/public/**").permitAll()
|
||||
.pathMatchers("/ws/**").permitAll()
|
||||
.pathMatchers("/**").permitAll()
|
||||
.pathMatchers("/actuator/**").permitAll();
|
||||
|
||||
if (isDevOrTest) {
|
||||
@@ -60,7 +60,7 @@ public class SecurityConfig {
|
||||
.pathMatchers("/v3/api-docs/**").permitAll()
|
||||
.pathMatchers("/swagger-resources/**").permitAll()
|
||||
.pathMatchers("/webjars/**").permitAll()
|
||||
.pathMatchers("/api/diagnostic/**").permitAll();
|
||||
.pathMatchers("/api/admin/diagnostic/**").permitAll();
|
||||
logger.info("SecurityConfig: Swagger路径和诊断端点已放行");
|
||||
}
|
||||
|
||||
|
||||
+19
@@ -20,6 +20,9 @@ public class AuthResponse {
|
||||
@Schema(description = "用户名", example = "admin")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "用户类型", example = "ADMIN")
|
||||
private String userType;
|
||||
|
||||
public AuthResponse() {
|
||||
}
|
||||
|
||||
@@ -27,6 +30,14 @@ public class AuthResponse {
|
||||
this.token = token;
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.userType = "ADMIN";
|
||||
}
|
||||
|
||||
public AuthResponse(String token, Long userId, String username, String userType) {
|
||||
this.token = token;
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.userType = userType;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
@@ -52,4 +63,12 @@ public class AuthResponse {
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getUserType() {
|
||||
return userType;
|
||||
}
|
||||
|
||||
public void setUserType(String userType) {
|
||||
this.userType = userType;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -133,8 +133,9 @@ public class SysAuthHandler {
|
||||
.generateToken(
|
||||
user.getUsername(),
|
||||
user.getId(),
|
||||
roleKeys);
|
||||
logger.info("用户登录成功: username={}, userId={}, roles={}",
|
||||
roleKeys,
|
||||
"ADMIN");
|
||||
logger.info("用户登录成功: username={}, userId={}, roles={}, userType=ADMIN",
|
||||
user.getUsername(),
|
||||
user.getId(),
|
||||
roleKeys);
|
||||
@@ -146,7 +147,8 @@ public class SysAuthHandler {
|
||||
AuthResponse response = new AuthResponse(
|
||||
token,
|
||||
user.getId(),
|
||||
user.getUsername());
|
||||
user.getUsername(),
|
||||
"ADMIN");
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(response);
|
||||
});
|
||||
|
||||
+4
@@ -37,6 +37,7 @@ public class JwtAuthenticationFilter implements WebFilter {
|
||||
String username = jwtTokenProvider.getUsernameFromToken(token);
|
||||
jwtTokenProvider.getUserIdFromToken(token);
|
||||
List<String> roles = jwtTokenProvider.getRolesFromToken(token);
|
||||
String userType = jwtTokenProvider.getUserTypeFromToken(token);
|
||||
|
||||
List<SimpleGrantedAuthority> authorities = roles.stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
|
||||
@@ -53,6 +54,9 @@ public class JwtAuthenticationFilter implements WebFilter {
|
||||
authorities
|
||||
);
|
||||
|
||||
// 将 userType 存入 authentication details,供后续 AuthUtil 使用
|
||||
authentication.setDetails(userType);
|
||||
|
||||
return chain.filter(exchange)
|
||||
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
|
||||
}
|
||||
|
||||
+10
-11
@@ -32,24 +32,19 @@ public class JwtTokenProvider {
|
||||
}
|
||||
|
||||
public String generateToken(String username, Long userId) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()))
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
return generateToken(username, userId, java.util.Collections.emptyList(), "ADMIN");
|
||||
}
|
||||
|
||||
public String generateToken(String username, Long userId, java.util.List<String> roles) {
|
||||
return generateToken(username, userId, roles, "ADMIN");
|
||||
}
|
||||
|
||||
public String generateToken(String username, Long userId, java.util.List<String> roles, String userType) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
claims.put("roles", roles);
|
||||
claims.put("userType", userType);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
@@ -85,6 +80,10 @@ public class JwtTokenProvider {
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
|
||||
public String getUserTypeFromToken(String token) {
|
||||
return getClaimsFromToken(token).get("userType", String.class);
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
getClaimsFromToken(token);
|
||||
|
||||
+35
-1
@@ -29,4 +29,38 @@ public class AuthUtil {
|
||||
if (jwtTokenProvider.getUserIdFromToken(token) <= 0L) throw new IllegalArgumentException("ID无效");
|
||||
return jwtTokenProvider.getUserIdFromToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 ADMIN 用户 ID,校验 userType 必须为 ADMIN
|
||||
*/
|
||||
public Long getAdminUserIdOrThrow(ServerRequest request) {
|
||||
String token = extractToken(request);
|
||||
if (token == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "缺少 Token");
|
||||
if (!jwtTokenProvider.validateToken(token)) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token 无效或已过期");
|
||||
String userType = jwtTokenProvider.getUserTypeFromToken(token);
|
||||
if (!"ADMIN".equals(userType)) {
|
||||
log.warn("非管理员用户尝试访问管理端接口, userType={}", userType);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "无权访问管理端接口");
|
||||
}
|
||||
Long userId = jwtTokenProvider.getUserIdFromToken(token);
|
||||
if (userId <= 0L) throw new IllegalArgumentException("ID无效");
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 MEMBER 用户 ID,校验 userType 必须为 MEMBER
|
||||
*/
|
||||
public Long getMemberUserIdOrThrow(ServerRequest request) {
|
||||
String token = extractToken(request);
|
||||
if (token == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "缺少 Token");
|
||||
if (!jwtTokenProvider.validateToken(token)) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token 无效或已过期");
|
||||
String userType = jwtTokenProvider.getUserTypeFromToken(token);
|
||||
if (!"MEMBER".equals(userType)) {
|
||||
log.warn("非会员用户尝试访问会员接口, userType={}", userType);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "无权访问会员接口");
|
||||
}
|
||||
Long userId = jwtTokenProvider.getUserIdFromToken(token);
|
||||
if (userId <= 0L) throw new IllegalArgumentException("ID无效");
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -80,7 +80,7 @@ class SysAuthHandlerTest {
|
||||
// 配置密码编码器Mock来验证密码
|
||||
when(passwordEncoder.matches(rawPassword, realEncodedPassword)).thenReturn(true);
|
||||
|
||||
when(jwtTokenProvider.generateToken(eq("testuser"), eq(1L), anyList())).thenReturn("test_token");
|
||||
when(jwtTokenProvider.generateToken(eq("testuser"), eq(1L), anyList(), eq("ADMIN"))).thenReturn("test_token");
|
||||
|
||||
// 使用测试数据工厂创建角色
|
||||
SysRole mockRole = TestDataFactory.createUserRole();
|
||||
@@ -103,7 +103,7 @@ class SysAuthHandlerTest {
|
||||
.verifyComplete();
|
||||
|
||||
verify(userService).findByUsername("testuser");
|
||||
verify(jwtTokenProvider).generateToken(eq("testuser"), eq(1L), anyList());
|
||||
verify(jwtTokenProvider).generateToken(eq("testuser"), eq(1L), anyList(), eq("ADMIN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+48
@@ -108,4 +108,52 @@ class JwtTokenProviderTest {
|
||||
|
||||
assertThat(isValid).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateTokenWithUserType() {
|
||||
when(jwtProperties.getSecret()).thenReturn("test-secret-key-for-testing-purposes-only-1234567890");
|
||||
when(jwtProperties.getExpiration()).thenReturn(3600000L);
|
||||
|
||||
String token = jwtTokenProvider.generateToken("testuser", 1L, java.util.List.of("admin"), "ADMIN");
|
||||
|
||||
assertThat(token).isNotNull();
|
||||
assertThat(token).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetUserTypeFromToken() {
|
||||
when(jwtProperties.getSecret()).thenReturn("test-secret-key-for-testing-purposes-only-1234567890");
|
||||
when(jwtProperties.getExpiration()).thenReturn(3600000L);
|
||||
|
||||
String token = jwtTokenProvider.generateToken("testuser", 1L, java.util.List.of("admin"), "ADMIN");
|
||||
|
||||
String userType = jwtTokenProvider.getUserTypeFromToken(token);
|
||||
|
||||
assertThat(userType).isEqualTo("ADMIN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetUserTypeFromToken_Member() {
|
||||
when(jwtProperties.getSecret()).thenReturn("test-secret-key-for-testing-purposes-only-1234567890");
|
||||
when(jwtProperties.getExpiration()).thenReturn(3600000L);
|
||||
|
||||
String token = jwtTokenProvider.generateToken("123", 123L, java.util.List.of(), "MEMBER");
|
||||
|
||||
String userType = jwtTokenProvider.getUserTypeFromToken(token);
|
||||
|
||||
assertThat(userType).isEqualTo("MEMBER");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetUserTypeFromToken_DefaultIsAdmin() {
|
||||
when(jwtProperties.getSecret()).thenReturn("test-secret-key-for-testing-purposes-only-1234567890");
|
||||
when(jwtProperties.getExpiration()).thenReturn(3600000L);
|
||||
|
||||
// 使用旧的两参数方法生成的 token 默认 userType 为 ADMIN
|
||||
String token = jwtTokenProvider.generateToken("testuser", 1L);
|
||||
|
||||
String userType = jwtTokenProvider.getUserTypeFromToken(token);
|
||||
|
||||
assertThat(userType).isEqualTo("ADMIN");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user