我来详细讲解 Spring Boot 3.x (Spring Boot 4的早期版本)整合 Spring Security 6 和 MyBatis Plus 的完整权限框架实现。
Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!
1. 项目依赖 (pom.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-security-mybatisplus</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.4</mybatis-plus.version>
</properties>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2. 数据库配置
Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!
2.1 application.yml
yaml
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
druid:
initial-size: 5
min-idle: 5
max-active: 20
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto
mapper-locations: classpath:mapper/*.xml
jwt:
secret: your-jwt-secret-key-for-spring-boot-security-demo-2024
expiration: 86400000 # 24小时
token-header: Authorization
token-prefix: "Bearer "
logging:
level:
com.example.mapper: debug
3. 实体类设计
Spring Cloud全栈实战:手撸企业级项目,从入门到架构师!
3.1 用户实体
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
@TableName("sys_user")
public class User implements UserDetails {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("username")
private String username;
@TableField("password")
private String password;
@TableField("nickname")
private String nickname;
@TableField("email")
private String email;
@TableField("phone")
private String phone;
@TableField("avatar")
private String avatar;
@TableField("status")
private Integer status; // 0-禁用 1-正常
@TableField("last_login_time")
private LocalDateTime lastLoginTime;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
@TableField("deleted")
private Integer deleted;
// 非数据库字段
@TableField(exist = false)
private List<String> roles;
@TableField(exist = false)
private List<String> permissions;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return status == 1;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return status == 1;
}
}
3.2 角色实体
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_role")
public class Role {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("role_code")
private String roleCode;
@TableField("role_name")
private String roleName;
@TableField("description")
private String description;
@TableField("status")
private Integer status;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
3.3 权限实体
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sys_permission")
public class Permission {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("permission_code")
private String permissionCode;
@TableField("permission_name")
private String permissionName;
@TableField("url")
private String url;
@TableField("method")
private String method; // GET, POST, PUT, DELETE, ALL
@TableField("parent_id")
private Long parentId;
@TableField("type")
private Integer type; // 0-目录 1-菜单 2-按钮
@TableField("icon")
private String icon;
@TableField("sort")
private Integer sort;
@TableField("status")
private Integer status;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
3.4 用户角色关系
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_user_role")
public class UserRole {
private Long userId;
private Long roleId;
}
3.5 角色权限关系
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("sys_role_permission")
public class RolePermission {
private Long roleId;
private Long permissionId;
}
4. Mapper 接口
4.1 UserMapper
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT r.role_code FROM sys_role r " +
"INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
"WHERE ur.user_id = #{userId} AND r.status = 1")
List<String> selectRolesByUserId(@Param("userId") Long userId);
@Select("SELECT p.permission_code FROM sys_permission p " +
"INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
"INNER JOIN sys_user_role ur ON rp.role_id = ur.role_id " +
"WHERE ur.user_id = #{userId} AND p.status = 1 " +
"UNION " +
"SELECT p.permission_code FROM sys_permission p " +
"INNER JOIN sys_user_permission up ON p.id = up.permission_id " +
"WHERE up.user_id = #{userId} AND p.status = 1")
List<String> selectPermissionsByUserId(@Param("userId") Long userId);
@Select("SELECT * FROM sys_user WHERE username = #{username} AND deleted = 0")
User selectByUsername(@Param("username") String username);
}
4.2 RoleMapper
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.Role;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}
4.3 PermissionMapper
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
@Select("SELECT p.* FROM sys_permission p " +
"INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
"WHERE rp.role_id = #{roleId} AND p.status = 1 " +
"ORDER BY p.sort")
List<Permission> selectByRoleId(@Param("roleId") Long roleId);
}
5. 自定义 UserDetailsService
java
package com.example.service;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserMapper userMapper;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// 查询用户信息
User user = userMapper.selectByUsername(username);
if (user == null) {
log.error("用户不存在: {}", username);
throw new UsernameNotFoundException("用户不存在");
}
// 查询用户角色
List<String> roles = userMapper.selectRolesByUserId(user.getId());
user.setRoles(roles);
// 查询用户权限
List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());
user.setPermissions(permissions);
log.info("用户登录成功: {}, 角色: {}, 权限: {}",
username, roles, permissions);
return user;
}
}
6. Spring Security 配置
6.1 JWT工具类
java
package com.example.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Slf4j
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private SecretKey key;
@PostConstruct
public void init() {
this.key = Keys.hmacShaKeyFor(secret.getBytes());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
}
6.2 JWT认证过滤器
java
package com.example.filter;
import com.example.utils.JwtTokenUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsService userDetailsService;
@Value("${jwt.token-header}")
private String tokenHeader;
@Value("${jwt.token-prefix}")
private String tokenPrefix;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader(tokenHeader);
if (StringUtils.hasText(authHeader) && authHeader.startsWith(tokenPrefix)) {
String token = authHeader.substring(tokenPrefix.length());
try {
String username = jwtTokenUtil.extractUsername(token);
if (username != null &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("用户认证成功: {}, URI: {}", username, request.getRequestURI());
}
}
} catch (Exception e) {
log.error("JWT认证失败: {}", e.getMessage());
}
}
filterChain.doFilter(request, response);
}
}
6.3 Security配置类
java
package com.example.config;
import com.example.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用CSRF
.csrf(AbstractHttpConfigurer::disable)
// 配置CORS
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// 配置权限规则
.authorizeHttpRequests(auth -> auth
// 公开接口
.requestMatchers(
"/auth/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/doc.html",
"/webjars/**",
"/favicon.ico"
).permitAll()
// 管理员接口
.requestMatchers("/admin/**").hasRole("ADMIN")
// 用户接口
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
// 需要认证的接口
.anyRequest().authenticated()
)
// 配置JWT过滤器
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
// 配置异常处理
.exceptionHandling(exception -> exception
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"message\":\"未认证或token过期\"}");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(403);
response.getWriter().write("{\"code\":403,\"message\":\"权限不足\"}");
})
)
// 无状态会话
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
}
7. 认证控制器
java
package com.example.controller;
import com.example.entity.User;
import com.example.service.UserService;
import com.example.utils.JwtTokenUtil;
import com.example.vo.request.LoginRequest;
import com.example.vo.request.RegisterRequest;
import com.example.vo.response.ApiResponse;
import com.example.vo.response.LoginResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenUtil jwtTokenUtil;
private final UserService userService;
@PostMapping("/login")
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
User user = (User) authentication.getPrincipal();
String token = jwtTokenUtil.generateToken(user);
// 更新最后登录时间
userService.updateLastLoginTime(user.getId());
LoginResponse response = LoginResponse.builder()
.userId(user.getId())
.username(user.getUsername())
.nickname(user.getNickname())
.email(user.getEmail())
.avatar(user.getAvatar())
.roles(user.getRoles())
.permissions(user.getPermissions())
.token(token)
.expiresIn(jwtTokenUtil.extractExpiration(token))
.build();
return ApiResponse.success("登录成功", response);
} catch (Exception e) {
log.error("登录失败: {}", e.getMessage());
return ApiResponse.error(401, "用户名或密码错误");
}
}
@PostMapping("/register")
public ApiResponse<?> register(@Valid @RequestBody RegisterRequest request) {
if (userService.existsByUsername(request.getUsername())) {
return ApiResponse.error(400, "用户名已存在");
}
if (userService.existsByEmail(request.getEmail())) {
return ApiResponse.error(400, "邮箱已存在");
}
userService.registerUser(request);
return ApiResponse.success("注册成功");
}
@PostMapping("/logout")
public ApiResponse<?> logout() {
SecurityContextHolder.clearContext();
return ApiResponse.success("退出成功");
}
@GetMapping("/info")
public ApiResponse<?> getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return ApiResponse.error(401, "未登录");
}
User user = (User) authentication.getPrincipal();
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("userId", user.getId());
userInfo.put("username", user.getUsername());
userInfo.put("nickname", user.getNickname());
userInfo.put("email", user.getEmail());
userInfo.put("avatar", user.getAvatar());
userInfo.put("roles", user.getRoles());
userInfo.put("permissions", user.getPermissions());
return ApiResponse.success("获取成功", userInfo);
}
@PostMapping("/refresh")
public ApiResponse<Map<String, String>> refreshToken(@RequestHeader("Authorization") String token) {
if (token != null && token.startsWith("Bearer ")) {
String authToken = token.substring(7);
try {
String username = jwtTokenUtil.extractUsername(authToken);
User user = (User) userService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, user)) {
String newToken = jwtTokenUtil.generateToken(user);
Map<String, String> data = new HashMap<>();
data.put("token", newToken);
data.put("expiresIn", jwtTokenUtil.extractExpiration(newToken).toString());
return ApiResponse.success("刷新成功", data);
}
} catch (Exception e) {
log.error("刷新token失败: {}", e.getMessage());
}
}
return ApiResponse.error(401, "token无效或已过期");
}
}
8. 业务Service
java
package com.example.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.vo.request.RegisterRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Collections;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService extends ServiceImpl<UserMapper, User> {
private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;
public boolean existsByUsername(String username) {
return lambdaQuery()
.eq(User::getUsername, username)
.eq(User::getDeleted, 0)
.exists();
}
public boolean existsByEmail(String email) {
return lambdaQuery()
.eq(User::getEmail, email)
.eq(User::getDeleted, 0)
.exists();
}
@Transactional(rollbackFor = Exception.class)
public void registerUser(RegisterRequest request) {
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setNickname(request.getNickname());
user.setEmail(request.getEmail());
user.setPhone(request.getPhone());
user.setStatus(1);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setDeleted(0);
save(user);
// 分配默认角色
assignDefaultRole(user.getId());
}
private void assignDefaultRole(Long userId) {
// 这里可以根据业务需求分配默认角色
// 例如:userMapper.insertUserRole(userId, 2L); // 2为普通用户角色ID
}
public void updateLastLoginTime(Long userId) {
lambdaUpdate()
.eq(User::getId, userId)
.set(User::getLastLoginTime, LocalDateTime.now())
.update();
}
@Transactional(readOnly = true)
public User getUserWithRoles(Long userId) {
User user = getById(userId);
if (user != null) {
user.setRoles(userMapper.selectRolesByUserId(userId));
user.setPermissions(userMapper.selectPermissionsByUserId(userId));
}
return user;
}
}
9. 响应VO对象
9.1 ApiResponse
java
package com.example.vo.response;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiResponse<T> implements Serializable {
private Integer code;
private String message;
private T data;
private Long timestamp;
public static <T> ApiResponse<T> success(String message, T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage(message);
response.setData(data);
response.setTimestamp(System.currentTimeMillis());
return response;
}
public static <T> ApiResponse<T> success(String message) {
return success(message, null);
}
public static <T> ApiResponse<T> success(T data) {
return success("操作成功", data);
}
public static <T> ApiResponse<T> error(Integer code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
response.setTimestamp(System.currentTimeMillis());
return response;
}
}
9.2 LoginResponse
java
package com.example.vo.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponse {
private Long userId;
private String username;
private String nickname;
private String email;
private String avatar;
private List<String> roles;
private List<String> permissions;
private String token;
private Date expiresIn;
}
10. 权限注解使用示例
java
package com.example.controller;
import com.example.vo.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/admin")
@Tag(name = "管理员管理")
public class AdminController {
@GetMapping("/users")
@Operation(summary = "获取用户列表")
@PreAuthorize("hasRole('ADMIN') and hasAuthority('user:list')")
public ApiResponse<?> getUsers() {
Map<String, Object> data = new HashMap<>();
data.put("users", "所有用户列表");
return ApiResponse.success(data);
}
@PostMapping("/users")
@Operation(summary = "创建用户")
@PreAuthorize("hasRole('ADMIN') and hasAuthority('user:create')")
public ApiResponse<?> createUser() {
return ApiResponse.success("创建用户成功");
}
@PutMapping("/users/{id}")
@Operation(summary = "更新用户")
@PreAuthorize("hasRole('ADMIN') and hasAuthority('user:update')")
public ApiResponse<?> updateUser(@PathVariable Long id) {
return ApiResponse.success("更新用户成功");
}
@DeleteMapping("/users/{id}")
@Operation(summary = "删除用户")
@PreAuthorize("hasRole('ADMIN') and hasAuthority('user:delete')")
public ApiResponse<?> deleteUser(@PathVariable Long id) {
return ApiResponse.success("删除用户成功");
}
}
11. 数据库表结构
sql
-- 用户表
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` tinyint DEFAULT '0' COMMENT '删除标记:0-未删除,1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_code` varchar(50) NOT NULL COMMENT '角色编码',
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`description` varchar(200) DEFAULT NULL COMMENT '描述',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 权限表
CREATE TABLE `sys_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`permission_code` varchar(100) NOT NULL COMMENT '权限编码',
`permission_name` varchar(50) NOT NULL COMMENT '权限名称',
`url` varchar(200) DEFAULT NULL COMMENT '接口路径',
`method` varchar(10) DEFAULT NULL COMMENT '请求方法',
`parent_id` bigint DEFAULT '0' COMMENT '父级ID',
`type` tinyint DEFAULT '1' COMMENT '类型:0-目录,1-菜单,2-按钮',
`icon` varchar(50) DEFAULT NULL COMMENT '图标',
`sort` int DEFAULT '0' COMMENT '排序',
`status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_permission_code` (`permission_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
-- 用户角色关系表
CREATE TABLE `sys_user_role` (
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关系表';
-- 角色权限关系表
CREATE TABLE `sys_role_permission` (
`role_id` bigint NOT NULL,
`permission_id` bigint NOT NULL,
PRIMARY KEY (`role_id`,`permission_id`),
KEY `idx_permission_id` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关系表';
-- 插入初始数据
INSERT INTO `sys_user` (`username`, `password`, `nickname`, `email`, `status`) VALUES
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lGSl8jK7qYpzqO', '管理员', 'admin@example.com', 1),
('user', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lGSl8jK7qYpzqO', '普通用户', 'user@example.com', 1);
INSERT INTO `sys_role` (`role_code`, `role_name`, `description`) VALUES
('ROLE_ADMIN', '管理员', '系统管理员'),
('ROLE_USER', '普通用户', '普通用户');
INSERT INTO `sys_permission` (`permission_code`, `permission_name`, `url`, `method`, `type`) VALUES
('user:list', '用户列表', '/api/admin/users', 'GET', 2),
('user:create', '创建用户', '/api/admin/users', 'POST', 2),
('user:update', '更新用户', '/api/admin/users/*', 'PUT', 2),
('user:delete', '删除用户', '/api/admin/users/*', 'DELETE', 2);
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES
(1, 1), -- admin用户拥有管理员角色
(2, 2); -- user用户拥有普通用户角色
INSERT INTO `sys_role_permission` (`role_id`, `permission_id`) VALUES
(1, 1), -- 管理员拥有所有权限
(1, 2),
(1, 3),
(1, 4);
12. 启动类
java
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@MapperScan("com.example.mapper")
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
13. 测试接口
13.1 注册用户
http
POST /api/auth/register
Content-Type: application/json
{
"username": "testuser",
"password": "123456",
"nickname": "测试用户",
"email": "test@example.com",
"phone": "13800138000"
}
13.2 登录获取Token
http
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "123456"
}
13.3 访问受保护接口
http
GET /api/admin/users
Authorization: Bearer {your-jwt-token}
14. 功能特点
- JWT认证:无状态token认证
- RBAC权限模型:基于角色的访问控制
- MyBatis Plus集成:简化数据库操作
- CORS支持:跨域资源共享
- 全局异常处理:统一异常响应
- 密码加密:BCrypt加密存储
- 权限注解:支持@PreAuthorize注解
- 自动填充:MyBatis Plus自动填充创建/更新时间
这个完整的框架包含了用户认证、权限管理、JWT令牌、数据库操作等完整功能,可以直接用于实际项目开发。