# 审计功能故障排查指南

> **适用场景**: 添加了 @Auditable() 装饰器，但审计日志没有生成  
> **最后更新**: 2025-12-18

---

## ❌ 常见问题

### 问题 1: 添加了 @Auditable() 但没有审计日志

#### 症状
- Controller方法已添加 `@Auditable()` 装饰器
- 执行操作成功，但在 `/audit/logs` 中看不到审计记录
- 数据库 `platform_audit.audit_log` 表中无数据

#### 原因
**审计拦截器（`AuditLogInterceptor`）没有注册为全局拦截器！**

虽然：
- ✅ `AuditModule` 已导入
- ✅ `@Auditable()` 装饰器已添加
- ✅ 代码编译成功

但是：
- ❌ 拦截器未注册到 `app.module.ts`
- ❌ 拦截器不会自动执行

#### 解决方案

**步骤 1**: 在 `app.module.ts` 中添加导入

```typescript
// app.module.ts
import { AuditLogInterceptor } from './audit/interceptors/audit-log.interceptor';
```

**步骤 2**: 注册为全局拦截器

```typescript
@Module({
  providers: [
    // ... 其他 providers
    {
      provide: APP_INTERCEPTOR,
      useClass: AuditLogInterceptor,  // ✅ 注册审计拦截器
    },
  ],
})
export class AppModule {}
```

**步骤 3**: 重启后端

```bash
# 停止后端
Ctrl+C

# 重新启动
npm run start:dev
```

**步骤 4**: 验证

执行任何带 `@Auditable()` 的操作，然后访问：
```bash
http://localhost:6010/audit/logs
```

应该能看到审计记录了！

---

### 问题 2: 审计日志中 userId 为 'anonymous'

#### 症状
- 审计日志已生成
- 但 `who` 字段显示 "anonymous"
- `userId` 是随机生成的UUID

#### 原因
用户未登录或 JWT Guard 未正确设置 `request.user`

#### 解决方案

**检查 Controller 是否有 JwtAuthGuard**:

```typescript
@Controller('users')
@UseGuards(JwtAuthGuard)  // ✅ 确保添加
export class UsersController {
  // ...
}
```

**检查请求是否携带 Token**:
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
  http://localhost:3001/api/v1/users
```

---

### 问题 3: 编译错误 "Cannot find module auditable.decorator"

#### 症状
```
error TS2307: Cannot find module '../../audit/decorators/auditable.decorator'
```

#### 原因
导入路径不正确

#### 解决方案

根据文件位置使用正确的相对路径：

| Controller位置 | 正确的导入路径 |
|---------------|---------------|
| `src/modules/organization/users/` | `'../../../audit/decorators/auditable.decorator'` |
| `src/form-engine/controllers/` | `'../../audit/decorators/auditable.decorator'` |
| `src/parts/` | `'../audit/decorators/auditable.decorator'` |
| `src/parts/controllers/` | `'../../audit/decorators/auditable.decorator'` |

**快速修复**:
```bash
# 查找错误的导入
grep -r "audit/decorators" src --include="*.controller.ts"

# 修复路径（示例）
sed -i "s|'../audit/|'../../audit/|g" src/parts/controllers/*.ts
```

---

### 问题 4: @Sensitive() 不生效

#### 症状
- 审计日志已生成
- 但 `isSensitive` 字段为 `false`
- `riskLevel` 不是 `HIGH`

#### 原因
`@Sensitive()` 装饰器必须和 `@Auditable()` 一起使用

#### 解决方案

```typescript
// ❌ 错误 - @Sensitive() 单独使用不起作用
@Post(':id/password')
@Sensitive()
async changePassword() { ... }

// ✅ 正确 - 必须与 @Auditable() 一起使用
@Post(':id/password')
@Auditable()
@Sensitive()
async changePassword() { ... }
```

---

### 问题 5: 审计日志信息不完整

#### 症状
- 审计日志已生成
- 但缺少 `oldValue`、`newValue`、`changes` 等详细信息

#### 原因
装饰器方式只能记录 HTTP 级别信息，无法自动记录业务数据变更

#### 解决方案

在 Service 层使用手动审计：

```typescript
@Injectable()
export class DepartmentsService {
  constructor(private readonly auditService: AuditService) {}
  
  async addMember(deptId: string, dto: AddMemberDto) {
    // 1. 获取旧值
    const oldMembers = await this.getMembers(deptId);
    
    // 2. 执行变更
    await this.prisma.userDepartmentRel.create({
      data: { userId: dto.userId, departmentId: deptId }
    });
    
    // 3. 获取新值
    const newMembers = await this.getMembers(deptId);
    
    // 4. 手动记录详细审计
    await this.auditService.log({
      module: 'Department',
      action: AuditAction.UPDATE,
      entityType: 'Department',
      entityId: deptId,
      oldValue: { memberCount: oldMembers.length, members: oldMembers },
      newValue: { memberCount: newMembers.length, members: newMembers },
      changes: {
        added: [{ userId: dto.userId, positionId: dto.positionId }]
      },
    });
  }
}
```

---

### 问题 6: UUID 格式错误

#### 症状
```
Error creating UUID, invalid character: found 'm' at 3
```

#### 原因
审计日志的 UUID 字段（`userId`、`entityId` 等）接收到了非 UUID 格式的字符串

#### 解决方案

已在以下文件中修复：
- ✅ `audit-log.interceptor.ts` - 使用 `uuidv4()` 生成标准UUID
- ✅ `audit.controller.ts` - 添加 UUID 格式验证
- ✅ `audit/search/page.tsx` - 前端验证 UUID 格式

**检查清单**:
- [ ] 所有ID字段使用 UUID 格式
- [ ] 匿名用户使用 `uuidv4()` 而不是 `'anonymous'`
- [ ] traceId、requestId 等使用 `uuidv4()` 而不是自定义格式

---

### 问题 7: 查询时报错 "Unknown argument startDate"

#### 症状
```
PrismaClientValidationError: Unknown argument `startDate`
```

#### 原因
Service 层使用对象展开运算符（`...otherFilters`）时，错误地将 `startDate` 和 `endDate` 传递给 Prisma 的 `where` 子句

#### 解决方案

显式提取 `startDate` 和 `endDate`：

```typescript
// ❌ 错误
const { page = 1, limit = 50, ...otherFilters } = filters;
const whereClause = {
  region,
  tenantId,
  ...otherFilters,  // ❌ startDate 和 endDate 也在这里
};

// ✅ 正确
const { page = 1, limit = 50, startDate, endDate, ...otherFilters } = filters;
const whereClause = {
  region,
  tenantId,
  ...otherFilters,  // ✅ 只包含有效字段
};

if (startDate || endDate) {
  whereClause.when = {};
  if (startDate) whereClause.when.gte = startDate;
  if (endDate) whereClause.when.lte = endDate;
}
```

---

## 🔍 调试步骤

### 步骤 1: 检查拦截器是否注册

```bash
# 搜索 APP_INTERCEPTOR
grep -n "APP_INTERCEPTOR" backend/src/app.module.ts

# 应该看到：
# {
#   provide: APP_INTERCEPTOR,
#   useClass: AuditLogInterceptor,
# }
```

### 步骤 2: 检查装饰器是否添加

```bash
# 搜索你的Controller方法
grep -A5 "addMember" src/modules/organization/departments/departments.controller.ts

# 应该看到：
# @Post(':id/members')
# @Auditable()
# async addMember() { ... }
```

### 步骤 3: 检查后端日志

查看后端终端输出，应该能看到：
```
[AuditLogInterceptor] Recording audit log for POST /api/v1/departments/:id/members
```

### 步骤 4: 查询数据库

```bash
cd backend
npm run prisma:studio

# 或者直接查询
psql -d ffws_dev -c "SELECT * FROM platform_audit.audit_log ORDER BY when DESC LIMIT 5;"
```

### 步骤 5: 检查前端网络请求

打开浏览器开发者工具：
1. Network 标签
2. 执行添加成员操作
3. 检查请求是否成功（200 OK）
4. 检查响应是否正常

---

## 📝 验证审计是否工作

### 快速测试脚本

```bash
#!/bin/bash
# 测试审计功能是否正常工作

echo "🧪 测试审计功能..."

# 1. 创建测试用户
RESPONSE=$(curl -s -X POST http://localhost:3001/api/v1/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "test_audit",
    "email": "test@example.com",
    "displayName": "Test User"
  }')

echo "✅ 创建用户响应: $RESPONSE"

# 2. 等待审计日志写入
sleep 2

# 3. 查询审计日志
AUDIT_LOGS=$(curl -s http://localhost:3001/api/v1/audit/logs?limit=1 \
  -H "Authorization: Bearer YOUR_TOKEN")

echo "📊 审计日志: $AUDIT_LOGS"

if echo "$AUDIT_LOGS" | grep -q "test_audit"; then
  echo "✅ 审计功能正常工作！"
else
  echo "❌ 审计功能未生效，请检查配置"
fi
```

---

## 🛠️ 配置检查清单

### Controller 层
- [ ] 导入审计装饰器：`import { Auditable, Sensitive, Financial } from '@/audit/decorators/auditable.decorator'`
- [ ] POST/PUT/PATCH/DELETE 方法添加 `@Auditable()`
- [ ] 敏感操作添加 `@Sensitive()`
- [ ] 财务操作添加 `@Financial()`
- [ ] 导入路径正确（根据文件深度）

### Module 配置
- [ ] `AuditModule` 已导入到 `app.module.ts`
- [ ] `AuditLogInterceptor` 注册为 `APP_INTERCEPTOR`
- [ ] `JwtAuthGuard` 正确配置（用于获取用户信息）

### Service 层（可选）
- [ ] 注入 `AuditService`（如需手动审计）
- [ ] 记录详细的 `oldValue`、`newValue`、`changes`
- [ ] 异常情况也记录审计日志

---

## 🔧 手动测试

### 测试 1: 添加部门成员

```bash
# 1. 添加成员
curl -X POST http://localhost:3001/api/v1/departments/YOUR_DEPT_ID/members \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "USER_UUID",
    "positionId": "POSITION_UUID"
  }'

# 2. 查看审计日志
curl http://localhost:3001/api/v1/audit/logs?limit=1 \
  -H "Authorization: Bearer YOUR_TOKEN"

# 3. 或访问前端页面
# http://localhost:6010/audit/logs
```

### 测试 2: 检查数据库

```sql
-- 查看最新的审计日志
SELECT 
  who, 
  what, 
  module, 
  action, 
  entity_type,
  entity_id,
  status,
  when
FROM platform_audit.audit_log
ORDER BY when DESC
LIMIT 10;

-- 检查是否有部门相关的日志
SELECT COUNT(*) 
FROM platform_audit.audit_log
WHERE module = 'Departments';
```

---

## ⚠️ 已知问题

### Issue 1: 审计拦截器未自动注册

**问题**: 即使 `AuditModule` 是 `@Global()` 模块，拦截器也不会自动注册

**原因**: NestJS 的拦截器需要显式注册为 `APP_INTERCEPTOR`

**状态**: ✅ 已修复（2025-12-18）

**修复方式**: 在 `app.module.ts` 中添加：
```typescript
{
  provide: APP_INTERCEPTOR,
  useClass: AuditLogInterceptor,
}
```

---

### Issue 2: UUID 格式验证问题

**问题**: 非 UUID 字符串导致查询失败

**状态**: ✅ 已修复（2025-12-18）

**修复内容**:
- `audit-log.interceptor.ts` - 所有ID字段使用 `uuidv4()`
- `audit.controller.ts` - 添加 UUID 格式验证
- `audit/search/page.tsx` - 前端实时验证

---

### Issue 3: 时间过滤参数错误

**问题**: `startDate` 和 `endDate` 被错误传递给 Prisma

**状态**: ✅ 已修复（2025-12-18）

**修复内容**:
- `getUserHistory()` - 显式提取时间参数
- `getSensitiveLogs()` - 显式提取时间参数

---

## 📚 相关文档

- [集成指南](./INTEGRATION_GUIDE.md) - 如何正确集成审计功能
- [集成示例](./EXAMPLE_INTEGRATION.md) - 实际案例
- [快速参考](./QUICK_REFERENCE.md) - 常用装饰器

---

## 🆘 仍然无法解决？

### 调试模式

在 `audit-log.interceptor.ts` 中添加调试日志：

```typescript
intercept(context: ExecutionContext, next: CallHandler) {
  const isAuditable = this.reflector.getAllAndOverride<boolean>(
    AUDITABLE_KEY,
    [handler, controller],
  );
  
  // ✅ 添加调试日志
  this.logger.debug(`Checking auditable: ${isAuditable}`);
  
  if (!isAuditable) {
    return next.handle();
  }
  
  // ✅ 记录审计数据
  this.logger.debug(`Recording audit for: ${request.method} ${request.url}`);
  
  // ...
}
```

然后查看后端控制台输出。

---

## 📞 联系支持

如果以上方法都无法解决问题：

1. 查看后端控制台的完整错误日志
2. 查看 `platform_audit.audit_log` 表结构是否正确
3. 检查 Prisma Client 是否最新：`npm run prisma:generate`
4. 联系开发团队

---

**最后更新**: 2025-12-18  
**维护者**: FFOA 开发团队
