Skip to content

Commit d423a57

Browse files
authored
Merge pull request #101 from Boyuan-IT-Club/feat/rbac的权限管理模式
Feat/rbac的权限管理模式
2 parents 29e7d9f + 4c78b07 commit d423a57

File tree

80 files changed

+4688
-1094
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+4688
-1094
lines changed

.env.example

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# 项目配置
2+
PROJECT_NAME=official
3+
DOCKERHUB_USERNAME=boyuanclub
4+
APP_PORT=8080
5+
6+
# 数据库配置
7+
DB_ROOT_PASSWORD=root
8+
DB_NAME=official
9+
DB_USERNAME=root
10+
DB_PASSWORD=root
11+
MYSQL_PORT=3306
12+
13+
# Redis配置
14+
REDIS_PORT=6379
15+
REDIS_PASSWORD=
16+
17+
# JWT配置
18+
JWT_SECRET=)fFQ82L8dU]t3C?.HB^;x,xYm9Y<aK21<|K|o0D6(c!X}8[cUZ_l=&0Y('#/K}n_
19+
JWT_EXPIRATION=86400000
20+
21+
# 邮件服务配置
22+
MAIL_HOST=smtp.feishu.cn
23+
MAIL_PORT=465
24+
MAIL_USERNAME=your-email@example.com
25+
MAIL_PASSWORD=your-email-password
26+
27+
# 短信服务配置
28+
SMS_ACCESS_KEY=your-sms-access-key
29+
SMS_SECRET_KEY=your-sms-secret-key
30+
SMS_TEMPLATE_ID_VERIFICATION=your-sms-template-id
31+
SMS_REGION_ID=your-sms-region-id

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,6 @@ build/
3939
.env
4040
.env.local
4141
.env.*.local
42-
!*.template
42+
!*.template
43+
.trae
44+
文档

pom.xml

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.springframework.boot</groupId>
77
<artifactId>spring-boot-starter-parent</artifactId>
8-
<version>3.5.3</version>
8+
<version>3.2.1</version>
99
<relativePath/> <!-- lookup parent from repository -->
1010
</parent>
1111
<groupId>club.boyuan</groupId>
@@ -32,12 +32,6 @@
3232

3333

3434
<dependencies>
35-
<dependency>
36-
<groupId>org.mybatis.spring.boot</groupId>
37-
<artifactId>mybatis-spring-boot-starter</artifactId>
38-
<version>3.0.3</version>
39-
</dependency>
40-
4135
<dependency>
4236
<groupId>org.springframework.boot</groupId>
4337
<artifactId>spring-boot-starter-web</artifactId>
@@ -50,10 +44,21 @@
5044
</dependency>
5145

5246
<dependency>
53-
<groupId>org.mybatis</groupId>
54-
<artifactId>mybatis</artifactId>
55-
<version>3.5.15</version>
47+
<groupId>com.baomidou</groupId>
48+
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
49+
<version>3.5.9</version>
50+
</dependency>
51+
<dependency>
52+
<groupId>com.baomidou</groupId>
53+
<artifactId>mybatis-plus-extension</artifactId>
54+
<version>3.5.9</version>
5655
</dependency>
56+
<dependency>
57+
<groupId>com.baomidou</groupId>
58+
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
59+
<version>3.5.9</version>
60+
</dependency>
61+
5762
<dependency>
5863
<groupId>com.mysql</groupId>
5964
<artifactId>mysql-connector-j</artifactId>
@@ -69,11 +74,16 @@
6974
<artifactId>spring-boot-starter-validation</artifactId>
7075
</dependency>
7176

72-
<!-- 安全依赖 -->
77+
<!-- Spring Security Test -->
7378
<dependency>
7479
<groupId>org.springframework.boot</groupId>
7580
<artifactId>spring-boot-starter-security</artifactId>
7681
</dependency>
82+
<dependency>
83+
<groupId>org.springframework.security</groupId>
84+
<artifactId>spring-security-test</artifactId>
85+
<scope>test</scope>
86+
</dependency>
7787
<!-- Redis依赖 -->
7888
<dependency>
7989
<groupId>org.springframework.boot</groupId>
@@ -137,6 +147,16 @@
137147
<groupId>com.fasterxml.jackson.datatype</groupId>
138148
<artifactId>jackson-datatype-jsr310</artifactId>
139149
</dependency>
150+
151+
<!-- Apache Commons Lang3 -->
152+
<dependency>
153+
<groupId>org.apache.commons</groupId>
154+
<artifactId>commons-lang3</artifactId>
155+
<version>3.14.0</version>
156+
</dependency>
157+
158+
159+
140160
</dependencies>
141161

142162
<build>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package club.boyuan.official.config;
2+
3+
import com.baomidou.mybatisplus.annotation.DbType;
4+
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
5+
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
/**
10+
* MyBatis-Plus配置类
11+
*
12+
* 此配置类用于设置 MyBatis-Plus 的全局插件
13+
* 主要功能包括分页插件等
14+
*
15+
* @author zewang
16+
* @version 1.0
17+
* @date 2026-01-22 19:31
18+
* @since 2026
19+
*/
20+
@Configuration
21+
public class MyBatisPlusConfig {
22+
23+
// MybatisPlus在执行分页操作时,会被该拦截器拦截
24+
// 拦截器的作用 动态拼接where条件!!!
25+
@Bean
26+
public MybatisPlusInterceptor mybatisPlusInterceptor() {
27+
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
28+
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
29+
return interceptor;
30+
}
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package club.boyuan.official.config;
2+
3+
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
4+
import org.apache.ibatis.reflection.MetaObject;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.time.LocalDateTime;
8+
9+
/**
10+
* MyBatis-Plus自动填充处理器
11+
*
12+
* @author zewang
13+
* @version 1.0
14+
* @date 2026-01-22 19:31
15+
* @since 2026
16+
*/
17+
@Component
18+
public class MyMetaObjectHandler implements MetaObjectHandler {
19+
20+
/**
21+
* 插入数据时自动填充
22+
* @param metaObject 元对象
23+
*/
24+
@Override
25+
public void insertFill(MetaObject metaObject) {
26+
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
27+
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
28+
}
29+
30+
/**
31+
* 更新数据时自动填充
32+
* @param metaObject 元对象
33+
*/
34+
@Override
35+
public void updateFill(MetaObject metaObject) {
36+
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
37+
}
38+
}

src/main/java/club/boyuan/official/config/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
44
import org.springframework.context.annotation.Bean;
55
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
67
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
78
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
89
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -24,6 +25,7 @@
2425
*/
2526
@Configuration
2627
@EnableWebSecurity
28+
@EnableMethodSecurity
2729
public class SecurityConfig {
2830

2931
private final JwtAuthenticationFilter jwtAuthenticationFilter;

src/main/java/club/boyuan/official/controller/AdminController.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import org.springframework.http.HttpStatus;
1616
import org.springframework.http.ResponseEntity;
17+
import org.springframework.security.access.prepost.PreAuthorize;
1718
import org.springframework.web.bind.annotation.RestController;
1819
import org.springframework.web.bind.annotation.*;
1920
import org.springframework.data.domain.Pageable;
@@ -22,8 +23,8 @@
2223

2324
import java.util.HashMap;
2425
import java.util.List;
26+
import club.boyuan.official.utils.PermissionUtils;
2527
import java.util.Map;
26-
import org.springframework.web.server.ResponseStatusException;
2728
import jakarta.servlet.http.HttpServletRequest;
2829
import club.boyuan.official.utils.JwtTokenUtil;
2930

@@ -79,8 +80,8 @@ private void checkAdminRole() {
7980
throw new BusinessException(BusinessExceptionEnum.AUTHENTICATION_FAILED, "用户不存在");
8081
}
8182

82-
if (!User.ROLE_ADMIN.equals(user.getRole())) {
83-
logger.warn("权限验证失败:用户ID为{}的用户角色为{},不是管理员", userId, user.getRole());
83+
if (!PermissionUtils.hasPermission(user, "admin:manage")) {
84+
logger.warn("权限验证失败:用户ID为{}的用户没有管理员权限", userId);
8485
throw new BusinessException(BusinessExceptionEnum.PERMISSION_DENIED, "需要管理员权限才能执行此操作");
8586
}
8687

@@ -101,10 +102,9 @@ private void checkAdminRole() {
101102
* @return 添加结果
102103
*/
103104
@PostMapping("/users")
105+
@PreAuthorize("hasAuthority('admin:manage')")
104106
public ResponseEntity<ResponseMessage<?>> addUser(@RequestBody UserDTO userDTO) {
105107
try {
106-
checkAdminRole();
107-
108108
// 检查用户名是否已存在
109109
User existingUser = userService.getUserByUsername(userDTO.getUsername());
110110
if (existingUser != null) {
@@ -141,11 +141,9 @@ public ResponseEntity<ResponseMessage<?>> addUser(@RequestBody UserDTO userDTO)
141141
* @return 操作结果
142142
*/
143143
@PostMapping("/users/{userId}/grant-admin")
144+
@PreAuthorize("hasAuthority('admin:manage')")
144145
public ResponseEntity<ResponseMessage<?>> grantAdminPermission(@PathVariable Integer userId) {
145146
try {
146-
// 检查管理员权限
147-
checkAdminRole();
148-
149147
// 获取当前登录的管理员信息
150148
String token = getTokenFromHeader();
151149
String adminUsername = jwtTokenUtil.extractUsername(token);
@@ -161,7 +159,7 @@ public ResponseEntity<ResponseMessage<?>> grantAdminPermission(@PathVariable Int
161159
}
162160

163161
// 检查用户是否已经是管理员
164-
if (User.ROLE_ADMIN.equals(targetUser.getRole())) {
162+
if (PermissionUtils.hasPermission(targetUser, "admin:manage")) {
165163
logger.warn("管理员 {} 尝试为已是管理员的用户 {} 授权", adminUsername, targetUser.getUsername());
166164
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
167165
.body(ResponseMessage.error(400, "用户已经是管理员"));
@@ -170,7 +168,8 @@ public ResponseEntity<ResponseMessage<?>> grantAdminPermission(@PathVariable Int
170168
// 更新用户角色为管理员
171169
UserDTO userDTO = new UserDTO();
172170
userDTO.setUserId(userId);
173-
userDTO.setRole(User.ROLE_ADMIN);
171+
// 注意:UserDTO已经没有role字段,这里的代码需要在后续重构中修改
172+
// 实际的管理员权限授予应该通过调用UserRoleService来实现
174173
userService.edit(userDTO);
175174

176175
logger.info("管理员 {} 成功为用户 {} 授予管理员权限", adminUsername, targetUser.getUsername());
@@ -199,6 +198,7 @@ public ResponseEntity<ResponseMessage<?>> grantAdminPermission(@PathVariable Int
199198
* 支持分页和条件查询
200199
*/
201200
@GetMapping("/users")
201+
@PreAuthorize("hasAuthority('admin:manage')")
202202
public ResponseEntity<ResponseMessage<?>> getUsers(
203203
@RequestParam(required = false) String role,
204204
@RequestParam(required = false) String dept,
@@ -233,6 +233,7 @@ public ResponseEntity<ResponseMessage<?>> getUsers(
233233
* @return 更新结果
234234
*/
235235
@PutMapping("/users/{userId}/status")
236+
@PreAuthorize("hasAuthority('admin:manage')")
236237
public ResponseEntity<ResponseMessage<?>> updateUserStatus(
237238
@PathVariable Integer userId,
238239
@RequestBody Map<String, String> statusRequest) {
@@ -270,6 +271,7 @@ public ResponseEntity<ResponseMessage<?>> updateUserStatus(
270271
* @return 更新结果
271272
*/
272273
@PutMapping("/users/{userId}/freeze")
274+
@PreAuthorize("hasAuthority('admin:manage')")
273275
public ResponseEntity<ResponseMessage<?>> freezeUser(
274276
@PathVariable Integer userId,
275277
@RequestBody Map<String, String> statusRequest) {
@@ -309,6 +311,7 @@ public ResponseEntity<ResponseMessage<?>> freezeUser(
309311
* @return 更新结果
310312
*/
311313
@PutMapping("/users/{userId}/membership")
314+
@PreAuthorize("hasAuthority('admin:manage')")
312315
public ResponseEntity<ResponseMessage<?>> updateUserMembership(
313316
@PathVariable Integer userId,
314317
@RequestBody Map<String, Boolean> membershipRequest) {
@@ -347,6 +350,7 @@ public ResponseEntity<ResponseMessage<?>> updateUserMembership(
347350
* @return 更新结果
348351
*/
349352
@PutMapping("/users/batch-status")
353+
@PreAuthorize("hasAuthority('admin:manage')")
350354
public ResponseEntity<ResponseMessage<?>> batchUpdateUserStatus(
351355
@RequestBody Map<String, Object> statusRequest) {
352356
try {
@@ -396,6 +400,7 @@ public ResponseEntity<ResponseMessage<?>> batchUpdateUserStatus(
396400
* @return 更新结果
397401
*/
398402
@PutMapping("/users/batch-dept")
403+
@PreAuthorize("hasAuthority('admin:manage')")
399404
public ResponseEntity<ResponseMessage<?>> batchUpdateUserDept(
400405
@RequestBody Map<String, Object> deptRequest) {
401406
try {
@@ -444,6 +449,7 @@ public ResponseEntity<ResponseMessage<?>> batchUpdateUserDept(
444449
* @return 更新结果
445450
*/
446451
@PutMapping("/users/batch-membership")
452+
@PreAuthorize("hasAuthority('admin:manage')")
447453
public ResponseEntity<ResponseMessage<?>> batchUpdateUserMembership(
448454
@RequestBody Map<String, Object> membershipRequest) {
449455
try {
@@ -493,6 +499,7 @@ public ResponseEntity<ResponseMessage<?>> batchUpdateUserMembership(
493499
* @return 清理结果
494500
*/
495501
@PostMapping("/cache/clear")
502+
@PreAuthorize("hasAuthority('admin:manage')")
496503
public ResponseEntity<ResponseMessage<?>> clearCache(
497504
@RequestBody(required = false) Map<String, String> cacheRequest) {
498505
try {

0 commit comments

Comments
 (0)