feat: 打造若依 (RuoYi) 规范的高品质 RBAC 用户权限体系,内置 admin/admin123 超级管理员与测试读者,支持登录注册、个人中心与用户管理
Build and Deploy Blog / deploy (push) Successful in 38s
Build and Deploy Blog / deploy (push) Successful in 38s
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package com.example.blog.common;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JwtUtils {
|
||||
|
||||
private static final String SECRET = "XiaoDong-RuoYi-Secret-Token-Key-2026-SuperSecureJWT!@#";
|
||||
private static final long EXPIRATION_MS = 7L * 24 * 60 * 60 * 1000; // 7 天有效
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 生成 JWT Token
|
||||
*/
|
||||
public static String createToken(Long userId, String username, String nickname, String role) {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
long exp = now + EXPIRATION_MS;
|
||||
|
||||
Map<String, Object> header = new HashMap<>();
|
||||
header.put("alg", "HS256");
|
||||
header.put("typ", "JWT");
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("userId", userId);
|
||||
payload.put("username", username);
|
||||
payload.put("nickname", nickname);
|
||||
payload.put("role", role);
|
||||
payload.put("iat", now / 1000);
|
||||
payload.put("exp", exp / 1000);
|
||||
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString(objectMapper.writeValueAsString(header).getBytes(StandardCharsets.UTF_8));
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding()
|
||||
.encodeToString(objectMapper.writeValueAsString(payload).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
String content = encodedHeader + "." + encodedPayload;
|
||||
String signature = sign(content, SECRET);
|
||||
|
||||
return content + "." + signature;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("生成 Token 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并校验 Token,返回 Claims
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, Object> parseToken(String token) {
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (token.startsWith("Bearer ")) {
|
||||
token = token.substring(7).trim();
|
||||
}
|
||||
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String content = parts[0] + "." + parts[1];
|
||||
String expectedSignature = sign(content, SECRET);
|
||||
if (!expectedSignature.equals(parts[2])) {
|
||||
return null; // 签名不匹配
|
||||
}
|
||||
|
||||
byte[] payloadBytes = Base64.getUrlDecoder().decode(parts[1]);
|
||||
Map<String, Object> claims = objectMapper.readValue(payloadBytes, Map.class);
|
||||
|
||||
// 检查过期时间
|
||||
Number exp = (Number) claims.get("exp");
|
||||
if (exp != null && exp.longValue() * 1000 < System.currentTimeMillis()) {
|
||||
return null; // Token 已过期
|
||||
}
|
||||
|
||||
return claims;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String sign(String data, String secret) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
mac.init(secretKey);
|
||||
byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(rawHmac);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.example.blog.common;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SecurityUtils {
|
||||
|
||||
/**
|
||||
* 生成随机盐值
|
||||
*/
|
||||
public static String generateSalt() {
|
||||
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* SHA-256 + 盐值加密密码 (若依风格安全加盐散列)
|
||||
*/
|
||||
public static String encryptPassword(String password, String salt) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
String salted = password + "{" + salt + "}";
|
||||
byte[] bytes = md.digest(salted.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("SHA-256 算法未找到", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验密码
|
||||
*/
|
||||
public static boolean matchesPassword(String rawPassword, String encryptedPassword, String salt) {
|
||||
String hash = encryptPassword(rawPassword, salt);
|
||||
return hash.equalsIgnoreCase(encryptedPassword);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.example.blog.config;
|
||||
|
||||
import com.example.blog.common.SecurityUtils;
|
||||
import com.example.blog.entity.Category;
|
||||
import com.example.blog.entity.Post;
|
||||
import com.example.blog.entity.SysUser;
|
||||
import com.example.blog.repository.CategoryRepository;
|
||||
import com.example.blog.repository.PostRepository;
|
||||
import com.example.blog.repository.SysUserRepository;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -15,14 +18,21 @@ public class DataInitializer implements CommandLineRunner {
|
||||
|
||||
private final PostRepository postRepository;
|
||||
private final CategoryRepository categoryRepository;
|
||||
private final SysUserRepository sysUserRepository;
|
||||
|
||||
public DataInitializer(PostRepository postRepository, CategoryRepository categoryRepository) {
|
||||
public DataInitializer(PostRepository postRepository,
|
||||
CategoryRepository categoryRepository,
|
||||
SysUserRepository sysUserRepository) {
|
||||
this.postRepository = postRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.sysUserRepository = sysUserRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
// 0. 初始化系统内置若依风格用户 (admin / admin123 与测试读者)
|
||||
initSystemUsers();
|
||||
|
||||
// 1. 初始化分类
|
||||
if (categoryRepository.count() == 0) {
|
||||
List<Category> categories = Arrays.asList(
|
||||
@@ -113,4 +123,36 @@ public class DataInitializer implements CommandLineRunner {
|
||||
postRepository.saveAll(existingPosts);
|
||||
}
|
||||
}
|
||||
|
||||
private void initSystemUsers() {
|
||||
// 1. 初始化内置超级管理员 admin / admin123
|
||||
if (!sysUserRepository.existsByUsername("admin")) {
|
||||
SysUser admin = new SysUser();
|
||||
admin.setUsername("admin");
|
||||
String salt = SecurityUtils.generateSalt();
|
||||
admin.setSalt(salt);
|
||||
admin.setPassword(SecurityUtils.encryptPassword("admin123", salt));
|
||||
admin.setNickname("小东 (管理员)");
|
||||
admin.setEmail("j2570852518@gmail.com");
|
||||
admin.setAvatar("👨💻");
|
||||
admin.setRole("ADMIN");
|
||||
admin.setStatus("0");
|
||||
sysUserRepository.save(admin);
|
||||
}
|
||||
|
||||
// 2. 初始化内置测试访客读者 ry_user / user123
|
||||
if (!sysUserRepository.existsByUsername("ry_user")) {
|
||||
SysUser visitor = new SysUser();
|
||||
visitor.setUsername("ry_user");
|
||||
String salt = SecurityUtils.generateSalt();
|
||||
visitor.setSalt(salt);
|
||||
visitor.setPassword(SecurityUtils.encryptPassword("user123", salt));
|
||||
visitor.setNickname("若依读者");
|
||||
visitor.setEmail("visitor@example.com");
|
||||
visitor.setAvatar("🚀");
|
||||
visitor.setRole("USER");
|
||||
visitor.setStatus("0");
|
||||
sysUserRepository.save(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.example.blog.controller;
|
||||
|
||||
import com.example.blog.common.JwtUtils;
|
||||
import com.example.blog.common.Result;
|
||||
import com.example.blog.dto.LoginBody;
|
||||
import com.example.blog.dto.RegisterBody;
|
||||
import com.example.blog.service.SysUserService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class AuthController {
|
||||
|
||||
private final SysUserService sysUserService;
|
||||
|
||||
public AuthController(SysUserService sysUserService) {
|
||||
this.sysUserService = sysUserService;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public Result<Map<String, Object>> login(@RequestBody LoginBody loginBody, HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return Result.success(sysUserService.login(loginBody, ip));
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public Result<Void> register(@RequestBody RegisterBody registerBody) {
|
||||
sysUserService.register(registerBody);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@GetMapping("/info")
|
||||
public Result<Map<String, Object>> getInfo(@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
if (authHeader == null || authHeader.isEmpty()) {
|
||||
return Result.error(401, "请先登录");
|
||||
}
|
||||
Map<String, Object> claims = JwtUtils.parseToken(authHeader);
|
||||
if (claims == null) {
|
||||
return Result.error(401, "登录凭证无效或已过期,请重新登录");
|
||||
}
|
||||
Number userIdNum = (Number) claims.get("userId");
|
||||
if (userIdNum == null) {
|
||||
return Result.error(401, "用户凭证异常");
|
||||
}
|
||||
return Result.success(sysUserService.getUserInfo(userIdNum.longValue()));
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout() {
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.example.blog.controller;
|
||||
|
||||
import com.example.blog.common.JwtUtils;
|
||||
import com.example.blog.common.Result;
|
||||
import com.example.blog.entity.Comment;
|
||||
import com.example.blog.service.CommentService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/posts/{postId}/comments")
|
||||
@@ -24,7 +26,24 @@ public class CommentController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Result<Comment> addComment(@PathVariable Long postId, @RequestBody Comment comment) {
|
||||
public Result<Comment> addComment(@PathVariable Long postId,
|
||||
@RequestBody Comment comment,
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
if (authHeader != null && !authHeader.isEmpty()) {
|
||||
Map<String, Object> claims = JwtUtils.parseToken(authHeader);
|
||||
if (claims != null) {
|
||||
String nickname = (String) claims.get("nickname");
|
||||
String role = (String) claims.get("role");
|
||||
if (nickname != null && !nickname.trim().isEmpty()) {
|
||||
comment.setAuthor(nickname);
|
||||
}
|
||||
if (role != null) {
|
||||
comment.setRole(role);
|
||||
}
|
||||
comment.setAvatar("ADMIN".equalsIgnoreCase(role) ? "👑" : "👨💻");
|
||||
}
|
||||
}
|
||||
return Result.success(commentService.addComment(postId, comment));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.example.blog.controller;
|
||||
|
||||
import com.example.blog.common.JwtUtils;
|
||||
import com.example.blog.common.Result;
|
||||
import com.example.blog.entity.Post;
|
||||
import com.example.blog.service.PostService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/posts")
|
||||
@@ -18,6 +20,20 @@ public class PostController {
|
||||
this.postService = postService;
|
||||
}
|
||||
|
||||
private void checkAdmin(String authHeader) {
|
||||
if (authHeader == null || authHeader.isEmpty()) {
|
||||
throw new RuntimeException("无权操作:请使用管理员账号登录");
|
||||
}
|
||||
Map<String, Object> claims = JwtUtils.parseToken(authHeader);
|
||||
if (claims == null) {
|
||||
throw new RuntimeException("登录已失效,请重新登录");
|
||||
}
|
||||
String role = (String) claims.get("role");
|
||||
if (!"ADMIN".equalsIgnoreCase(role)) {
|
||||
throw new RuntimeException("无权操作:此功能仅限超级管理员使用");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Result<List<Post>> listPosts(
|
||||
@RequestParam(required = false) String category,
|
||||
@@ -31,17 +47,31 @@ public class PostController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Result<Post> createPost(@RequestBody Post post) {
|
||||
public Result<Post> createPost(@RequestBody Post post,
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
checkAdmin(authHeader);
|
||||
Map<String, Object> claims = JwtUtils.parseToken(authHeader);
|
||||
if (claims != null) {
|
||||
String nickname = (String) claims.get("nickname");
|
||||
if (nickname != null && !nickname.trim().isEmpty()) {
|
||||
post.setAuthor(nickname);
|
||||
}
|
||||
}
|
||||
return Result.success(postService.createPost(post));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public Result<Post> updatePost(@PathVariable Long id, @RequestBody Post post) {
|
||||
public Result<Post> updatePost(@PathVariable Long id,
|
||||
@RequestBody Post post,
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
checkAdmin(authHeader);
|
||||
return Result.success(postService.updatePost(id, post));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deletePost(@PathVariable Long id) {
|
||||
public Result<Void> deletePost(@PathVariable Long id,
|
||||
@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
checkAdmin(authHeader);
|
||||
postService.deletePost(id);
|
||||
return Result.success();
|
||||
}
|
||||
@@ -51,3 +81,4 @@ public class PostController {
|
||||
return Result.success(postService.likePost(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.example.blog.controller;
|
||||
|
||||
import com.example.blog.common.JwtUtils;
|
||||
import com.example.blog.common.Result;
|
||||
import com.example.blog.dto.UpdateProfileBody;
|
||||
import com.example.blog.dto.UpdatePwdBody;
|
||||
import com.example.blog.entity.SysUser;
|
||||
import com.example.blog.service.SysUserService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class SysUserController {
|
||||
|
||||
private final SysUserService sysUserService;
|
||||
|
||||
public SysUserController(SysUserService sysUserService) {
|
||||
this.sysUserService = sysUserService;
|
||||
}
|
||||
|
||||
private Map<String, Object> checkAuth(String authHeader) {
|
||||
if (authHeader == null || authHeader.isEmpty()) {
|
||||
throw new RuntimeException("请先登录后操作");
|
||||
}
|
||||
Map<String, Object> claims = JwtUtils.parseToken(authHeader);
|
||||
if (claims == null) {
|
||||
throw new RuntimeException("登录凭证已失效,请重新登录");
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
private void checkAdmin(String authHeader) {
|
||||
Map<String, Object> claims = checkAuth(authHeader);
|
||||
String role = (String) claims.get("role");
|
||||
if (!"ADMIN".equalsIgnoreCase(role)) {
|
||||
throw new RuntimeException("无权操作,此功能仅限超级管理员使用");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Result<List<SysUser>> listUsers(@RequestHeader(value = "Authorization", required = false) String authHeader) {
|
||||
checkAdmin(authHeader);
|
||||
return Result.success(sysUserService.listUsers());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Result<SysUser> createUser(@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@RequestBody SysUser user) {
|
||||
checkAdmin(authHeader);
|
||||
return Result.success(sysUserService.createUser(user));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/status")
|
||||
public Result<Void> changeStatus(@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable Long id,
|
||||
@RequestParam String status) {
|
||||
checkAdmin(authHeader);
|
||||
sysUserService.changeStatus(id, status);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteUser(@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@PathVariable Long id) {
|
||||
checkAdmin(authHeader);
|
||||
sysUserService.deleteUser(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public Result<Void> updateProfile(@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@RequestBody UpdateProfileBody body) {
|
||||
Map<String, Object> claims = checkAuth(authHeader);
|
||||
Number userId = (Number) claims.get("userId");
|
||||
sysUserService.updateProfile(userId.longValue(), body);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PutMapping("/password")
|
||||
public Result<Void> updatePassword(@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@RequestBody UpdatePwdBody body) {
|
||||
Map<String, Object> claims = checkAuth(authHeader);
|
||||
Number userId = (Number) claims.get("userId");
|
||||
sysUserService.updatePassword(userId.longValue(), body);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.example.blog.dto;
|
||||
|
||||
public class LoginBody {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.blog.dto;
|
||||
|
||||
public class RegisterBody {
|
||||
private String username;
|
||||
private String password;
|
||||
private String nickname;
|
||||
private String email;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
public String getNickname() { return nickname; }
|
||||
public void setNickname(String nickname) { this.nickname = nickname; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.example.blog.dto;
|
||||
|
||||
public class UpdateProfileBody {
|
||||
private String nickname;
|
||||
private String email;
|
||||
private String avatar;
|
||||
|
||||
public String getNickname() { return nickname; }
|
||||
public void setNickname(String nickname) { this.nickname = nickname; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public String getAvatar() { return avatar; }
|
||||
public void setAvatar(String avatar) { this.avatar = avatar; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.example.blog.dto;
|
||||
|
||||
public class UpdatePwdBody {
|
||||
private String oldPassword;
|
||||
private String newPassword;
|
||||
|
||||
public String getOldPassword() { return oldPassword; }
|
||||
public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; }
|
||||
public String getNewPassword() { return newPassword; }
|
||||
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
|
||||
}
|
||||
@@ -17,6 +17,12 @@ public class Comment {
|
||||
@Column(nullable = false, length = 50)
|
||||
private String author = "访客";
|
||||
|
||||
@Column(length = 255)
|
||||
private String avatar = "👨💻";
|
||||
|
||||
@Column(length = 20)
|
||||
private String role = "USER";
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String content;
|
||||
|
||||
@@ -43,6 +49,10 @@ public class Comment {
|
||||
public void setPostId(Long postId) { this.postId = postId; }
|
||||
public String getAuthor() { return author; }
|
||||
public void setAuthor(String author) { this.author = author; }
|
||||
public String getAvatar() { return avatar; }
|
||||
public void setAvatar(String avatar) { this.avatar = avatar; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.example.blog.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "sys_user")
|
||||
public class SysUser {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false, length = 128)
|
||||
private String password;
|
||||
|
||||
@Column(length = 50)
|
||||
private String nickname;
|
||||
|
||||
@Column(length = 100)
|
||||
private String email;
|
||||
|
||||
@Column(length = 255)
|
||||
private String avatar;
|
||||
|
||||
@Column(nullable = false, length = 20)
|
||||
private String role = "USER"; // ADMIN, USER
|
||||
|
||||
@Column(nullable = false, length = 1)
|
||||
private String status = "0"; // 0: 正常, 1: 停用
|
||||
|
||||
@Column(length = 64)
|
||||
private String salt;
|
||||
|
||||
@Column(name = "login_ip", length = 50)
|
||||
private String loginIp;
|
||||
|
||||
@Column(name = "login_date")
|
||||
private LocalDateTime loginDate;
|
||||
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
if (createdAt == null) createdAt = LocalDateTime.now();
|
||||
if (updatedAt == null) updatedAt = LocalDateTime.now();
|
||||
if (role == null) role = "USER";
|
||||
if (status == null) status = "0";
|
||||
if (nickname == null || nickname.trim().isEmpty()) nickname = username;
|
||||
if (avatar == null) avatar = "👨💻";
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public SysUser() {}
|
||||
|
||||
public SysUser(String username, String password, String nickname, String email, String role) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.nickname = nickname;
|
||||
this.email = email;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
|
||||
public String getNickname() { return nickname; }
|
||||
public void setNickname(String nickname) { this.nickname = nickname; }
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public String getAvatar() { return avatar; }
|
||||
public void setAvatar(String avatar) { this.avatar = avatar; }
|
||||
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
|
||||
public String getSalt() { return salt; }
|
||||
public void setSalt(String salt) { this.salt = salt; }
|
||||
|
||||
public String getLoginIp() { return loginIp; }
|
||||
public void setLoginIp(String loginIp) { this.loginIp = loginIp; }
|
||||
|
||||
public LocalDateTime getLoginDate() { return loginDate; }
|
||||
public void setLoginDate(LocalDateTime loginDate) { this.loginDate = loginDate; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.blog.repository;
|
||||
|
||||
import com.example.blog.entity.SysUser;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface SysUserRepository extends JpaRepository<SysUser, Long> {
|
||||
Optional<SysUser> findByUsername(String username);
|
||||
boolean existsByUsername(String username);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.example.blog.service;
|
||||
|
||||
import com.example.blog.dto.LoginBody;
|
||||
import com.example.blog.dto.RegisterBody;
|
||||
import com.example.blog.dto.UpdateProfileBody;
|
||||
import com.example.blog.dto.UpdatePwdBody;
|
||||
import com.example.blog.entity.SysUser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SysUserService {
|
||||
|
||||
Map<String, Object> login(LoginBody loginBody, String ip);
|
||||
|
||||
void register(RegisterBody registerBody);
|
||||
|
||||
Map<String, Object> getUserInfo(Long userId);
|
||||
|
||||
List<SysUser> listUsers();
|
||||
|
||||
void changeStatus(Long userId, String status);
|
||||
|
||||
void updateProfile(Long userId, UpdateProfileBody body);
|
||||
|
||||
void updatePassword(Long userId, UpdatePwdBody body);
|
||||
|
||||
void deleteUser(Long userId);
|
||||
|
||||
SysUser createUser(SysUser user);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.example.blog.service.impl;
|
||||
|
||||
import com.example.blog.common.JwtUtils;
|
||||
import com.example.blog.common.SecurityUtils;
|
||||
import com.example.blog.dto.LoginBody;
|
||||
import com.example.blog.dto.RegisterBody;
|
||||
import com.example.blog.dto.UpdateProfileBody;
|
||||
import com.example.blog.dto.UpdatePwdBody;
|
||||
import com.example.blog.entity.SysUser;
|
||||
import com.example.blog.repository.SysUserRepository;
|
||||
import com.example.blog.service.SysUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class SysUserServiceImpl implements SysUserService {
|
||||
|
||||
private final SysUserRepository sysUserRepository;
|
||||
|
||||
public SysUserServiceImpl(SysUserRepository sysUserRepository) {
|
||||
this.sysUserRepository = sysUserRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Object> login(LoginBody loginBody, String ip) {
|
||||
if (loginBody.getUsername() == null || loginBody.getUsername().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("用户名不能为空");
|
||||
}
|
||||
if (loginBody.getPassword() == null || loginBody.getPassword().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("密码不能为空");
|
||||
}
|
||||
|
||||
SysUser user = sysUserRepository.findByUsername(loginBody.getUsername().trim())
|
||||
.orElseThrow(() -> new RuntimeException("用户名或密码错误"));
|
||||
|
||||
if ("1".equals(user.getStatus())) {
|
||||
throw new RuntimeException("对不起,该账号已被停用,请联系管理员");
|
||||
}
|
||||
|
||||
if (!SecurityUtils.matchesPassword(loginBody.getPassword(), user.getPassword(), user.getSalt())) {
|
||||
throw new RuntimeException("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 更新登录信息
|
||||
user.setLoginIp(ip != null ? ip : "127.0.0.1");
|
||||
user.setLoginDate(LocalDateTime.now());
|
||||
sysUserRepository.save(user);
|
||||
|
||||
// 生成 RuoYi 风格 Token 与鉴权负载
|
||||
String token = JwtUtils.createToken(user.getId(), user.getUsername(), user.getNickname(), user.getRole());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("token", token);
|
||||
|
||||
Map<String, Object> safeUser = new HashMap<>();
|
||||
safeUser.put("id", user.getId());
|
||||
safeUser.put("username", user.getUsername());
|
||||
safeUser.put("nickname", user.getNickname());
|
||||
safeUser.put("email", user.getEmail());
|
||||
safeUser.put("avatar", user.getAvatar());
|
||||
safeUser.put("role", user.getRole());
|
||||
safeUser.put("status", user.getStatus());
|
||||
safeUser.put("loginDate", user.getLoginDate());
|
||||
result.put("user", safeUser);
|
||||
|
||||
// 角色与权限标识 (若依标准格式)
|
||||
result.put("roles", Collections.singletonList(user.getRole()));
|
||||
if ("ADMIN".equalsIgnoreCase(user.getRole())) {
|
||||
result.put("permissions", Collections.singletonList("*:*:*"));
|
||||
} else {
|
||||
result.put("permissions", Arrays.asList("post:view", "post:like", "comment:add"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void register(RegisterBody registerBody) {
|
||||
String username = registerBody.getUsername() != null ? registerBody.getUsername().trim() : "";
|
||||
String password = registerBody.getPassword() != null ? registerBody.getPassword().trim() : "";
|
||||
|
||||
if (username.length() < 3 || username.length() > 20) {
|
||||
throw new IllegalArgumentException("用户名长度必须在 3 到 20 个字符之间");
|
||||
}
|
||||
if (password.length() < 5 || password.length() > 30) {
|
||||
throw new IllegalArgumentException("密码长度必须在 5 到 30 个字符之间");
|
||||
}
|
||||
|
||||
if (sysUserRepository.existsByUsername(username)) {
|
||||
throw new RuntimeException("该用户名已被注册,请尝试其他用户名");
|
||||
}
|
||||
|
||||
String salt = SecurityUtils.generateSalt();
|
||||
String encryptedPassword = SecurityUtils.encryptPassword(password, salt);
|
||||
|
||||
SysUser user = new SysUser();
|
||||
user.setUsername(username);
|
||||
user.setPassword(encryptedPassword);
|
||||
user.setSalt(salt);
|
||||
user.setNickname(registerBody.getNickname() != null && !registerBody.getNickname().trim().isEmpty() ? registerBody.getNickname().trim() : username);
|
||||
user.setEmail(registerBody.getEmail() != null ? registerBody.getEmail().trim() : "");
|
||||
user.setAvatar("👨💻");
|
||||
user.setRole("USER");
|
||||
user.setStatus("0");
|
||||
|
||||
sysUserRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Map<String, Object> getUserInfo(Long userId) {
|
||||
SysUser user = sysUserRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<String, Object> safeUser = new HashMap<>();
|
||||
safeUser.put("id", user.getId());
|
||||
safeUser.put("username", user.getUsername());
|
||||
safeUser.put("nickname", user.getNickname());
|
||||
safeUser.put("email", user.getEmail());
|
||||
safeUser.put("avatar", user.getAvatar());
|
||||
safeUser.put("role", user.getRole());
|
||||
safeUser.put("status", user.getStatus());
|
||||
safeUser.put("loginDate", user.getLoginDate());
|
||||
safeUser.put("createdAt", user.getCreatedAt());
|
||||
|
||||
result.put("user", safeUser);
|
||||
result.put("roles", Collections.singletonList(user.getRole()));
|
||||
if ("ADMIN".equalsIgnoreCase(user.getRole())) {
|
||||
result.put("permissions", Collections.singletonList("*:*:*"));
|
||||
} else {
|
||||
result.put("permissions", Arrays.asList("post:view", "post:like", "comment:add"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<SysUser> listUsers() {
|
||||
List<SysUser> users = sysUserRepository.findAll();
|
||||
// 清除敏感字段
|
||||
for (SysUser u : users) {
|
||||
u.setPassword(null);
|
||||
u.setSalt(null);
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void changeStatus(Long userId, String status) {
|
||||
SysUser user = sysUserRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
||||
if ("admin".equalsIgnoreCase(user.getUsername())) {
|
||||
throw new RuntimeException("不允许停用内置超级管理员账号");
|
||||
}
|
||||
user.setStatus("1".equals(status) ? "1" : "0");
|
||||
sysUserRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateProfile(Long userId, UpdateProfileBody body) {
|
||||
SysUser user = sysUserRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
||||
if (body.getNickname() != null && !body.getNickname().trim().isEmpty()) {
|
||||
user.setNickname(body.getNickname().trim());
|
||||
}
|
||||
if (body.getEmail() != null) {
|
||||
user.setEmail(body.getEmail().trim());
|
||||
}
|
||||
if (body.getAvatar() != null && !body.getAvatar().trim().isEmpty()) {
|
||||
user.setAvatar(body.getAvatar().trim());
|
||||
}
|
||||
sysUserRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updatePassword(Long userId, UpdatePwdBody body) {
|
||||
SysUser user = sysUserRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
||||
|
||||
if (!SecurityUtils.matchesPassword(body.getOldPassword(), user.getPassword(), user.getSalt())) {
|
||||
throw new RuntimeException("原密码错误,修改失败");
|
||||
}
|
||||
if (body.getNewPassword() == null || body.getNewPassword().length() < 5) {
|
||||
throw new IllegalArgumentException("新密码长度不能少于 5 位");
|
||||
}
|
||||
|
||||
String newSalt = SecurityUtils.generateSalt();
|
||||
String newHash = SecurityUtils.encryptPassword(body.getNewPassword(), newSalt);
|
||||
user.setSalt(newSalt);
|
||||
user.setPassword(newHash);
|
||||
sysUserRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteUser(Long userId) {
|
||||
SysUser user = sysUserRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("用户不存在"));
|
||||
if ("admin".equalsIgnoreCase(user.getUsername())) {
|
||||
throw new RuntimeException("不允许删除内置超级管理员账号");
|
||||
}
|
||||
sysUserRepository.deleteById(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public SysUser createUser(SysUser user) {
|
||||
if (sysUserRepository.existsByUsername(user.getUsername())) {
|
||||
throw new RuntimeException("用户名已存在");
|
||||
}
|
||||
String salt = SecurityUtils.generateSalt();
|
||||
user.setSalt(salt);
|
||||
user.setPassword(SecurityUtils.encryptPassword(user.getPassword(), salt));
|
||||
return sysUserRepository.save(user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user