Skip to content

后端功能说明 — CodeNote Server

1. 概述

CodeNote Server 是 CodeNote 二维码管理系统的后端服务。

1.1 核心定位

  • API 服务中心:为 Android 端和管理后台提供约 140 个 REST API 接口
  • 数据管理中心:管理 34 张 MySQL 数据表,实现完整的数据模型和业务关系
  • 离线同步仲裁:支持移动端离线优先设计,提供增量同步、冲突仲裁(last-write-wins)、指数退避重试机制
  • 公开分享服务:通过 Thymeleaf 模板引擎渲染公开分享页面,无需额外部署

1.2 技术栈

  • 框架: Spring Boot 3.2.5 + Kotlin 1.9.23
  • 数据库: MySQL 8.0 + JPA (Hibernate) + Flyway 迁移
  • 认证: JWT (io.jsonwebtoken) + Spring Security
  • 存储: 阿里云 OSS(文件存储)
  • 构建工具: Gradle Kotlin DSL
  • 部署: Docker + Docker Compose + 阿里云容器镜像服务
  • 核心能力: REST API(约140个接口)、34张数据表、会话管理、多账号切换、登录记录审计、离线同步仲裁、全局管理后台 API

1.3 角色体系

Server 端负责所有角色的认证、授权和数据隔离,是权限控制的核心:

系统级角色

  • GUEST(未登录):仅可访问公开分享页
  • USER(普通用户):默认角色,可使用所有个人业务功能
  • ADMIN(系统管理员):可访问管理后台 API,拥有全局数据查看和管理权限

管理后台角色(PLATFORM 域,RBAC 预置,调整)

  • SYSTEM_ADMIN 系统管理员:全量权限(唯一可管理 RBAC 三页/系统配置/配额/操作日志)
  • USER_ADMIN 用户管理员:用户管理 + 组织管理(含转让所有权/成员管理)+ 登录记录/账号(已并入原 ORG_ADMIN 的组织/分享/二维码/资产权限)
  • BUSINESS_ADMIN 业务管理员:二维码/资源/分类/相册/文件/元字段/元模板/模板绑定(meta_*,不含对象注册)
  • OPERATION_ADMIN 运营管理员:活动/签到/通行证/通知/分享/扫码记录

登录方式

  • 用户名/邮箱 + 密码登录
  • 手机号 + 短信验证码登录(PNVS 号码认证服务)
  • 手机号 + 密码登录
  • 一键登录(Android PNVS SDK 取号)
  • 设备免注册登录(deviceId)

组织级角色:OWNER / ADMIN / MEMBER / GUEST


1.4 安全架构

安全架构(认证机制、公开路径清单、权限校验、示例)已迁移至 /reference/api.md 第 6 节。

关键文件:

  • SecurityConfig.kt — Spring Security 配置
  • JwtAuthFilter.kt — JWT 认证过滤器
  • AuthInterceptor.kt(Android) — 客户端认证拦截器

2. 项目结构

codenote-server/
├── src/main/kotlin/com/codenote/
│   ├── CodenoteApplication.kt              # 应用入口
│   ├── common/                             # 公共组件
│   │   ├── ApiResponse.kt                  # 统一响应格式
│   │   ├── BusinessException.kt            # 业务异常
│   │   └── GlobalExceptionHandler.kt       # 全局异常处理
│   ├── config/                             # 配置类
│   │   ├── JacksonConfig.kt                # JSON 序列化配置
│   │   ├── JwtAuthFilter.kt                # JWT 认证过滤器
│   │   ├── SecurityConfig.kt               # Spring Security 配置
│   │   ├── UserIdArgumentResolver.kt       # 用户 ID 参数解析器
│   │   └── WebConfig.kt                    # Web 配置(CORS 等)
│   ├── controller/                         # REST API 控制器(26 个控制器文件)
│   │   ├── AccountSwitchController.kt      # 会话管理/账号关联
│   │   ├── ActivityController.kt           # 活动管理
│   │   ├── AdminChatController.kt          # 管理后台聊天设置/统计
│   │   ├── AdminAccountSwitchController.kt # 管理端会话管理/账号关联
│   │   ├── AdminAuthController.kt          # 管理端登录
│   │   ├── AdminController.kt              # 管理后台 API(14 个子控制器)
│   │   ├── AdminMetaController.kt          # 管理端元数据管理(对象/字段/模板)
│   │   ├── AlbumController.kt              # 水印相册
│   │   ├── AssetController.kt              # 固定资源
│   │   ├── AuthController.kt               # 用户认证
│   │   ├── CategoryController.kt           # 分类管理
│   │   ├── CheckInController.kt            # 签到系统
│   │   ├── FileController.kt               # 文件上传
│   │   ├── LoginRecordController.kt        # 登录记录
│   │   ├── MetaController.kt               # App 端元数据(字段/模板/值)
│   │   ├── NotificationController.kt       # 通知中心
│   │   ├── OrgController.kt                # 组织管理
│   │   ├── OrgMetaController.kt            # 组织级元数据(ORG 字段/模板/fork)
│   │   ├── PassController.kt               # 通行证系统
│   │   ├── PublicQrCodeController.kt       # 业务二维码渲染/落地页
│   │   ├── PublicShareController.kt        # 公开分享
│   │   ├── QrCodeController.kt             # 二维码 CRUD
│   │   ├── ScanRecordController.kt         # 扫码记录
│   │   └── SyncController.kt               # 云同步
│   ├── service/                            # 业务逻辑层(24 个 Service)
│   │   ├── AuthService.kt                  # JWT / BCrypt 密码加密
│   │   ├── ChatService.kt                  # 会话+消息编排(发送/撤回/已读/转发/草稿)
│   │   ├── ChatSyncService.kt              # 增量补拉(afterId 游标)
│   │   ├── ChatSettingService.kt           # 聊天全局设置(类型化+缓存+留痕)
│   │   ├── ContactService.kt               # 好友关系/申请/黑名单/名片码/隐私
│   │   ├── MessageTypeRegistry.kt          # 消息类型注册表(handler/ 8 个实现)
│   │   ├── QrCodeService.kt                # 二维码核心业务
│   │   ├── QrCodeImageService.kt           # 二维码图片即时渲染
│   │   ├── CategoryService.kt              # 分类树管理
│   │   ├── ScanRecordService.kt            # 扫码记录
│   │   ├── FileService.kt                  # OSS 文件上传 + 存储用量自动更新
│   │   ├── SyncService.kt                  # 离线同步仲裁
│   │   ├── NotificationService.kt          # 消息中心
│   │   ├── OrgService.kt                   # 组织管理
│   │   ├── OrgShareService.kt              # 资源共享
│   │   ├── OrgLocationService.kt           # 组织位置管理
│   │   ├── AssetService.kt                 # 固定资源
│   │   ├── MetaObjectService.kt             # 业务对象注册/查询
│   │   ├── MetaFieldService.kt              # 元字段 CRUD + scope 可见性合并
│   │   ├── MetaTemplateService.kt           # 元模板 CRUD + fork + 实体绑定
│   │   ├── MetaValueService.kt              # 字段值读写 + 冗余列回填 + 固定列转写
│   │   ├── FieldValidatorRegistry.kt        # 字段类型校验器注册表(15 种)
│   │   ├── ActivityService.kt              # 活动管理
│   │   ├── CheckInService.kt               # 签到系统
│   │   ├── PassService.kt                  # 通行证系统
│   │   ├── AlbumService.kt                 # 水印相册
│   │   ├── AdminService.kt                 # 管理后台业务
│   │   ├── PublicShareService.kt           # 公开分享
│   │   ├── AccountSwitchService.kt         # 会话管理/账号切换
│   │   ├── LoginRecordService.kt           # 登录记录
│   │   ├── JwtService.kt                   # JWT 令牌管理
│   │   ├── QuotaService.kt                 # 配额管理
│   ├── entity/                             # JPA 实体(45 张表,47 个 @Entity)
│   │   ├── AccountLink.kt                  # 账号关联
│   │   ├── Activity.kt                     # 活动
│   │   ├── AdminLog.kt                     # 操作日志
│   │   ├── AlbumPhoto.kt                   # 相册照片
│   │   ├── Contact.kt                      # 好友关系
│   │   ├── ContactBlock.kt                 # 黑名单
│   │   ├── ContactRequest.kt               # 好友申请
│   │   ├── Conversation.kt                 # 会话
│   │   ├── ConversationMember.kt           # 会话成员
│   │   ├── Message.kt                      # 消息(类型化 content JSON)
│   │   ├── UserChatSetting.kt              # 个人聊天隐私设置
│   │   ├── Asset.kt                        # 资源
│   │   ├── AssetCategory.kt                # 资源分类
│   │   ├── AssetInventory.kt               # 资源盘点
│   │   ├── AssetInventoryItem.kt           # 盘点项
│   │   ├── AssetTransfer.kt                # 资源流转
│   │   ├── Category.kt                     # 分类
│   │   ├── CheckInActivity.kt              # 签到活动
│   │   ├── CheckInConfig.kt                # 签到配置
│   │   ├── CheckInRecord.kt                # 签到记录
│   │   ├── File.kt                         # 文件
│   │   ├── LoginRecord.kt                  # 登录记录
│   │   ├── MetaField.kt                    # 元字段
│   │   ├── MetaObject.kt                   # 业务对象
│   │   ├── MetaObjectBinding.kt            # 实体-模板绑定
│   │   ├── MetaTemplate.kt                 # 元模板
│   │   ├── MetaTemplateField.kt            # 模板-字段明细
│   │   ├── MetaValue.kt                    # 通用字段值
│   │   ├── Notification.kt                 # 通知
│   │   ├── OrgInvitation.kt                # 组织邀请
│   │   ├── OrgLocation.kt                  # 组织位置
│   │   ├── OrgMember.kt                    # 组织成员
│   │   ├── OrgShare.kt                     # 资源共享
│   │   ├── Organization.kt                 # 组织
│   │   ├── Pass.kt                         # 通行证
│   │   ├── PassConfig.kt                   # 通行配置
│   │   ├── PassTemplate.kt                 # 通行模板
│   │   ├── PassUsageRecord.kt              # 通行使用记录
│   │   ├── PublicLink.kt                   # 公开链接
│   │   ├── QrCode.kt                       # 二维码
│   │   ├── ScanRecord.kt                   # 扫码记录
│   │   ├── SyncMapping.kt                  # 同步映射
│   │   ├── SystemSetting.kt                # 系统配置
│   │   ├── TokenBlacklist.kt               # Token 黑名单
│   │   ├── UnsyncedRecord.kt               # 未同步记录
│   │   ├── User.kt                         # 用户
│   │   └── UserSession.kt                  # 用户会话
│   ├── ws/                                 # WebSocket 聊天长连接
│   │   └── ChatWsHandler.kt               # /ws/chat 多设备长连接 + 事件推送
│   ├── repository/                         # JPA Repository(41 个)
│   ├── dto/                                # 请求/响应 DTO(21 个)
│   │   ├── ActivityDto.kt
│   │   ├── AdminDto.kt
│   │   ├── AlbumDto.kt
│   │   ├── AssetDto.kt
│   │   ├── AuthDto.kt
│   │   ├── CategoryDto.kt
│   │   ├── CheckInDto.kt
│   │   ├── FileDto.kt
│   │   ├── NotificationDto.kt
│   │   ├── OrgDto.kt
│   │   ├── OrgShareDto.kt
│   │   ├── OrgLocationDto.kt
│   │   ├── PassDto.kt
│   │   ├── QrCodeDto.kt
│   │   ├── ScanRecordDto.kt
│   │   ├── SyncDto.kt
│   │   └── ...其他 DTO
│   └── common/                             # 公共异常/响应
├── src/main/resources/
│   ├── application.yml                     # 应用配置
│   ├── application-prod.yml                # 生产环境配置
│   ├── db/migration/                       # Flyway 数据库迁移
│   │   └── V1__init_schema.sql             # 初始化 34 表 + 默认管理员
│   ├── templates/                          # Thymeleaf 模板
│   │   └── public-share.html               # 公开分享页
│   └── static/                             # 静态资源
├── build.gradle.kts                        # Gradle 构建配置
├── docker-compose.yml                      # Docker 编排
├── Dockerfile                              # Docker 镜像构建
├── deploy.sh                               # 部署脚本
└── README.md                               # 项目说明

3. API 接口清单

完整 API 接口(约 140 个)参见 /reference/api.md,包含端点、请求/响应格式、安全架构。

3.1 关键架构特性

离线优先设计支持

  • Android 端所有核心操作(扫码、生成、编辑、搜索)完全离线可用
  • Room 本地数据库 + FTS4 全文索引
  • 离线队列 + WorkManager 后台同步
  • 冲突仲裁:last-write-wins,服务端 updated_at 为准
  • 冲突解决:SyncService.resolve() 已实现,acceptServer=true 确认服务端版本,acceptServer=false 允许客户端重新推送覆盖

二维码作为万物入口

  • 普通二维码:URL/WiFi/Contact/TEXT,通过 qr_codes 表管理
  • 业务二维码(资源/活动/签到/通行):内容存储在各业务表 qr_content 字段,通过 /api/qrcode/render 即时渲染
  • 扫码后自动识别内容前缀并路由到对应业务模块

活动容器模式

  • 活动是签到和通行证的容器
  • 通过 has_check_in / has_pass 开关子功能

组织协作体系

  • 三级角色:OWNER(所有者)/ADMIN(管理员)/MEMBER(成员)
  • 转移所有权:OWNER 可转让给其他成员,原 OWNER 降级为 MEMBER
  • 成员邀请:邀请状态枚举 PENDING(待处理)/ ACCEPTED(已接受)/ REJECTED(已拒绝)/ CANCELLED(已撤回)/ EXPIRED(已过期),仅 OWNER/ADMIN 可发起与撤回(撤回仅限 PENDING);有效期 7 天(org_invitations.expires_at,惰性过期:读取或操作时超时自动置 EXPIRED)
  • 加入通道:① OWNER/ADMIN 搜索用户批量邀请(POST /orgs/{id}/invite)② 组织码/二维码自由加入 ③ 邀请审批策略组织码加入需 OWNER/ADMIN 审批 ④ 分享申请链接(POST /orgs/{id}/share-join-link 发 ORG_JOIN_LINK 通知,接收方点通知/扫码 org-join:{code} → 查看组织信息 → 申请加入;GET /orgs/public/by-id/{orgId} 免登录按 ID 反查组织公开信息,供通知跳转)⑤ 发现页加入(§可见性架构):非成员从发现页点公开组织 → GET /api/orgs/{id} 放行只读(PUBLIC 非成员 200 + 空权限,INTERNAL 403)→ POST /api/orgs/{id}/apply 按 joinPolicy 处理(FREE 直接加入 / APPROVAL 提交申请 / INVITE·INTERNAL 拒绝 403,抽 doJoin 与 join-by-code 共用)

管理后台全局审计

  • 管理页面覆盖所有业务模块
  • 所有管理员操作自动记录到 admin_logs

详细 API 接口清单参见 /reference/api.md

版本更新:Android App 版本更新功能参见 /reference/app-update.md,后端提供 /api/app/update/check 公开接口和 system_settings 存储。

Markdown 统一渲染(原《Markdown渲染通用方案》已合并本文档):

设计原则:内容统一为 Markdown 原文存储与编辑;渲染唯一出口在后端(Markdown → 安全 HTML),App 端零解析(WebView 展示)。一份原文,所有端展示一致。

依赖(build.gradle.kts):org.commonmark:commonmark:0.22.0 + commonmark-ext-gfm-tables:0.22.0(表格)+ commonmark-ext-autolink:0.22.0(自动识别 URL)

渲染服务 util/MarkdownRenderer.ktrender() 输出 HTML 片段;renderPlainText()TextContentRenderer 输出纯文本摘要)。安全配置三条硬性要求(踩坑记录):

  1. ⚠️ escapeHtml(true) 必须显式开启 —— 0.22 默认 false,raw HTML 会原样输出 = XSS 漏洞
  2. ⚠️ 扩展(Tables/Autolink)必须 Parser 和 HtmlRenderer 双侧注册,否则表格/自动链接不渲染
  3. ⚠️ urlSanitizer 需配合 sanitizeUrls(true) 开关才生效;丢弃 URL 返回空串而非 null(返回 null 触发 NPE);白名单:http/https + 相对路径保留,其余(javascript:/data:/vbscript: 等)置空

接口约定:任何返回 Markdown 的接口同时带 xxx 原文 + xxxHtml 渲染(摘要场景再加 xxxPlain)。字段名前缀按业务对象命名:协议 content/contentHtml,通知 body/bodyHtml/bodyPlain,更新 releaseNotes/releaseNotesHtml

应用场景

场景存储字段展示端渲染出口
用户服务协议 / 隐私政策agreements.contentApp 协议页协议接口 contentHtml
系统通知正文notifications.body(Markdown)App 通知详情页通知接口 bodyHtml + bodyPlain
更新日志app_updates.release_notesApp 更新弹窗更新接口 releaseNotesHtml(待接入)
公告 / 帮助文档(未来)新表或 system_settingsApp / 后台通用渲染接口

协议agreements 表版本化:只增不改(历史可追溯)、发布即生效(version 自动 +1,旧版本自动失效)、历史版本可回滚激活;GET /api/agreement/current 公开,后台 /api/admin/agreements 管理(列表/发布/激活)。表结构见 /architecture/data-model.md「agreements 用户协议/隐私政策表」,接口见 /reference/api.md M8/A23。 预览POST /api/admin/common/render-markdowncontent:render 权限 + 每用户每分钟 60 次限流)。 Android 展示core:uiMarkdownView 组件(WebView + 后端 HTML,JS 禁用),详见 /reference/android.md


附录:认证接口与新增服务

接口清单

  • 密码/解绑端点:password/forgot-code、password/reset、password/change-code、password/change、email/unbind、phone/unbind
  • 关联账号验证码端点
    • POST /api/auth/sms/link-code(需登录):向目标账号手机号发送关联验证码(type=ACCOUNT_LINK);发码前校验手机号已绑定真实账号,且不能是自己的手机号
    • POST /api/auth/email/link-code(需登录):向目标账号邮箱发送关联验证码(type=ACCOUNT_LINK);校验同上
    • POST /api/auth/link 支持三种验证方式:verifyType = PASSWORD(默认,linkedUserId+password)/ PHONE_CODE / EMAIL_CODE(credential+code);验证码方式由后端从 user_identities 反查目标 userId;关联成功后向目标账号发送站内通知(type=ACCOUNT_LINK,WebSocket 实时推送)
    • 验证码类型 ACCOUNT_LINK 已加入 EmailService 限频(60s + 日上限 5),与 LOGIN/BIND 互不通用
    • LinkedAccountDto/SwitchAccountResponse 返回 phone;账号显示名由端上按规则拼(用户名>手机号>邮箱>设备);管理端 GET /api/admin/auth/link/list 不带 userId 返回全量关联(含 ownerUserId 发起方);POST /api/auth/link/{linkId}/switch 携带 X-Device-Name 写入 session/login_records
    • login_records 新增 city 列(App 定位城市上报,入库时记录);每用户最多保留 50 条(删旧入新);token_refresh 不再写记录(会话保活非用户行为);绑定手机补审计 phone_bind;管理端 GET /api/login-records/admin/all 支持 loginType 参数筛选(含 account_delete 注销账户审计)
  • 昵称词库 admin 端点:/api/admin/nickname/adjectives|nouns CRUD ×8
  • 用户名/密码独立设置PUT /api/users/me/username(设置用户名)、POST /api/auth/password/set(设置密码,不强制登出)
  • 身份分离后行为
    • device-login 自动注册登录,返回统一 LoginResponse
    • phone-login / phone-password-login / oneclick-login / email-login 返回类型统一为 LoginResponse
    • GET /api/auth/devices(登录设备列表:登录时间/最后活跃时间/当前设备标记)、DELETE /api/auth/devices/{deviceId}(移除登录设备 = 吊销该设备全部会话,需重新登录)
    • LoginResponse / UserInfoResponse 包含 hasRecoverableCredential(仅有设备身份时为 false,客户端提示绑定);UserInfoResponse 包含 identities(已绑定的 provider 列表)
    • users 表认证字段迁至 user_identities;手机/邮箱/设备登录与绑定改走 identity 表
    • createAdminUser / updateAdminUser 返回脱敏 UserAdminVO(不含 password 哈希);Android 登录成功且 hasRecoverableCredential=false 时弹窗提醒绑定
    • 登录暴力破解限频:login/phonePasswordLogin 同一凭证 5 分钟 5 次失败锁定 5 分钟;createAdminUser 用户名查重返回 400
    • 设备不绑定账号:logout(deviceId 路径)删除该设备 DEVICE identity;绑定手机/邮箱/设置密码成功后删除该用户全部 DEVICE identity(此后免注册登录创建新账号);凭据登录不再自动登记设备(移除 device_bind);设备管理 = 会话设备管理(user_sessions),移除 = 吊销会话
    • 解绑保护:unbindPhone/unbindEmail 解绑后无任何可登录方式时拒绝(防孤儿账号)
    • 注销账户:POST /api/auth/account/delete(管理员禁止;身份 CASCADE 删除,业务数据保留)
    • verify-mobile:SDK 一键授权 token → 服务端 getMobile 取号后绑定
    • 管理后台全量登录记录:GET /api/login-records/admin/all(仅 ADMIN,可选 userId/hours 筛选)
  • 个性签名PUT /api/users/userinfo 支持 signature(≤100 字符,null 不改、空串清空);UserInfoResponse/listUsers 返回 signature
  • 安全配置:SecurityConfig.permitAll 含 forgot-code/reset;DeviceBindingFilter.skipAllPaths 同步

新增服务

  • NicknameService:昵称生成(词库随机组合,空词库降级)
  • CredentialNormalizer:normalizePhone / normalizeEmail 统一凭证归一化
  • UserIdentity / UserIdentityRepository:认证身份表(PHONE/EMAIL/DEVICE...),登录/绑定/设备管理统一走 identity
  • hasRecoverableCredential(AuthService):= password 非空 ‖ 有 PHONE/EMAIL identity,驱动设备丢失/更换提醒

RBAC 权限系统(接入)

通用三域 RBAC(方案 v3.3 定稿,实施完成)。三域:PLATFORM(后台)/ APP(App)/ ORG(组织)。

核心组件

  • PermissionRegistry:212 权限点常量(PLATFORM 76 / APP 73 / ORG 63),启动幂等同步 rbac_permissions(只增不删,停用代替删除)
  • RbacService / RbacServiceImpl:hasPermission / permissionsOf / roleNameOf / grant·revokeRole / 组织生命周期(createOrgRoles·bindOrgOwner·bindOrgMember·transferOrgOwnership·cleanupOrg·removeUserFromOrg·cleanupUser·bindDefaultAppRole);Caffeine 60s 缓存(key=userId:domain:scopeId),写操作主动失效
  • @RequirePermission(domain, resource, action, orgIdParam, fallbackDomain) + PermissionAspect:域/scope 解析、OWNER/SYSTEM_ADMIN 短路、双域降级(orgId 空→APP 域,不回退 current_org_id)
  • RbacControllerGET /api/rbac/my-permissions + 权限点/角色/用户授权 CRUD(PLATFORM 域 rbac_permission:/rbac_role:/rbac_user_role:*;角色管理覆盖 PLATFORM/APP/ORG 域,ORG 域 scopeId=0=初始角色模板、scopeId=orgId=组织实例角色,OWNER 权限绑定被拒)

关键行为变更

  • users.role / org_members.role 列删除;JWT 不再写 role claim,角色从 RBAC 读取(ROLE_<code> 注入)
  • 后台登录 = PLATFORM 域存在任一角色(AdminAuthController);requireAdmin 同义
  • 组织全流程同步 rbac:创建绑 OWNER / 审批通过·接受邀请绑 MEMBER(PENDING 不绑)/ 退出·移除清理绑定 / 转让旧 OWNER 降 ADMIN
  • 属主语义(决策 B):组织资源由组织角色管理;个人资源(org_id 空)属主私有;findOwned 双域
  • 分享 = 复制副本(qr/album 加 org_id,org_shares 存 source_id+copy_id 映射,取消=物理删副本+删映射)
  • 参与语义:checkin:create 成员即可;pass:issue/verify/revoke 活动创建者豁免 + 组织角色
  • 注销守卫:PLATFORM 域有角色不可注销;组织唯一 OWNER 不可注销(先转让或解散)
  • POST /api/notifications 收紧为 PLATFORM notification:send(原无鉴权漏洞)