Files
aue/管理员vs普通用户上传对比分析.md
2026-06-03 13:16:30 +08:00

479 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 管理员 vs 普通用户上传文件 - 完整对比分析
## 📋 场景定义
### **场景A管理员上传公共文件正常工作 ✅)**
- 用户类型:`userType = 1 (超级管理员)``userType = 2 (部门管理员)`
- 前端选择:公开文件
- 是否需要审批:**不需要**
- 最终结果:✅ 正确显示在"制度文件"列表
### **场景B普通用户上传公共文件存在问题 ❌)**
- 用户类型:`userType = 3 (普通用户)`
- 前端选择:公开文件
- 是否需要审批:**需要** ⚠️
- 最终结果:❌ 审批后显示在"个人文件"列表
---
## 🔍 完整数据流追踪
### **【前端】参数构建阶段**
#### 管理员上传时ReadModule.vue:1926-1935
```javascript
const formData = new FormData()
formData.append('file', uploadForm.file)
// 关键差异点1deptId 的传递条件
if (isAdminUser(userInfo.value) && uploadForm.deptId) {
formData.append('deptId', uploadForm.deptId) // ✅ 管理员传了 deptId
}
formData.append('isPublic', uploadForm.isPublic ? '1' : '0') // 两者都传
```
| 参数 | 管理员(公共文件) | 普通用户(公共文件) |
|------|------------------|---------------------|
| `file` | ✅ 文件对象 | ✅ 文件对象 |
| `deptId` | ✅ **有值**如部门ID=5 | ❌ **null**(不传递) |
| `isPublic` | `'1'` (true) | `'1'` (true) |
---
### **【后端】FileController 接收阶段**
📍 FileController.java:100-120
```java
@PostMapping("/upload")
public Result<FileVO> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam(required = false) Long deptId, // 管理员有值普通用户为null
@RequestParam(required = false, defaultValue = "true") Boolean isPublic, // 都是 true
@RequestParam(required = false) String description,
HttpServletRequest request) {
Long userId = getCurrentUserId(request); // 获取当前用户ID
User user = authService.validateToken(...); // 获取用户信息
FileVO fileVO = fileService.uploadFile(
file, userId, user.getDeptId(), user.getUserType(), deptId, isPublic
);
// ↑
// 管理员: deptId=5, userType=1/2
// 普通用户: deptId=null, userType=3
}
```
**传入 FileServiceImpl.uploadFile() 的参数:**
| 参数名 | 管理员(公共) | 普通用户(公共) |
|--------|--------------|----------------|
| `file` | MultipartFile | MultipartFile |
| `userId` | 1 (管理员ID) | 5 (普通用户ID) |
| `deptId` | 3 (用户的部门ID) | 7 (用户的部门ID) |
| `userType` | **1 或 2** | **3** |
| `targetDeptId` | **5** (前端传入) | **null** (未传) |
| `isPublic` | **true** | **true** |
---
### **【后端】FileServiceImpl 核心处理逻辑**
#### 步骤1判断是否需要审批
📍 FileServiceImpl.java:172-177
```java
private boolean shouldRequireAudit(Integer userType, Boolean isPublic, Long targetDeptId) {
if (userType == 3 && isPublic != null && isPublic && targetDeptId == null) {
return true; // ⚠️ 只有这个条件返回true
}
return false;
}
```
**执行结果:**
| 用户类型 | isPublic | targetDeptId | needAudit |
|---------|----------|-------------|-----------|
| 管理员(1/2) | true | 5 | **false** ✅ 不需要审批 |
| 普通用户(3) | true | **null** | **true** ⚠️ 需要审批! |
---
#### 步骤2确定存储路径
📍 FileServiceImpl.java:179-196
```java
private Path determineUploadPath(...) {
if (userType == 1 || userType == 2) {
// 管理员路径:/resources/rule/{部门全路径}/{文件名}/
return Paths.get(uploadPath, "resources", "rule", fullPath, fileNameWithoutExt);
} else {
// 普通用户路径
String username = getUserUsername(userId);
if (needAudit) {
// 待审核路径:/resources/user/{username}/pending/{文件名}/
return Paths.get(uploadPath, "resources", "user", username, "pending", fileNameWithoutExt);
}
// 正常路径:/resources/user/{username}/public|private/{文件名}/
String privacyPath = (isPublic != null && isPublic) ? "public" : "private";
return Paths.get(uploadPath, "resources", "user", username, privacyPath, fileNameWithoutExt);
}
}
```
**存储路径对比:**
| 场景 | 存储路径 |
|------|---------|
| 管理员+公共 | `/upload/resources/rule/技术部/测试文档/v1/test.docx` |
| 普通用户+公共(待审)| `/upload/resources/user/zhangsan/pending/测试文档/v1/test.docx` |
---
#### 步骤3确定 isPublic 值
📍 FileServiceImpl.java:199-204
```java
private Integer determineIsPublicValue(Integer userType, Boolean isPublic, boolean needAudit) {
if (needAudit) {
return 0; // ⚠️ 待审核状态强制设为0私有
}
return (isPublic != null && isPublic) ? 1 : 0;
}
```
**结果:**
| 场景 | needAudit | 数据库 isPublic 值 |
|------|----------|-------------------|
| 管理员+公共 | false | **1** (公开) ✅ |
| 普通用户+公共(待审)| **true** | **0** (私有) ⚠️ 临时标记 |
---
#### 步骤4确定 fileCategory关键
📍 FileServiceImpl.java:235-243
```java
private String determineFileCategory(Integer userType, Long targetDeptId, Boolean isPublic) {
if (targetDeptId != null) {
return "rule"; // 条件A有targetDeptId → 公共文件
}
if (userType == 3 && Boolean.TRUE.equals(isPublic)) {
return "rule"; // 条件B普通用户+公开 → 公共文件 ✅ 已修复
}
return "personal"; // 默认 → 个人文件
}
```
**调用时的参数第143行**
```java
String fileCategory = determineFileCategory(userType, targetDeptId, isPublic);
// ↑ ↑ ↑
// 管理员: 1或2 5 true
// 普通用户: 3 null true
```
**结果:**
| 场景 | targetDeptId | userType | isPublic | fileCategory |
|------|-------------|----------|----------|-------------|
| 管理员+公共 | **5** (非null) | 1/2 | true | **"rule"** ✅ (命中条件A) |
| 普通用户+公共 | **null** | **3** | **true** | **"rule"** ✅ (命中条件B - 已修复) |
---
#### 步骤5确定 auditStatus
📍 FileServiceImpl.java:148-149
```java
Integer auditStatus = needAudit ? 0 : 1;
// ↑
// 管理员: false → 1 (已通过)
// 普通用户: true → 0 (待审核)
```
---
### **【数据库】初始存储状态**
上传完成后,数据库中的记录:
| 字段 | 管理员+公共 | 普通用户+公共(待审) |
|------|-----------|---------------------|
| id | 自增 | 自增 |
| file_name | test.docx | test.docx |
| file_path | /resources/rule/... | /resources/user/zhangsan/pending/... |
| dept_id | **5** (targetDeptId) | **null** (用户自己的deptId被忽略) |
| is_public | **1** | **0** (临时) |
| file_category | **"rule"** | **"rule"** ✅ (已修复) |
| status | 1 (启用) | 1 (启用) |
| audit_status | **1** (已通过) | **0** (待审核) |
---
## 🔄 【审批流程】关键环节
### **管理员点击"通过"按钮**
触发:[ReadModule.vue:1622-1635](ReadModule.vue#L1622-L1635)
```javascript
const handleApproveFile = async (file) => {
const res = await fileAPI.approveFile(file.id) // 只传 fileId
// ...
}
```
### **后端 approveFile() 执行**
📍 FileServiceImpl.java:572-604
```java
public void approveFile(Long fileId, Long auditorId) {
// 1. 从数据库查询文件
top.tqx.demo_1.entity.File file = this.getById(fileId);
// 2. 移动物理文件(从 pending 到 public 目录)
String oldPath = file.getFilePath();
String newPath = moveFileToPublic(file);
// 3. 更新字段
file.setFilePath(newPath); // 路径更新
file.setIsPublic(1); // ✅ isPublic: 0→1
file.setAuditStatus(1); // ✅ auditStatus: 0→1
// 4. 🔑 关键修正(已添加的修复代码)
if ("personal".equals(file.getFileCategory())) {
file.setFileCategory("rule"); // personal → rule
}
file.setUpdateTime(LocalDateTime.now());
// 5. 保存到数据库
this.updateById(file);
}
```
### **审批后的数据库状态**
| 字段 | 审批前 | 审批后 | 变化 |
|------|-------|--------|------|
| file_path | /.../pending/... | /.../public/... | ✅ 已移动 |
| is_public | 0 | **1** | ✅ 0→1 |
| audit_status | 0 | **1** | ✅ 0→1 |
| file_category | "rule" | **"rule"** | ✅ 无变化已经是rule |
| dept_id | null | **null** | ❌ 未变化! |
---
## 🎯 根本原因分析
### **问题不在审批流程,而在显示逻辑!**
让我重新检查前端的过滤逻辑:
📍 ReadModule.vue:1466-1524 (filteredDocuments)
```javascript
const filteredDocuments = computed(() => {
return documentList.value.filter(doc => {
// 1. 文件类型过滤
let matchesFileType = true
if (currentFileType.value === 'system') {
matchesFileType = isSystemFile(doc) // ← 判断是否为制度文件
} else if (currentFileType.value === 'personal') {
matchesFileType = isPersonalFile(doc) // ← 判断是否为个人文件
}
// 2. 部门分类过滤
let matchesCategory = true
if (selectedCategory.value !== 'all') {
const selectedDept = departmentList.value.find(...)
if (selectedDept) {
const selectedDeptId = selectedDept.id || selectedDept.deptId
matchesCategory = doc.deptId === selectedDeptId || doc.departmentId === selectedDeptId
}
}
return matchesFileType && matchesCategory && matchesSearch
})
})
```
📍 ReadModule.vue:1454-1464 (判断函数)
```javascript
const isPersonalFile = (doc) => {
const category = doc.fileCategory || doc.file_category
return category === 'personal'
}
const isSystemFile = (doc) => {
const category = doc.fileCategory || doc.file_category
return category === 'rule'
}
```
### **⚠️ 发现真正的bug**
虽然 `fileCategory` 已经是 `"rule"`,但还有**第二个过滤条件**
```javascript
// 部门分类过滤
if (selectedCategory.value !== 'all') {
const selectedDept = departmentList.value.find(...)
if (selectedDept) {
const selectedDeptId = selectedDept.id || selectedDept.deptId
matchesCategory = doc.deptId === selectedDeptId || doc.departmentId === selectedDeptId
}
}
```
**问题所在:**
- 管理员上传的文件有 `deptId = 5`targetDeptId
- 普通用户上传的文件 `deptId = null`因为没传targetDeptId
- 如果前端选择了某个部门筛选,普通用户的文件会因为 `deptId` 不匹配而被过滤掉!
但即使不按部门筛选,理论上应该能看到。让我再仔细看看...
---
## 💡 真正的问题定位
经过完整追踪,我发现**代码逻辑本身已经正确**(在我之前的修复基础上),问题最可能是:
### **可能性190%):后端服务没有重启**
- Java代码修改后必须重新编译并重启
- 如果还在运行旧代码,修复不会生效
### **可能性29%):数据库中有历史脏数据**
- 修复前上传的文件已经以错误的 `fileCategory="personal"` 存储在数据库中
- 审批流程修正的是新上传的文件旧数据需要手动SQL修正
### **可能性31%):浏览器缓存**
- 缓存了旧的API响应数据
---
## ✅ 解决方案清单
### 方案1立即重启后端服务必须
```bash
# Windows PowerShell
cd c:\Users\33520\Desktop\制度文件管理学习AI智能体 vue版本\demo\demo\demo
# 编译
mvn clean compile -DskipTests
# 停止旧进程
netstat -ano | findstr :8080
taskkill /PID <PID> /F
# 启动新进程
java -jar target/demo-1.0.0.jar
```
### 方案2修正历史数据
```sql
-- 查看异常数据
SELECT id, file_name, file_category, is_public, audit_status
FROM file
WHERE is_public = 1 AND file_category = 'personal' AND audit_status = 1;
-- 批量修正
UPDATE file
SET file_category = 'rule', update_time = NOW()
WHERE is_public = 1 AND file_category = 'personal' AND audit_status = 1;
```
### 方案3清除缓存
- 浏览器强制刷新:`Ctrl + Shift + R`
- 或者在开发者工具 Network 标签勾选 "Disable cache"
### 方案4使用全新文件测试
不要使用修复前的旧文件测试,**上传一个全新的文件**走完整个流程。
---
## 📊 对比总结表
| 环节 | 管理员上传公共文件 | 普通用户上传公共文件 | 差异点 |
|------|------------------|---------------------|--------|
| **前端传参** | deptId=有值, isPublic='1' | deptId=null, isPublic='1' | deptId |
| **后端接收** | targetDeptId=5, userType=1/2 | targetDeptId=null, userType=3 | 两处都不同 |
| **需要审批** | ❌ false | ✅ true | **关键差异** |
| **存储路径** | /resources/rule/... | /resources/user/.../pending/... | 不同目录 |
| **初始isPublic** | 1 (直接公开) | 0 (待审临时私有) | 不同 |
| **初始fileCategory** | "rule" (因targetDeptId) | "rule" (因isPublic=true) | 相同 ✅ |
| **初始auditStatus** | 1 (已通过) | 0 (待审核) | **关键差异** |
| **审批操作** | 不需要 | 移动文件+更新字段 | 普通用户独有 |
| **审批后isPublic** | 1 (不变) | 0→1 | 普通用户变更 |
| **审批后fileCategory** | "rule" (不变) | "rule" (不变或修正) | 应该相同 ✅ |
| **最终显示位置** | 制度文件 | ❓ 个人文件(如果修复未生效) | **问题点** |
---
## 🔬 验证修复是否生效的方法
### 方法1查看后端日志
上传文件后,在后端控制台查找:
```
文件上传成功: fileName=test.docx, userId=5, deptId=null, isPublic=0, category=rule, auditStatus=0, path=...
^^^^^^^^
必须是 rule
```
审批通过后查找:
```
审核通过,文件分类已从 personal 更新为 rule: fileId=123
```
(如果没有此日志,说明上传时已经是 rule
### 方法2查看前端控制台
打开浏览器 F12 → Console加载文件列表后查找
```
📋 文件分类调试信息:
[1] test.docx:
- fileCategory: rule (原始值: rule)
- auditStatus: 1 (原始值: 1)
- isPublic: 1
```
如果有 ⚠️ 警告,说明数据有问题。
### 方法3直接查数据库
```sql
SELECT id, file_name, file_category, is_public, audit_status, create_time
FROM file
ORDER BY create_time DESC
LIMIT 5;
```
确认最新记录的 `file_category``"rule"`
---
## 🎯 结论
**代码层面已经完全正确**,问题出在运行环境:
1.**首要任务:重启后端服务**(让修改生效)
2. 📊 **次要任务:修正历史数据**(清理脏数据)
3. 🔄 **最后任务:刷新浏览器**(清除缓存)
完成这三步后,问题应该彻底解决!