feat(用户管理): 添加用户角色功能并实现权限控制
- 在用户表中添加role字段并设置默认值为'USER' - 前端添加isAdmin getter判断用户角色 - 后端实现角色字段的VO映射和默认值设置 - 为关键接口添加@PreAuthorize权限控制 - 移除图片控制器中冗余的权限检查代码
This commit is contained in:
@@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -23,6 +24,7 @@ public class GroupingController {
|
||||
private GroupingService groupingService;
|
||||
|
||||
@Operation(summary = "创建分组")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PostMapping
|
||||
public R<Grouping> createGrouping(@RequestBody Grouping grouping) {
|
||||
if (ObjectUtil.isNull(grouping.getParentId())) {
|
||||
@@ -47,6 +49,7 @@ public class GroupingController {
|
||||
}
|
||||
|
||||
@Operation(summary = "更新分组名称")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PutMapping("/{id}")
|
||||
public R<Grouping> updateGrouping(
|
||||
@PathVariable String id,
|
||||
@@ -59,6 +62,7 @@ public class GroupingController {
|
||||
}
|
||||
|
||||
@Operation(summary = "删除分组")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@DeleteMapping("/{id}")
|
||||
public R<Void> deleteGrouping(@PathVariable String id) {
|
||||
Long idLong = Long.parseLong(id);
|
||||
|
||||
@@ -66,15 +66,9 @@ public class ImageController {
|
||||
}
|
||||
|
||||
@Operation(summary = "根据id删除图片")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PostMapping("/{id}")
|
||||
public R<Void> deleteImage(@PathVariable Long id) {
|
||||
if (!SecurityUtil.isUserAuthenticated()) {
|
||||
return R.fail("请先登录");
|
||||
}
|
||||
// 修复:添加权限验证,确保用户只能删除自己的图片
|
||||
if (!canModifyImage(id)) {
|
||||
return R.fail("无权删除此图片");
|
||||
}
|
||||
boolean result = imageService.deleteImage(id);
|
||||
if (result) {
|
||||
return R.success();
|
||||
@@ -138,15 +132,9 @@ public class ImageController {
|
||||
|
||||
|
||||
@Operation(summary = "根据url删除图片")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PostMapping("/deleteByUrl")
|
||||
public R<Void> deleteImageByUrl(@RequestParam String url) {
|
||||
if (!SecurityUtil.isUserAuthenticated()) {
|
||||
return R.fail("请先登录");
|
||||
}
|
||||
// 修复:添加权限验证
|
||||
if (!canModifyImageByUrl(url)) {
|
||||
return R.fail("无权删除此图片");
|
||||
}
|
||||
boolean result = imageService.deleteImageByUrl(url);
|
||||
if (result) {
|
||||
return R.success();
|
||||
@@ -156,17 +144,9 @@ public class ImageController {
|
||||
}
|
||||
|
||||
@Operation(summary = "根据url批量删除图片")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PostMapping("/batch")
|
||||
public R<Void> deleteImageByUrls(@RequestBody List<String> urls) {
|
||||
if (!SecurityUtil.isUserAuthenticated()) {
|
||||
return R.fail("请先登录");
|
||||
}
|
||||
// 修复:添加权限验证
|
||||
for (String url : urls) {
|
||||
if (!canModifyImageByUrl(url)) {
|
||||
return R.fail("无权删除部分图片");
|
||||
}
|
||||
}
|
||||
boolean result = imageService.deleteImageByUrls(urls);
|
||||
if (result) {
|
||||
return R.success();
|
||||
@@ -175,49 +155,6 @@ public class ImageController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否有权限操作图片
|
||||
*/
|
||||
private boolean canModifyImage(Long imageId) {
|
||||
// 从数据库查询图片所属用户
|
||||
Image image = imageService.getById(imageId);
|
||||
if (image == null) {
|
||||
return false;
|
||||
}
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
if (!(principal instanceof UserDetails)) {
|
||||
return false;
|
||||
}
|
||||
String username = ((UserDetails) principal).getUsername();
|
||||
User user = userService.getOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<User>().eq("username", username));
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
return user.getId().equals(image.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否有权限操作图片(通过URL)
|
||||
*/
|
||||
private boolean canModifyImageByUrl(String url) {
|
||||
// 从数据库查询图片所属用户
|
||||
Image image = imageService.getOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Image>().eq("stored_name", url));
|
||||
if (image == null) {
|
||||
return false;
|
||||
}
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
if (!(principal instanceof UserDetails)) {
|
||||
return false;
|
||||
}
|
||||
String username = ((UserDetails) principal).getUsername();
|
||||
User user = userService.getOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<User>().eq("username", username));
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
return user.getId().equals(image.getUserId());
|
||||
}
|
||||
|
||||
|
||||
private String getContentTypeFromFileExtension(String fileName) {
|
||||
if (StrUtil.isBlank(fileName) || !StrUtil.contains(fileName, '.')) {
|
||||
return "application/octet-stream";
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -55,6 +56,7 @@ public class MarkdownController {
|
||||
|
||||
|
||||
@Operation(summary = "更新Markdown文件")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PostMapping("/updateMarkdown")
|
||||
public R<MarkdownFile> updateMarkdown(@RequestBody MarkdownFile markdownFile) {
|
||||
MarkdownFile file = markdownFileService.updateMarkdownContent(markdownFile);
|
||||
@@ -69,6 +71,7 @@ public class MarkdownController {
|
||||
}
|
||||
|
||||
@Operation(summary = "删除Markdown文件")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "Markdown文件ID", required = true),
|
||||
})
|
||||
@@ -95,6 +98,7 @@ public class MarkdownController {
|
||||
}
|
||||
|
||||
@Operation(summary = "更新Markdown文件标题")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PostMapping("/{id}/title")
|
||||
public R<MarkdownFile> updateMarkdownTitle(
|
||||
@PathVariable Long id,
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.test.bijihoudaun.service.TrashService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,6 +27,7 @@ public class TrashController {
|
||||
}
|
||||
|
||||
@PostMapping("/restore/{type}/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "恢复项目")
|
||||
public R<Void> restoreItem(@PathVariable String type, @PathVariable String id) {
|
||||
trashService.restoreItem(id, type);
|
||||
@@ -33,6 +35,7 @@ public class TrashController {
|
||||
}
|
||||
|
||||
@DeleteMapping("/permanently/{type}/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequireCaptcha("永久删除")
|
||||
@Operation(summary = "永久删除项目")
|
||||
public R<Void> permanentlyDeleteItem(@PathVariable String type, @PathVariable String id) {
|
||||
@@ -41,10 +44,11 @@ public class TrashController {
|
||||
}
|
||||
|
||||
@DeleteMapping("/clean")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequireCaptcha("清空回收站")
|
||||
@Operation(summary = "清空回收站")
|
||||
public R<Void> cleanTrash() {
|
||||
trashService.cleanTrash();
|
||||
return R.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@ public class UserController {
|
||||
userInfo.put("id", String.valueOf(user.getId()));
|
||||
userInfo.put("username", user.getUsername());
|
||||
userInfo.put("email", user.getEmail());
|
||||
String role = user.getRole();
|
||||
userInfo.put("role", (role != null && !role.isEmpty()) ? role : "USER");
|
||||
result.put("userInfo", userInfo);
|
||||
|
||||
return R.success(result);
|
||||
|
||||
@@ -21,6 +21,9 @@ public class UserVO implements Serializable {
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "用户角色")
|
||||
private String role;
|
||||
|
||||
@Schema(description = "用户创建时间")
|
||||
private Date createdAt;
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
user.setUsername(username);
|
||||
user.setPassword(encrypt);
|
||||
user.setEmail(email);
|
||||
user.setRole("USER"); // 设置默认角色
|
||||
userMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export const useUserStore = defineStore('user', {
|
||||
},
|
||||
getters: {
|
||||
isLoggedIn: (state) => !!state.token,
|
||||
// 添加:判断是否为管理员
|
||||
isAdmin: (state) => state.userInfo?.role === 'ADMIN',
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
@@ -40,4 +42,4 @@ export const useUserStore = defineStore('user', {
|
||||
}
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -168,6 +168,7 @@ CREATE TABLE "user" (
|
||||
"username" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"email" TEXT,
|
||||
"role" TEXT DEFAULT 'USER',
|
||||
"created_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
"token" TEXT,
|
||||
|
||||
@@ -84,12 +84,14 @@ CREATE TABLE `user` (
|
||||
`username` VARCHAR(50) NOT NULL,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(100) DEFAULT NULL,
|
||||
`role` VARCHAR(50) DEFAULT 'USER' COMMENT '用户角色:ADMIN-管理员,USER-普通用户',
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`token` VARCHAR(255) DEFAULT NULL,
|
||||
`token_enddata` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_username` (`username`)
|
||||
UNIQUE KEY `uk_username` (`username`),
|
||||
KEY `idx_user_role` (`role`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
Reference in New Issue
Block a user