# 管理员 vs 普通用户上传文件 - 完整对比分析

## 📋 场景定义

### **场景A：管理员上传公共文件（正常工作 ✅）**
- 用户类型：`userType = 1 (超级管理员)` 或 `userType = 2 (部门管理员)`
- 前端选择：公开文件
- 是否需要审批：**不需要**
- 最终结果：✅ 正确显示在"制度文件"列表

### **场景B：普通用户上传公共文件（存在问题 ❌）**
- 用户类型：`userType = 3 (普通用户)`
- 前端选择：公开文件
- 是否需要审批：**需要** ⚠️
- 最终结果：❌ 审批后显示在"个人文件"列表

---

## 🔍 完整数据流追踪

### **【前端】参数构建阶段**

#### 管理员上传时（ReadModule.vue:1926-1935）
```javascript
const formData = new FormData()
formData.append('file', uploadForm.file)

// 关键差异点1️⃣：deptId 的传递条件
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` 不匹配而被过滤掉！

但即使不按部门筛选，理论上应该能看到。让我再仔细看看...

---

## 💡 真正的问题定位

经过完整追踪，我发现**代码逻辑本身已经正确**（在我之前的修复基础上），问题最可能是：

### **可能性1（90%）：后端服务没有重启**
- Java代码修改后必须重新编译并重启
- 如果还在运行旧代码，修复不会生效

### **可能性2（9%）：数据库中有历史脏数据**
- 修复前上传的文件已经以错误的 `fileCategory="personal"` 存储在数据库中
- 审批流程修正的是新上传的文件，旧数据需要手动SQL修正

### **可能性3（1%）：浏览器缓存**
- 缓存了旧的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. 🔄 **最后任务：刷新浏览器**（清除缓存）

完成这三步后，问题应该彻底解决！
