OpenCloud userlog 服务实战指南:从事件总线到多语言用户通知的实现解析
【免费下载链接】opencloud🌤️ OpenCloud is the open source platform for file management, sharing and collaboration. Simple and sovereign.项目地址: https://gitcode.com/GitHub_Trending/op/opencloud
本文围绕 OpenCloud 仓库中的 services/userlog/README.md 展开,系统讲解userlog服务在日志服务生态中的定位、存储后端的选型与配置、面向用户的 OCS 风格通知 API、全局 deprovision 公告的下发与鉴权机制,以及嵌入式与自定义翻译的完整配置方法。读完后,你将能够独立完成 userlog 的部署调优、配置自定义语言包,并通过 API 向全部用户发布维护公告。
一、userlog 的定位:日志服务生态中的“人类可读”层
在 OpenCloud 中,通知类功能由一组“日志服务”(Log services)协作完成,它们共同负责为特定受众组装消息,但分工明确:
userlog:将事件翻译并调整为人类可读的文案(即本篇主题);clientlog(见 services/clientlog/README.md):组装机器可读消息,让客户端无需回查服务器即可直接采取行动;sse(见 services/sse/README.md):只负责把消息发送出去,不关心消息的形式与语言。
userlog是eventhistory服务与最终客户端之间的中介:它消费事件总线上的事件,把用户关心的事件“记账”到存储里,再通过 HTTP API 提供符合 OC10 通知风格(oc10 notification GET API)的查询接口。
一个关键的部署前提:userlog无法脱离eventhistory单独运行。它读取的是 eventhistory 中持久化的事件本体,自身只保存“某用户应收到哪些事件 ID”的映射。这一点在源码中体现得很清楚:GetEvents先从本地 store 读出一组事件 ID,再调用historyClient.GetEvents换取完整事件内容(见 service.go)。
二、工作流:事件如何变成用户通知
从源码 server.go 中的_registeredEvents列表看,当前版本 userlog 关心的事件是硬编码的(README 也明确指出“哪些用户相关事件值得关注目前是写死的,无法通过配置修改”):
| 分类 | 事件 |
|---|---|
| 文件相关 | PostprocessingStepFinished(病毒扫描、策略执行结果) |
| 空间相关 | SpaceDisabled、SpaceDeleted、SpaceShared、SpaceUnshared、SpaceMembershipExpired |
| 分享相关 | ShareCreated、ShareRemoved、ShareExpired |
| 其他 | ResourceMention(资源被提及) |
每个事件到达后,processEvent会执行四个步骤(见 service.go):
- 找到有资格接收该事件的用户——例如空间类事件通过 CS3 gateway 解析空间成员,分享类事件解析被共享者 ID;
- 过滤——通过
userlogFilter结合 settings 服务的ValueClient,剔除用户设置中不想接收该类通知的用户; - 把事件 ID 存入用户记录——以用户 ID 为 key,在配置的 store 中追加事件 ID;
- 推送 SSE——若未通过
USERLOG_DISABLE_SSE关闭,则按用户语言(locale)把事件转换为通知并通过事件总线发布userlog-notification类型的 SSE 消息。
值得注意的两个过滤细节:病毒扫描结果中未感染的文件不会产生通知;策略执行结果为continue(放行)时同样静默处理(见 service.go),只有真正对用户产生影响的动作才会进入用户通知队列。
三、存储配置(USERLOG_STORE)
userlog通过USERLOG_STORE环境变量(或persistence.store配置项)指定持久化后端,源码中支持的取值为memory、nats-js-kv、redis-sentinel、noop(见 config.go):
memory:基础内存存储,默认值。重启即丢失数据;redis-sentinel:存储到配置的 Redis Sentinel 集群;nats-js-kv:利用 NATS JetStream 的键值存储能力存储数据;noop:什么都不存,仅用于测试,不推荐用于生产。
README 明确提示:其他 store 类型“可能能跑通但不受支持”。
横向扩展的前提:只有在不使用memory存储、且所有实例的 store 配置完全一致时,userlog 才能多实例扩展。
各 store 的专项说明(继承自 README):
- 使用
redis-sentinel时,Redis master 通过 store 节点配置项指定,形式为<sentinel-host>:<sentinel-port>/<redis-master>,例如10.10.0.200:26379/mymaster。注意:README 原文写的是OC_CACHE_STORE_NODES,而从当前配置结构看,对应本服务的变量是OC_PERSISTENT_STORE_NODES或 userlog 专属的USERLOG_STORE_NODES(见 config.go),部署时建议以配置结构为准; - 使用
nats-js-kv时,推荐将 store 节点配置为与OC_EVENTS_ENDPOINT相同的值,让缓存与事件总线复用同一 NATS 实例; nats-js-kv还支持OC_CACHE_DISABLE_PERSISTENCE来指示 NATS 不将缓存数据落盘;- 如果此前使用了已弃用(deprecated)的 store,应尽快迁移到上述受支持的类型,弃用 store 将在后续版本中移除。
store 相关参数在 config.go 中的默认值来自 defaultconfig.go:
| 参数 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
persistence.store | USERLOG_STORE | memory | 存储后端类型 |
persistence.database | USERLOG_STORE_DATABASE | userlog | 存储使用的数据库名 |
persistence.table | USERLOG_STORE_TABLE | events | 存储使用的表 |
persistence.ttl | USERLOG_STORE_TTL | 336h(2 周) | 事件在 store 中的存活时间 |
persistence.username/password | USERLOG_STORE_AUTH_USERNAME/USERLOG_STORE_AUTH_PASSWORD | 空 | 仅nats-js-kv生效 |
persistence.enable_tls等 | USERLOG_STORE_ENABLE_TLS等 | — | 仅nats-js-kv生效(7.3.0 起引入) |
store 的创建在启动命令中完成:store.Create接收类型、TTL、节点、库表名、认证与 TLS 参数(见 server.go)。
其他常用配置(见 config.go):
| 环境变量 | 作用 |
|---|---|
USERLOG_EVENTS_ENDPOINT/USERLOG_EVENTS_CLUSTER | 事件总线(NATS)地址与集群 ID,默认127.0.0.1:9233/opencloud-cluster |
USERLOG_STORE_TTL | 见上表,默认 336 小时 |
USERLOG_MAX_CONCURRENCY | 消费事件的并发 goroutine 数,默认 1;小于 1 的值会被忽略并回退默认(见 defaultconfig.go) |
USERLOG_HTTP_ADDR | HTTP 绑定地址,默认127.0.0.1:9210 |
USERLOG_JWT_SECRET | 用于签发/校验 JWT 的密钥(OC_JWT_SECRET的 service 级覆盖) |
USERLOG_SERVICE_ACCOUNT_ID/USERLOG_SERVICE_ACCOUNT_SECRET | 服务账号凭据,用于通过 gateway 查询用户/资源元数据 |
USERLOG_DISABLE_SSE | 禁用 SSE 推送 |
USERLOG_LOG_LEVEL | 日志级别,默认error |
四、检索 API(GET)
userlog对外暴露的 HTTP 路由挂载在/ocs/v2.php/apps/notifications/api/v1/notifications前缀下,由 service.go 统一注册:
| 方法 | 路径 | 说明 |
|---|---|---|
GET | / | 获取当前用户的通知列表 |
DELETE | / | 按 ID 删除当前用户的通知 |
POST | /global | 发布全局消息(受鉴权限制) |
DELETE | /global | 删除全局消息(受鉴权限制) |
GET的响应体刻意保持 OC10 兼容格式,即 OCS 信封:ocs.meta.statuscode为 200,ocs.data为通知数组(见 http.go)。每条通知的字段映射(见 conversion.go):
{ "notification_id": "…", "app": "userlog", "user": "执行者用户名", "datetime": "RFC3339 时间戳", "object_id": "资源 ID", "object_type": "resource | storagespace | share | mention | global", "subject": "人类可读标题(已翻译)", "subjectRich": "标题模板原文", "message": "人类可读正文(已翻译)", "messageRich": "正文模板原文", "messageRichParameters": { "user": {…}, "space": {…}, "resource": {…}, "share": {…} } }实现上有两个值得了解的行为:
- 语言协商:
GET处理器读取请求头Accept-Language作为 locale 传给转换器(见 http.go、L61),据此选择对应语言的消息文案; - 自动清理:查询过程中若某条事件的资源已不存在(NotFound/PermissionDenied),会被异步标记并删除,同时 store 中已过期的事件 ID 也会异步移除(见 service.go、http.go)。
一个带鉴权头的检索示例(token 需替换为实际会话 token):
curl -s "https://<host>/ocs/v2.php/apps/notifications/api/v1/notifications" \ -H "Authorization: Bearer <token>" \ -H "Accept-Language: de"五、全局公告(Posting):向所有用户发布消息
userlog支持存储全局消息——它会随每个用户的GET请求一并返回,展示在 Web UI 中。用户即使在前端删除,刷新后仍会再次出现。目前仅支持一种类型:deprovision(实例下线公告),用于告知所有用户实例将于某日期下线、数据需在此日期前下载。
端点为/ocs/v2.php/apps/notifications/api/v1/notifications/global,通过POST激活;再次POST同类型消息会覆盖旧消息。请求体只需提供deprovision_date(以及可选的deprovision_date_format),最终文案由服务自动组装。日期字符串默认必须为RFC3339格式,也可通过deprovision_date_format使用任意 Gotime包支持的布局。示例:
curl -X POST "https://<host>/ocs/v2.php/apps/notifications/api/v1/notifications/global" \ -H "Authorization: Bearer <admin-token>" \ -H "Content-Type: application/json" \ -d '{ "type": "deprovision", "data": { "deprovision_date": "2026-12-31T23:59:59Z" } }'源码侧的解析逻辑要求data中必须含deprovision_date,未提供deprovision_date_format时回落到time.RFC3339,解析失败即报错返回(见 service.go)。对应模板见 templates.go:
PlatformDeprovision = NotificationTemplate{ Subject: l10n.Template("Instance will be shut down and deprovisioned"), Message: l10n.Template("Attention! The instance will be shut down and deprovisioned on {date}. Download all your data before that date as no access past that date is possible."), }需要强调:发布公告只是通知手段,实例下线的实际操作并不依赖该消息。
鉴权(Authentication)
POST /global与DELETE /global影响所有用户,因此只有两类身份可以调用(实现见 RequireAdminOrSecret):
- 拥有admin 角色(源码中具体检查
AccountManagementPermissionID权限)的用户; - 知道静态密钥的用户:将
USERLOG_GLOBAL_NOTIFICATIONS_SECRET配置的值放入请求头secret中即可绕过 admin 要求。
两者都不满足时,处理器会以 404 渲染“Not found”,避免暴露端点存在性。
六、删除通知(Deleting)
删除某个用户的事件:向ocs/v2.php/apps/notifications/api/v1/notifications发送DELETE请求,请求体携带要删除的事件 ID 数组(见 DeleteEventsRequest):
curl -X DELETE "https://<host>/ocs/v2.php/apps/notifications/api/v1/notifications" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"ids": ["<event-id-1>", "<event-id-2>"]}'删除全局消息则是对/global端点的DELETE请求,属于受限操作,鉴权规则与“发布”一节相同。
七、翻译机制(Translations)
userlog 内置了通过 transifex 引入的嵌入式翻译,随二进制以embed.FS方式编译(见 conversion.go),覆盖 l10n/locale 目录下的语言:ca、de、el、es、fi、fr、hu、it、ja、ko、nl、no、pl、pt、ru、sv、zh等,适用于所有部署场景。
自定义翻译
若配置了自定义翻译,则完全取代嵌入式翻译(目前无法叠加)。配置方式为将USERLOG_TRANSLATION_PATH指向一个所有 userlog 实例都能访问的基础目录(多实例部署建议使用共享存储)。翻译文件必须是.po或.mo类型,且命名/目录结构固定为:
{USERLOG_TRANSLATION_PATH}/{language-code}/LC_MESSAGES/userlog.po其中language-code的模式是language[_territory],language为基础语言,_territory为可选的国家/地区限定。例如德语de的翻译文件应放在:
{USERLOG_TRANSLATION_PATH}/de_DE/LC_MESSAGES/userlog.po翻译回退规则
- 请求的
language_territory不存在时,回退到language(如de_DE缺失则回退到de); language也不存在时,回退到系统默认英文(en),即代码中的源文案。
重要限制:当前嵌入式 OpenCloud Web 前端只识别主语言码、不处理 territory。因此如果用户请求的是de,而翻译只放在de_DE下,前端将“看不到”它并回退默认语言——自定义翻译务必同时提供请求所用language目录下的文件。
八、内置通知模板一览
README 提到“当前哪些事件值得关注是硬编码的”,这些事件的文案同样集中在 templates.go 中,每个模板含Subject与Message两条文案,占位符在翻译后会被替换为 Go template 语法(如{user}→{{ .username }})再渲染:
| 模板 | Subject(英文源文案) | Message(英文源文案,节选) |
|---|---|---|
VirusFound | Virus found | Virus found in {resource}. Upload not possible. Virus: {virus} |
PoliciesEnforced | Policies enforced | The file {resource} was deleted because it violates the restrictions of this cloud. … |
SpaceShared | Space shared | {user} added you to Space {space} |
SpaceUnshared | Removed from Space | {user} removed you from Space {space} |
SpaceDisabled | Space disabled | {user} disabled Space {space} |
SpaceDeleted | Space deleted | {user} deleted Space {space} |
SpaceMembershipExpired | Membership expired | Access to Space {space} lost |
ShareCreated | Resource shared | {user} shared {resource} with you |
ShareRemoved | Resource unshared | {user} unshared {resource} with you |
ShareExpired | Share expired | Access to {resource} expired |
Mention | You have been mentioned | {user} mentioned you in {resource} |
PlatformDeprovision | Instance will be shut down and deprovisioned | Attention! The instance will be shut down and deprovisioned on {date}. … |
这套模板正是subjectRich/messageRich字段的来源:客户端拿到“模板原文 + 参数”(messageRichParameters中含 user/space/resource/share 结构化数据)即可自行二次渲染,这也是 OC10 rich notifications 兼容性的关键。
九、默认语言(Default Language)
默认语言通过OC_DEFAULT_LANGUAGE环境变量定义,未设置时使用英文。该变量的完整语义以settings服务的文档为准;对 userlog 而言,它决定了翻译缺失时使用的回退语言(见 config.go)。
十、小结:源码入口速查
| 关注点 | 文件 |
|---|---|
| 服务装配、事件注册与 store 创建 | services/userlog/pkg/command/server.go |
| 路由注册、事件处理流水线、全局事件存取 | services/userlog/pkg/service/service.go |
| HTTP 处理器、请求/响应结构、admin 或 secret 鉴权 | services/userlog/pkg/service/http.go |
| 事件到 OC10 通知的转换与模板加载 | services/userlog/pkg/service/conversion.go |
| 通知文案模板与占位符 | services/userlog/pkg/service/templates.go |
| 全部环境变量与默认值 | services/userlog/pkg/config/config.go、services/userlog/pkg/config/defaults/defaultconfig.go |
| 嵌入式翻译文件 | services/userlog/pkg/service/l10n/locale |
| 服务文档(本文蓝本) | services/userlog/README.md |
总体来看,userlog是一个职责单一但链路完整的服务:以eventhistory为事件源,以可插拔 store 为状态层,以硬编码事件集 + 模板化多语言文案为表现层,最终以 OCS 兼容 API 对接 Web 前端。掌握上文第三节(存储)、第五节(全局公告与鉴权)和第七节(翻译)三块配置,即可覆盖绝大多数生产部署场景。
【免费下载链接】opencloud🌤️ OpenCloud is the open source platform for file management, sharing and collaboration. Simple and sovereign.项目地址: https://gitcode.com/GitHub_Trending/op/opencloud
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考