feat(file): 实现文件删除权限控制功能

- 新增 FILE_DELETE_NO_PERMISSION 错误码用于无权删除文件的提示
- 移除控制器层的 @PreAuthorize 注解,将权限校验逻辑下沉到服务层
- 在 FileServiceImpl 中注入 SecurityFrameworkService 进行权限验证
- 实现 validateFileDeletePermission 方法校验删除权限
- 支持上传者本人可删除自己上传的文件,管理员仍保留全局删除权限
- 批量删除时逐个校验权限,任一越权即整体拒绝操作
This commit is contained in:
2026-07-13 16:23:24 +08:00
parent 5b2c6dd94c
commit f77007c4b7
3 changed files with 33 additions and 2 deletions

View File

@@ -106,6 +106,7 @@ public interface ErrorCodeConstants {
ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在"); ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在");
ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在"); ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在");
ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空"); ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空");
ErrorCode FILE_DELETE_NO_PERMISSION = new ErrorCode(1_001_003_003, "无权删除该文件,仅可删除本人上传的文件");
// ========== OAuth2 客户端 1-002-020-000 ========= // ========== OAuth2 客户端 1-002-020-000 =========
ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在"); ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在");

View File

@@ -84,7 +84,8 @@ public class FileController {
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除文件") @Operation(summary = "删除文件")
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:file:delete')") // 权限下沉到 FileService上传者本人可删自己上传的文件其余需 system:file:delete 权限。
// 故意不挂 @PreAuthorize否则无该权限码的上传者会在进方法体前被 AOP 挡死(口径同项目域"属主放行+权限码兜底")。
public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception { public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
fileService.deleteFile(id); fileService.deleteFile(id);
return success(true); return success(true);
@@ -93,7 +94,7 @@ public class FileController {
@DeleteMapping("/delete-list") @DeleteMapping("/delete-list")
@Operation(summary = "批量删除文件") @Operation(summary = "批量删除文件")
@Parameter(name = "ids", description = "编号列表", required = true) @Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('system:file:delete')") // 权限下沉到 FileService逐个按上传者归属校验其余需 system:file:delete 权限(见 deleteFile 注释)。
public CommonResult<Boolean> deleteFileList(@RequestParam("ids") List<Long> ids) throws Exception { public CommonResult<Boolean> deleteFileList(@RequestParam("ids") List<Long> ids) throws Exception {
fileService.deleteFileList(ids); fileService.deleteFileList(ids);
return success(true); return success(true);

View File

@@ -10,6 +10,8 @@ import com.google.common.annotations.VisibleForTesting;
import com.njcn.rdms.framework.common.pojo.PageResult; import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.http.HttpUtils; import com.njcn.rdms.framework.common.util.http.HttpUtils;
import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.security.core.service.SecurityFrameworkService;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.system.controller.admin.file.vo.file.FileCreateReqVO; import com.njcn.rdms.module.system.controller.admin.file.vo.file.FileCreateReqVO;
import com.njcn.rdms.module.system.controller.admin.file.vo.file.FilePageReqVO; import com.njcn.rdms.module.system.controller.admin.file.vo.file.FilePageReqVO;
import com.njcn.rdms.module.system.controller.admin.file.vo.file.FilePresignedUrlRespVO; import com.njcn.rdms.module.system.controller.admin.file.vo.file.FilePresignedUrlRespVO;
@@ -27,6 +29,7 @@ import java.util.List;
import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN; import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.FILE_DELETE_NO_PERMISSION;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.FILE_NOT_EXISTS; import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
/** /**
@@ -52,6 +55,9 @@ public class FileServiceImpl implements FileService {
@Resource @Resource
private FileContentMapper fileContentMapper; private FileContentMapper fileContentMapper;
@Resource
private SecurityFrameworkService securityFrameworkService;
@Override @Override
public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) { public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) {
return fileMapper.selectPage(pageReqVO); return fileMapper.selectPage(pageReqVO);
@@ -172,6 +178,8 @@ public class FileServiceImpl implements FileService {
public void deleteFile(Long id) throws Exception { public void deleteFile(Long id) throws Exception {
// 校验存在 // 校验存在
FileDO file = validateFileExists(id); FileDO file = validateFileExists(id);
// 校验删除权限:上传者本人,或具备文件删除权限者
validateFileDeletePermission(file);
// 从文件存储器中删除 // 从文件存储器中删除
FileClient client = fileConfigService.getFileClient(file.getConfigId()); FileClient client = fileConfigService.getFileClient(file.getConfigId());
@@ -187,6 +195,8 @@ public class FileServiceImpl implements FileService {
public void deleteFileList(List<Long> ids) { public void deleteFileList(List<Long> ids) {
// 删除文件 // 删除文件
List<FileDO> files = fileMapper.selectByIds(ids); List<FileDO> files = fileMapper.selectByIds(ids);
// 逐个校验删除权限,任一越权即整体拒绝
files.forEach(this::validateFileDeletePermission);
for (FileDO file : files) { for (FileDO file : files) {
// 获取客户端 // 获取客户端
FileClient client = fileConfigService.getFileClient(file.getConfigId()); FileClient client = fileConfigService.getFileClient(file.getConfigId());
@@ -207,6 +217,25 @@ public class FileServiceImpl implements FileService {
return fileDO; return fileDO;
} }
/**
* 校验文件删除权限。
* <p>
* 口径:上传者本人可删除自己上传的文件;其余情况需具备文件管理删除权限 {@code system:file:delete}(运维 / 管理员)。
* 该接口按 id 删除、不校验业务归属,故不能对所有登录用户放开,只对"本人上传"放行。
*/
private void validateFileDeletePermission(FileDO file) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
// 上传者本人放行creator 存的是上传时的登录用户 id
if (loginUserId != null && String.valueOf(loginUserId).equals(file.getCreator())) {
return;
}
// 兜底:具备文件删除权限者(运维 / 管理员)可删除任意文件
if (securityFrameworkService.hasPermission("system:file:delete")) {
return;
}
throw exception(FILE_DELETE_NO_PERMISSION);
}
@Override @Override
public byte[] getFileContent(Long configId, String path) throws Exception { public byte[] getFileContent(Long configId, String path) throws Exception {
// 软删 / 不存在的记录直接拒绝 // 软删 / 不存在的记录直接拒绝