拦截项目中的敏感操作

本文最后更新于 2025年7月23日 15:05

分析现有日志中间件执行流程

  • init 方法:注册日志中间件,使用 once.Do() 保证 processLogs 协程以单例模式存在。

  • Shutdown 方法:优雅关闭日志处理器。

  • logFilterHandle 方法:实现了真正的日志记录逻辑,这里可以类比 Java 中的 AOP 来理解,以一种环绕通知的方式实现了日志记录。

    sequenceDiagram
        participant Client
        participant logFilterHandle
        participant BusinessLogic
        participant logChan
        participant processLogs
    
        Client->>logFilterHandle: 发起请求
        alt 正在关闭?
            logFilterHandle->>BusinessLogic: 直接调用next()
        else 正常处理
            logFilterHandle->>logFilterHandle: 提取请求信息
            logFilterHandle->>BusinessLogic: 调用next()
            BusinessLogic-->>logFilterHandle: 返回响应/错误
            logFilterHandle->>logChan: 异步发送日志条目
            Note right of logChan: 非阻塞操作
            logChan->>processLogs: 传递日志条目
            processLogs->>processLogs: 批量处理日志
        end
        logFilterHandle-->>Client: 返回响应
    
  • processLogs 方法:通过一个 for 循环持续监听 logChan 和 shutdown 两个 Channel;并且通过 Channel 和 select 实现日志的异步处理,避免阻塞主流程;通过批量写入来减少I/O操作,提升性能。

    flowchart TD
        A[开始循环] --> B{select监听}
        B -->|logChan| C{通道关闭?}
        C -- 是 --> D[处理剩余日志并退出]
        C -- 否 --> E[追加日志到缓冲区]
        E --> F{缓冲区满?}
        F -- 是 --> G[批量写入并清空缓冲区]
        F -- 否 --> B
        B -->|shutdown| H[处理剩余日志并退出]
    

哪些请求会被拦截

configs/trpc_go_{env}.yaml 中,日志中间件被显式注册到 trpc.cloud.pms.Services.http 服务的 filter 列表中:

1
2
3
4
5
6
7
service:
- name: trpc.cloud.pms.Services.http # 服务名称
protocol: http # 协议类型
port: 8001 # 监听端口
filter:
- login # 其他中间件
- log # 注册log中间件

因此只有 trpc.cloud.pms.Services.http 这个服务的请求会被拦截(也就是所有的 http 请求),然后走日志中间件的处理逻辑。

梳理 service.go 中的增删改操作

1. Add 相关方法:4个

  • AddRole
  • AddPermission
  • AddRoleUser
  • CreatePlmApp

2. Update 相关方法:8个

  • UpdateRole
  • UpdatePermission
  • UpdateResourceRiskLevel
  • UpdatePlmAppManager
  • UpdatePlmProductManager
  • UpdateIndustryProductManager
  • UpdateRoleMemberCapacity
  • UpdateApproveStatus

3. Upsert 相关方法:6个

  • UpsertRolePermission
  • UpsertRoleUser
  • UpsertRoleResource
  • UpsertDutySchedule
  • UpsertPlmPlatformRel
  • UpsertIndustryPlatformRel

4. Del 相关方法:6个

  • DelRole
  • DelPermission
  • DelRoleUser
  • DelResource
  • DelDutySchedule
  • DeletePlmApp

初步思路

方案一:新增独立 Filter

  • 实现方式:在 filter 包下创建一个新的 Filter,通过维护敏感词数组(如敏感资源名和操作类型)进行拦截判断。
  • 流程:当请求命中敏感资源及操作时,异步记录操作日志到数据库,随后放行请求继续执行原逻辑。
  • 问题:该方案可能导致一个请求被多个 Filter(日志 Filter 和敏感操作 Filter)重复拦截,造成资源浪费。

方案二:扩展原有 Log Filter

  • 实现方式:在现有的 log Filter 中增加判断逻辑,若检测到请求涉及敏感资源的敏感操作,则将相关日志写入数据库。
  • 优点:无需新增独立的拦截器,简化系统架构。
  • 缺点:需要对原有 log Filter 的代码进行侵入性修改,可能增加维护复杂度。

目前优先考虑第一个方案,本地测试是可行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 简单实现一个拦截器
func init() {
filter.Register("sensitiveOperation", sensitiveOperationfilterhandle, filter.NoopFilter)
}

func sensitiveOperationfilterhandle(ctx context.Context, req interface{}, next filter.ServerHandleFunc) (interface{}, error) {

// 记录请求日志
staffName := GetStaffName(ctx)
method := codec.Message(ctx).ServerRPCName()

if checkSenstiveOperation(method) {
// 记录敏感操作日志(后续这里修改为异步存入db即可)
log.Infof("sensitive operation: {Staffname: %s, Method: %s}", staffName, method)
}

resp, err := next(ctx, req)
return resp, err
}

func checkSenstiveOperation(method string) bool {
var sensitiveWords = []string{
// 后续添加需要拦截的敏感词
"Role",
}
for _, word := range sensitiveWords {
if strings.Contains(method, word) {
return true
}
}
return false
}

尝试调用 ListRole 接口,可以在控制台看到如下输出:

Clipboard_Screenshot_1753098372.png

表设计

序号 字段名称 字段描述 数据类型
1 操作人 记录执行操作的用户身份 文本类型
2 操作时间 记录操作发生的具体时间(需支持前端实现范围查询功能) 日期时间类型
3 操作对象 指明被操作的目标实体(如用户、产品信息、产品族、产品树等) 文本类型
4 操作类型 操作行为的分类(限定为以下枚举值:新增、修改、删除) 枚举类型
5 操作内容 描述操作的具体行为(必须包含被操作对象的名称作为必填信息) 文本类型
6 原始数据 记录操作执行前的原始状态数据(用于对比或回滚参考) 文本类型
7 操作结果 记录操作执行的最终状态(如成功/失败/部分成功及具体结果说明) 文本类型
8 CreateTime 审计字段 日期时间类型
9 UpdateTime 审计字段 日期时间类型

拦截项目中的敏感操作
http://example.com/2025/07/20/拦截项目中的敏感操作/
作者
Moonike
发布于
2025年7月20日
更新于
2025年7月23日
许可协议