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.Parameters;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -23,6 +24,7 @@ public class GroupingController {
|
|||||||
private GroupingService groupingService;
|
private GroupingService groupingService;
|
||||||
|
|
||||||
@Operation(summary = "创建分组")
|
@Operation(summary = "创建分组")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public R<Grouping> createGrouping(@RequestBody Grouping grouping) {
|
public R<Grouping> createGrouping(@RequestBody Grouping grouping) {
|
||||||
if (ObjectUtil.isNull(grouping.getParentId())) {
|
if (ObjectUtil.isNull(grouping.getParentId())) {
|
||||||
@@ -47,6 +49,7 @@ public class GroupingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "更新分组名称")
|
@Operation(summary = "更新分组名称")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public R<Grouping> updateGrouping(
|
public R<Grouping> updateGrouping(
|
||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@@ -59,6 +62,7 @@ public class GroupingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "删除分组")
|
@Operation(summary = "删除分组")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public R<Void> deleteGrouping(@PathVariable String id) {
|
public R<Void> deleteGrouping(@PathVariable String id) {
|
||||||
Long idLong = Long.parseLong(id);
|
Long idLong = Long.parseLong(id);
|
||||||
|
|||||||
@@ -66,15 +66,9 @@ public class ImageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据id删除图片")
|
@Operation(summary = "根据id删除图片")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/{id}")
|
@PostMapping("/{id}")
|
||||||
public R<Void> deleteImage(@PathVariable Long 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);
|
boolean result = imageService.deleteImage(id);
|
||||||
if (result) {
|
if (result) {
|
||||||
return R.success();
|
return R.success();
|
||||||
@@ -138,15 +132,9 @@ public class ImageController {
|
|||||||
|
|
||||||
|
|
||||||
@Operation(summary = "根据url删除图片")
|
@Operation(summary = "根据url删除图片")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/deleteByUrl")
|
@PostMapping("/deleteByUrl")
|
||||||
public R<Void> deleteImageByUrl(@RequestParam String url) {
|
public R<Void> deleteImageByUrl(@RequestParam String url) {
|
||||||
if (!SecurityUtil.isUserAuthenticated()) {
|
|
||||||
return R.fail("请先登录");
|
|
||||||
}
|
|
||||||
// 修复:添加权限验证
|
|
||||||
if (!canModifyImageByUrl(url)) {
|
|
||||||
return R.fail("无权删除此图片");
|
|
||||||
}
|
|
||||||
boolean result = imageService.deleteImageByUrl(url);
|
boolean result = imageService.deleteImageByUrl(url);
|
||||||
if (result) {
|
if (result) {
|
||||||
return R.success();
|
return R.success();
|
||||||
@@ -156,17 +144,9 @@ public class ImageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "根据url批量删除图片")
|
@Operation(summary = "根据url批量删除图片")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/batch")
|
@PostMapping("/batch")
|
||||||
public R<Void> deleteImageByUrls(@RequestBody List<String> urls) {
|
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);
|
boolean result = imageService.deleteImageByUrls(urls);
|
||||||
if (result) {
|
if (result) {
|
||||||
return R.success();
|
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) {
|
private String getContentTypeFromFileExtension(String fileName) {
|
||||||
if (StrUtil.isBlank(fileName) || !StrUtil.contains(fileName, '.')) {
|
if (StrUtil.isBlank(fileName) || !StrUtil.contains(fileName, '.')) {
|
||||||
return "application/octet-stream";
|
return "application/octet-stream";
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.Parameters;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -55,6 +56,7 @@ public class MarkdownController {
|
|||||||
|
|
||||||
|
|
||||||
@Operation(summary = "更新Markdown文件")
|
@Operation(summary = "更新Markdown文件")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/updateMarkdown")
|
@PostMapping("/updateMarkdown")
|
||||||
public R<MarkdownFile> updateMarkdown(@RequestBody MarkdownFile markdownFile) {
|
public R<MarkdownFile> updateMarkdown(@RequestBody MarkdownFile markdownFile) {
|
||||||
MarkdownFile file = markdownFileService.updateMarkdownContent(markdownFile);
|
MarkdownFile file = markdownFileService.updateMarkdownContent(markdownFile);
|
||||||
@@ -69,6 +71,7 @@ public class MarkdownController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "删除Markdown文件")
|
@Operation(summary = "删除Markdown文件")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@Parameters({
|
@Parameters({
|
||||||
@Parameter(name = "id", description = "Markdown文件ID", required = true),
|
@Parameter(name = "id", description = "Markdown文件ID", required = true),
|
||||||
})
|
})
|
||||||
@@ -95,6 +98,7 @@ public class MarkdownController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "更新Markdown文件标题")
|
@Operation(summary = "更新Markdown文件标题")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/{id}/title")
|
@PostMapping("/{id}/title")
|
||||||
public R<MarkdownFile> updateMarkdownTitle(
|
public R<MarkdownFile> updateMarkdownTitle(
|
||||||
@PathVariable Long id,
|
@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.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -26,6 +27,7 @@ public class TrashController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/restore/{type}/{id}")
|
@PostMapping("/restore/{type}/{id}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@Operation(summary = "恢复项目")
|
@Operation(summary = "恢复项目")
|
||||||
public R<Void> restoreItem(@PathVariable String type, @PathVariable String id) {
|
public R<Void> restoreItem(@PathVariable String type, @PathVariable String id) {
|
||||||
trashService.restoreItem(id, type);
|
trashService.restoreItem(id, type);
|
||||||
@@ -33,6 +35,7 @@ public class TrashController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/permanently/{type}/{id}")
|
@DeleteMapping("/permanently/{type}/{id}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@RequireCaptcha("永久删除")
|
@RequireCaptcha("永久删除")
|
||||||
@Operation(summary = "永久删除项目")
|
@Operation(summary = "永久删除项目")
|
||||||
public R<Void> permanentlyDeleteItem(@PathVariable String type, @PathVariable String id) {
|
public R<Void> permanentlyDeleteItem(@PathVariable String type, @PathVariable String id) {
|
||||||
@@ -41,6 +44,7 @@ public class TrashController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/clean")
|
@DeleteMapping("/clean")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@RequireCaptcha("清空回收站")
|
@RequireCaptcha("清空回收站")
|
||||||
@Operation(summary = "清空回收站")
|
@Operation(summary = "清空回收站")
|
||||||
public R<Void> cleanTrash() {
|
public R<Void> cleanTrash() {
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ public class UserController {
|
|||||||
userInfo.put("id", String.valueOf(user.getId()));
|
userInfo.put("id", String.valueOf(user.getId()));
|
||||||
userInfo.put("username", user.getUsername());
|
userInfo.put("username", user.getUsername());
|
||||||
userInfo.put("email", user.getEmail());
|
userInfo.put("email", user.getEmail());
|
||||||
|
String role = user.getRole();
|
||||||
|
userInfo.put("role", (role != null && !role.isEmpty()) ? role : "USER");
|
||||||
result.put("userInfo", userInfo);
|
result.put("userInfo", userInfo);
|
||||||
|
|
||||||
return R.success(result);
|
return R.success(result);
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ public class UserVO implements Serializable {
|
|||||||
@Schema(description = "邮箱")
|
@Schema(description = "邮箱")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
@Schema(description = "用户角色")
|
||||||
|
private String role;
|
||||||
|
|
||||||
@Schema(description = "用户创建时间")
|
@Schema(description = "用户创建时间")
|
||||||
private Date createdAt;
|
private Date createdAt;
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
user.setPassword(encrypt);
|
user.setPassword(encrypt);
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
|
user.setRole("USER"); // 设置默认角色
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export const useUserStore = defineStore('user', {
|
|||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isLoggedIn: (state) => !!state.token,
|
isLoggedIn: (state) => !!state.token,
|
||||||
|
// 添加:判断是否为管理员
|
||||||
|
isAdmin: (state) => state.userInfo?.role === 'ADMIN',
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ CREATE TABLE "user" (
|
|||||||
"username" TEXT NOT NULL,
|
"username" TEXT NOT NULL,
|
||||||
"password" TEXT NOT NULL,
|
"password" TEXT NOT NULL,
|
||||||
"email" TEXT,
|
"email" TEXT,
|
||||||
|
"role" TEXT DEFAULT 'USER',
|
||||||
"created_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
"created_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
"updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
"token" TEXT,
|
"token" TEXT,
|
||||||
|
|||||||
@@ -84,12 +84,14 @@ CREATE TABLE `user` (
|
|||||||
`username` VARCHAR(50) NOT NULL,
|
`username` VARCHAR(50) NOT NULL,
|
||||||
`password` VARCHAR(255) NOT NULL,
|
`password` VARCHAR(255) NOT NULL,
|
||||||
`email` VARCHAR(100) DEFAULT NULL,
|
`email` VARCHAR(100) DEFAULT NULL,
|
||||||
|
`role` VARCHAR(50) DEFAULT 'USER' COMMENT '用户角色:ADMIN-管理员,USER-普通用户',
|
||||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
`token` VARCHAR(255) DEFAULT NULL,
|
`token` VARCHAR(255) DEFAULT NULL,
|
||||||
`token_enddata` DATETIME DEFAULT NULL,
|
`token_enddata` DATETIME DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`),
|
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;
|
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
Reference in New Issue
Block a user