26 KiB
架构设计
**本文引用的文件** - [CrmAppApplication.java](file://crm-app/src/main/java/com/crm/app/CrmAppApplication.java) - [application.yml](file://crm-app/src/main/resources/application.yml) - [AuthApplication.java](file://crm-auth/src/main/java/com/crm/auth/AuthApplication.java) - [application.yml](file://crm-auth/src/main/resources/application.yml) - [pom.xml](file://pom.xml) - [GlobalExceptionHandlerAdvice.java](file://crm-base/src/main/java/com/crm/base/advice/GlobalExceptionHandlerAdvice.java) - [DataScope.java](file://crm-base/src/main/java/com/crm/base/annotation/DataScope.java) - [CorsConfig.java](file://crm-base/src/main/java/com/crm/base/config/CorsConfig.java) - [MybatisPlusConfig.java](file://crm-base/src/main/java/com/crm/base/config/MybatisPlusConfig.java) - [RedisConfig.java](file://crm-base/src/main/java/com/crm/base/config/RedisConfig.java) - [SnowflakeIdWorker.java](file://crm-base/src/main/java/com/crm/base/config/SnowflakeIdWorker.java) - [BaseEntity.java](file://crm-base/src/main/java/com/crm/base/domain/entity/BaseEntity.java) - [OwnedEntity.java](file://crm-base/src/main/java/com/crm/base/domain/entity/OwnedEntity.java) - [Result.java](file://crm-base/src/main/java/com/crm/base/domain/result/Result.java) - [PageResult.java](file://crm-base/src/main/java/com/crm/base/domain/result/PageResult.java) - [SecurityUtils.java](file://crm-base/src/main/java/com/crm/base/security/SecurityUtils.java) - [LoginUser.java](file://crm-base/src/main/java/com/crm/base/security/LoginUser.java) - [DataVisibilityContext.java](file://crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java) - [DataScopeHelper.java](file://crm-base/src/main/java/com/crm/base/security/DataScopeHelper.java) - [AuthService.java](file://crm-auth/src/main/java/com/crm/auth/service/IAuthService.java) - [AuthServiceImpl.java](file://crm-auth/src/main/java/com/crm/auth/service/impl/AuthServiceImpl.java) - [AuthController.java](file://crm-auth/src/main/java/com/crm/auth/controller/AuthController.java) - [SecurityConfig.java](file://crm-auth/src/main/java/com/crm/auth/config/SecurityConfig.java) - [JwtAuthenticationFilter.java](file://crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java) - [TokenService.java](file://crm-auth/src/main/java/com/crm/auth/security/TokenService.java) - [DeptTreeCache.java](file://crm-auth/src/main/java/com/crm/auth/service/DeptTreeCache.java) - [ThirdPartyAuthClientFactory.java](file://crm-auth/src/main/java/com/crm/auth/service/client/ThirdPartyAuthClientFactory.java) - [DingTalkAuthClient.java](file://crm-auth/src/main/java/com/crm/auth/service/client/DingTalkAuthClient.java) - [ThirdPartyUserInfo.java](file://crm-auth/src/main/java/com/crm/auth/service/client/ThirdPartyUserInfo.java) - [FileApi.java](file://crm-file/src/main/java/com/crm/file/api/FileApi.java) - [FileApiImpl.java](file://crm-file/src/main/java/com/crm/file/service/impl/FileApiImpl.java) - [FileController.java](file://crm-file/src/main/java/com/crm/file/controller/FileController.java) - [MinioConfig.java](file://crm-file/src/main/java/com/crm/file/config/MinioConfig.java) - [FileInfoServiceImpl.java](file://crm-file/src/main/java/com/crm/file/service/impl/FileInfoServiceImpl.java) - [OrphanChunkCleanupTask.java](file://crm-file/src/main/java/com/crm/file/task/OrphanChunkCleanupTask.java) - [0009-sso-reuses-scan-login-endpoint.md](file://docs/adr/0009-sso-reuses-scan-login-endpoint.md)更新摘要
所做更改
- 更新了第三方集成模式章节,详细说明单点登录端点复用决策
- 新增了SSO架构设计说明,解释钉钉免登与扫码登录的统一处理机制
- 更新了认证流程图,展示两种登录方式的统一处理路径
- 增强了扩展性设计说明,包含未来JS-SDK支持的规划
目录
引言
本架构设计文档面向架构师与高级开发者,围绕CRM系统的微服务划分、分层设计原则、组件交互关系与数据流向展开。重点阐述安全架构(认证授权、数据权限)、数据访问层(MyBatis-Plus、分布式ID)、缓存策略(Redis、部门树缓存)、文件存储方案(分片上传、对象存储、在线预览)等关键技术决策。同时给出系统边界定义、第三方集成模式与扩展点设计,并提供架构图表以说明模块间的依赖关系与通信机制。
更新 本次更新重点阐述了单点登录(SSO)的架构决策,特别是钉钉免登录场景下复用现有扫码登录端点的设计模式,消除了冗余代码的同时支持多种登录方式。
项目结构
本项目采用多模块Maven工程,按领域与服务进行拆分:
- crm-app:应用启动入口与全局配置聚合
- crm-base:公共基础能力(统一异常、通用实体、安全工具、MyBatis-Plus与Redis配置、分布式ID生成等)
- crm-auth:认证授权与系统管理域(用户、角色、菜单、部门、数据权限、第三方登录)
- crm-file:文件服务(分片上传、断点续传、对象存储、在线预览、清理任务)
graph TB
subgraph "应用层"
APP["crm-app<br/>应用启动"]
end
subgraph "基础能力"
BASE["crm-base<br/>通用配置/安全/工具"]
end
subgraph "业务服务"
AUTH["crm-auth<br/>认证与系统管理"]
FILE["crm-file<br/>文件服务"]
end
APP --> AUTH
APP --> FILE
AUTH --> BASE
FILE --> BASE
图表来源
- CrmAppApplication.java
- AuthApplication.java
- pom.xml
章节来源
- CrmAppApplication.java
- AuthApplication.java
- pom.xml
核心组件
- 统一响应与分页:Result、PageResult
- 通用实体:BaseEntity、OwnedEntity(支持审计字段与数据归属)
- 安全上下文:LoginUser、SecurityUtils、DataVisibilityContext
- 数据权限:@DataScope注解、DataScopeHelper、拦截器与SQL注入器
- MyBatis-Plus:CRUD增强、自动填充、分页插件
- Redis:缓存与分布式会话支撑
- 分布式ID:雪花算法实现
- 文件服务:分片上传、断点续传、对象存储、在线预览、定时清理
- 第三方认证:工厂模式抽象、统一用户信息模型、组织准入校验
章节来源
- Result.java
- PageResult.java
- BaseEntity.java
- OwnedEntity.java
- SecurityUtils.java
- LoginUser.java
- DataVisibilityContext.java
- DataScope.java
- DataScopeHelper.java
- MybatisPlusConfig.java
- RedisConfig.java
- SnowflakeIdWorker.java
- ThirdPartyAuthClientFactory.java
- ThirdPartyUserInfo.java
架构总览
系统采用"网关/应用聚合 + 领域微服务"的分层与解耦模式:
- 应用层:统一入口与跨服务编排
- 领域服务:认证授权、文件服务等独立部署
- 基础能力:被各服务复用,保证一致性与可维护性
graph TB
Client["客户端"] --> API["API层<br/>控制器"]
API --> SVC["服务层<br/>业务编排"]
SVC --> SEC["安全层<br/>鉴权/数据权限"]
SVC --> DB["数据访问层<br/>MyBatis-Plus"]
SVC --> CACHE["缓存层<br/>Redis"]
SVC --> EXT["外部集成<br/>第三方登录/文件预览"]
DB --> DS["数据库"]
CACHE --> RC["Redis集群"]
EXT --> OSS["对象存储"]
EXT --> VIEW["在线预览服务"]
EXT --> THIRD["第三方平台<br/>钉钉OAuth"]
图表来源
- AuthController.java
- AuthService.java
- AuthServiceImpl.java
- SecurityConfig.java
- JwtAuthenticationFilter.java
- TokenService.java
- MybatisPlusConfig.java
- RedisConfig.java
- MinioConfig.java
详细组件分析
认证与安全架构
- 认证流程:客户端提交凭证 → 过滤器校验JWT → 构建登录用户上下文 → 控制器调用服务层完成登录逻辑 → 返回令牌与用户信息
- 授权与数据权限:基于角色的菜单权限与基于注解的数据范围控制,结合SQL注入器动态拼接数据可见性条件
- 第三方登录:通过工厂模式扩展不同平台(如钉钉),统一抽象为第三方认证客户端
更新 单点登录(SSO)采用端点复用策略,钉钉免登录与扫码登录共用同一接口,简化了架构设计。
sequenceDiagram
participant C as "客户端"
participant F as "JwtAuthenticationFilter"
participant A as "AuthController"
participant S as "AuthServiceImpl"
participant T as "TokenService"
participant R as "Redis"
C->>F : "携带JWT请求"
F->>F : "解析并验证Token"
F-->>A : "放行并注入Security上下文"
A->>S : "登录/刷新Token"
S->>T : "生成/更新Token"
T->>R : "写入会话/黑名单"
T-->>S : "返回Token"
S-->>A : "返回结果"
A-->>C : "统一响应Result"
图表来源
- JwtAuthenticationFilter.java
- AuthController.java
- AuthService.java
- AuthServiceImpl.java
- TokenService.java
- RedisConfig.java
章节来源
- SecurityConfig.java
- JwtAuthenticationFilter.java
- TokenService.java
- AuthService.java
- AuthServiceImpl.java
- AuthController.java
数据权限模型
- 注解驱动:@DataScope标注方法或类,声明数据范围级别
- 执行期处理:DataScopeHelper解析当前用户与范围,动态注入SQL过滤条件
- 上下文隔离:DataVisibilityContext在请求链路中传递可见性参数
classDiagram
class DataScope {
+注解 : 声明数据范围
}
class DataScopeHelper {
+解析范围()
+注入SQL条件()
}
class DataVisibilityContext {
+设置可见性()
+获取可见性()
}
DataScopeHelper --> DataVisibilityContext : "读写上下文"
图表来源
- DataScope.java
- DataScopeHelper.java
- DataVisibilityContext.java
章节来源
- DataScope.java
- DataScopeHelper.java
- DataVisibilityContext.java
数据访问层设计
- ORM框架:MyBatis-Plus提供CRUD增强、分页、自动填充、条件构造器
- 分布式ID:SnowflakeIdWorker生成唯一主键,避免冲突
- 通用基类:BaseEntity统一审计字段,OwnedEntity扩展数据归属字段
- 配置:注册MetaObjectFillHandler、分页插件、自定义SQL注入器
flowchart TD
Start(["进入查询"]) --> BuildQuery["构建查询条件"]
BuildQuery --> ApplyScope["应用数据权限条件"]
ApplyScope --> Execute["执行SQL(分页/排序)"]
Execute --> MapEntity["映射到实体(BaseEntity/OwenedEntity)"]
MapEntity --> Return["返回结果集"]
图表来源
- MybatisPlusConfig.java
- SnowflakeIdWorker.java
- BaseEntity.java
- OwnedEntity.java
章节来源
- MybatisPlusConfig.java
- SnowflakeIdWorker.java
- BaseEntity.java
- OwnedEntity.java
缓存策略
- 部门树缓存:将部门层级结构缓存至Redis,降低频繁查询压力
- 会话与令牌:TokenService使用Redis维护会话状态与黑名单
- 通用缓存:RedisConfig提供连接池、序列化等基础能力
sequenceDiagram
participant S as "服务层"
participant D as "DeptTreeCache"
participant R as "Redis"
S->>D : "获取部门树"
D->>R : "读取缓存"
alt "命中"
R-->>D : "返回缓存数据"
D-->>S : "直接返回"
else "未命中"
D->>D : "查询数据库构建树"
D->>R : "写入缓存"
D-->>S : "返回数据"
end
图表来源
- DeptTreeCache.java
- RedisConfig.java
章节来源
- DeptTreeCache.java
- RedisConfig.java
文件存储方案
- 分片上传:前端分片、服务端合并、断点续传与会话管理
- 对象存储:MinIO作为底层存储,统一文件元数据与下载接口
- 在线预览:KkFileViewClient对接预览服务,返回预览链接
- 清理任务:定时扫描孤儿分片,释放存储空间
sequenceDiagram
participant FE as "前端"
participant FC as "FileController"
participant FA as "FileApiImpl"
participant FS as "FileInfoServiceImpl"
participant OSS as "对象存储"
participant PV as "预览服务"
FE->>FC : "发起分片上传"
FC->>FA : "初始化上传会话"
FA->>FS : "创建/更新分片记录"
loop "分片传输"
FE->>FC : "上传分片"
FC->>FA : "保存分片到OSS"
FA->>FS : "更新分片状态"
end
FE->>FC : "触发合并"
FC->>FA : "合并分片并生成文件信息"
FA->>PV : "生成预览链接"
FA-->>FE : "返回文件信息与预览地址"
图表来源
- FileController.java
- FileApi.java
- FileApiImpl.java
- FileInfoServiceImpl.java
- MinioConfig.java
- OrphanChunkCleanupTask.java
章节来源
- FileController.java
- FileApi.java
- FileApiImpl.java
- FileInfoServiceImpl.java
- MinioConfig.java
- OrphanChunkCleanupTask.java
第三方集成模式
- 工厂模式:ThirdPartyAuthClientFactory根据类型选择具体客户端
- 统一抽象:ThirdPartyUserInfo标准化第三方用户信息
- 可扩展:新增平台只需实现客户端接口并注册到工厂
- 单点登录优化:钉钉免登录复用现有扫码登录端点,消除冗余代码
更新 单点登录(SSO)架构采用了端点复用策略,钉钉免登录与扫码登录共用 POST /api/auth/login/dingtalk 接口。这种设计避免了为免登录场景创建独立的SSO端点,因为两种场景的authCode都通过钉钉 oauth2/userAccessToken API换取用户级accessToken,后端处理逻辑完全一致。
sequenceDiagram
participant Browser as "浏览器"
participant DingTalk as "钉钉平台"
participant CRM as "CRM后端"
participant Auth as "认证服务"
participant Client as "钉钉客户端"
Note over Browser,DingTalk : 扫码登录流程
Browser->>DingTalk : "打开扫码页面"
DingTalk-->>Browser : "返回二维码"
Browser->>DingTalk : "用户扫码确认"
DingTalk-->>Browser : "返回authCode"
Browser->>CRM : "POST /api/auth/login/dingtalk?authCode=xxx"
CRM->>Auth : "调用登录服务"
Auth->>Client : "getUserInfo(authCode)"
Client->>DingTalk : "换取accessToken"
DingTalk-->>Client : "返回accessToken"
Client->>DingTalk : "查询用户信息"
DingTalk-->>Client : "返回用户信息"
Client-->>Auth : "返回ThirdPartyUserInfo"
Auth->>Auth : "用户匹配/注册/绑定身份"
Auth-->>CRM : "返回登录结果"
CRM-->>Browser : "返回JWT Token"
Note over Browser,DingTalk : 免登录流程复用同一端点
Browser->>DingTalk : "已登录态访问CRM"
DingTalk-->>Browser : "自动重定向带authCode"
Browser->>CRM : "POST /api/auth/login/dingtalk?authCode=xxx"
Note over CRM : "处理逻辑与扫码登录完全相同"
图表来源
- AuthController.java
- DingTalkAuthClient.java
- ThirdPartyAuthClientFactory.java
- 0009-sso-reuses-scan-login-endpoint.md
章节来源
- ThirdPartyAuthClientFactory.java
- DingTalkAuthClient.java
- ThirdPartyUserInfo.java
- 0009-sso-reuses-scan-login-endpoint.md
单点登录架构设计
新增 基于ADR-0009的决策,系统采用了统一的第三方认证架构:
- 端点复用策略:钉钉免登录与扫码登录共用
POST /api/auth/login/dingtalk接口 - 统一处理逻辑:两种场景的authCode都通过相同的钉钉API换取用户级accessToken
- 零后端改动:前端自行管理OAuth重定向URL和clientId,后端无需区分登录来源
- 扩展性考虑:未来如需支持钉钉桌面客户端内H5免登(JS-SDK),需新增签名端点
classDiagram
class ThirdPartyAuthClient {
<<interface>>
+getType() IdentityTypeEnum
+getUserInfo(authCode) ThirdPartyUserInfo
+isOrgMember(user) boolean
}
class DingTalkAuthClient {
+fetchUserAccessToken(code) String
+fetchUserInfo(token) ThirdPartyUserInfo
+getCorpAccessToken() String
+requestTokenApi(code) String
+requestUserInfoApi(token) String
+requestCorpTokenApi() String
+requestGetByUnionIdApi(token, unionId) String
}
class ThirdPartyAuthClientFactory {
+getClient(type) ThirdPartyAuthClient
}
class AuthServiceImpl {
+login(type, authCode) LoginResultDTO
+doLoginInTx(type, thirdUser) AuthUser
+preCheckOrgMembership(type, user, client) void
}
ThirdPartyAuthClient <|.. DingTalkAuthClient
ThirdPartyAuthClientFactory --> ThirdPartyAuthClient
AuthServiceImpl --> ThirdPartyAuthClientFactory
图表来源
- DingTalkAuthClient.java
- ThirdPartyAuthClientFactory.java
- AuthServiceImpl.java
章节来源
- DingTalkAuthClient.java
- ThirdPartyAuthClientFactory.java
- AuthServiceImpl.java
- 0009-sso-reuses-scan-login-endpoint.md
依赖关系分析
- 模块耦合:crm-app仅负责启动与聚合;crm-auth与crm-file均依赖crm-base
- 内部依赖:auth服务内service→mapper→entity的单向依赖;file服务内controller→api→service→mapper
- 外部依赖:Redis、对象存储、第三方登录平台
graph LR
APP["crm-app"] --> AUTH["crm-auth"]
APP --> FILE["crm-file"]
AUTH --> BASE["crm-base"]
FILE --> BASE
AUTH --> REDIS["Redis"]
FILE --> OSS["对象存储"]
AUTH --> THIRD["第三方登录平台"]
图表来源
- pom.xml
- CrmAppApplication.java
- AuthApplication.java
章节来源
- pom.xml
- CrmAppApplication.java
- AuthApplication.java
性能考量
- 数据访问优化:合理使用分页、索引与条件构造器,避免全表扫描;利用MyBatis-Plus批量操作减少往返
- 缓存策略:热点数据(如部门树、字典)优先缓存;注意缓存穿透、雪崩与一致性策略
- 分布式ID:雪花算法避免中心节点瓶颈,提升写入吞吐
- 文件上传:分片并发上传与断点续传提升大文件体验;合并后及时清理临时分片
- 安全开销:JWT无状态减少会话存储压力;敏感操作加限流与幂等保护
- 第三方调用优化:钉钉企业级token缓存、组织准入校验事务外执行,避免长时间占用数据库连接
[本节为通用指导,不直接分析具体文件]
故障排查指南
- 统一异常处理:GlobalExceptionHandlerAdvice集中捕获业务与系统异常,返回标准错误码与消息
- 常见错误定位:检查日志中的TraceId;核对Redis连接与对象存储配置;确认JWT签名与过期时间
- 数据权限问题:核查@DataScope注解与上下文是否正确设置;检查SQL注入器是否生效
- 文件上传失败:查看分片完整性与合并任务;确认对象存储桶权限与网络连通性
- 第三方登录问题:检查钉钉配置(client-id/secret)、网络连通性、token缓存状态、组织准入校验结果
章节来源
- GlobalExceptionHandlerAdvice.java
- application.yml
- application.yml
结论
本架构以清晰的模块边界与分层设计为基础,通过安全、数据访问、缓存与文件服务的专项设计,满足CRM系统在认证授权、数据权限、高可用与可扩展方面的需求。特别地,单点登录架构通过端点复用策略实现了简洁高效的第三方认证处理。未来可在网关层引入更细粒度的路由与限流,在服务间引入事件总线以提升异步处理能力,并持续完善监控与可观测性体系。
[本节为总结性内容,不直接分析具体文件]
附录
- 系统边界定义:认证与系统管理(crm-auth)、文件服务(crm-file)、基础能力(crm-base)、应用聚合(crm-app)
- 扩展点设计:第三方登录客户端工厂、数据权限注解、文件上传处理器、缓存项命名规范
- 配置要点:跨域、Jackson序列化、Knife4j文档、定时任务属性、雪花ID参数
- SSO设计决策:钉钉免登录复用扫码登录端点,消除冗余代码,支持未来JS-SDK扩展
[本节为补充信息,不直接分析具体文件]