24 KiB
通用中后台底座平台建设规划
1. 系统定位
本系统定位为一个通用中后台基础框架。
本框架只提供中后台常用基础能力,不绑定任何具体业务。它的职责是提供稳定、轻量、现代的管理端基础设施,便于后续在同一工程内按需扩展页面、接口和基础模块。
本次设计只包含:
- 前端通用框架
- 后端通用框架
- 用户、组织、角色、菜单、权限、字典、日志等基础模块
本次不包含:
- 任何具体业务模块
- 旧 Vue 页面兼容
- 接口文档平台
- 多语言
- 微前端
2. 建设目标
核心目标:
- UI 现代、极简、高级。
- 前端轻便快速,交互顺滑,首屏和页面切换速度快。
- 后端结构清晰,接口稳定,权限和日志能力完善。
- 项目可以快速获得稳定的中后台基础能力。
- 新页面开发路径固定、清晰、可复制。
- 代码可读性优先,避免过度封装和框架魔法。
设计原则:
- 只做底座,不预设业务。
- 组件库负责基础 UI,不自研复杂 UI 框架。
- 权限、菜单、字典、日志做成通用能力。
- 数据查询、缓存、分页、错误处理形成统一模式。
- 前后端边界清晰,前端不承载安全规则,后端必须做权限校验。
3. 技术栈
3.1 前端技术栈
推荐技术栈:
- Vue 3
- TypeScript
- Vite
- Vue Router
- Pinia
- TanStack Query
- Naive UI
- Tailwind CSS
- lucide-vue-next
- Axios
- VueUse
- Vitest
- Playwright
- ESLint
- Prettier
说明:
- Vue 3 使用
<script setup lang="ts">和 Composition API。 - Pinia 只负责全局状态,不承载页面列表数据。
- TanStack Query 负责服务端状态、缓存、刷新、分页、错误重试。
- Naive UI 负责基础组件,不额外建设复杂主题系统。
- Tailwind CSS 负责布局、间距、响应式和少量自定义样式。
- lucide-vue-next 统一图标风格。
- 系统只使用中文,不做多语言。
3.2 后端技术栈
推荐技术栈:
- Kotlin
- Ktor
- kotlinx.serialization
- Exposed
- HikariCP
- JWT
- Logback
- PostgreSQL
- Redis
- Docker Compose
说明:
- Ktor 负责 HTTP 服务、认证、路由、异常处理。
- kotlinx.serialization 作为 JSON 序列化方案。
- Exposed 作为数据库访问层。
- HikariCP 作为数据库连接池。
- JWT 作为前后端认证令牌。
- Logback 输出系统运行日志。
- PostgreSQL 作为默认关系型数据库。
- Redis 作为缓存、验证码、临时会话、限流等基础设施。
- PostgreSQL、Redis 等配套组件统一用根目录
docker-compose.yml管理。
4. 总体架构
flowchart LR
Web["Vue3 管理端"]
Api["Ktor API Server"]
DB["PostgreSQL"]
Cache["Redis"]
Web -->|"HTTP API / JWT"| Api
Api -->|"Exposed / HikariCP"| DB
Api -->|"Redisson"| Cache
本框架分为两部分:
web:前端管理端。server:后端 API 服务。
前端负责:
- 登录页
- 主布局
- 动态菜单
- 路由守卫
- 页面渲染
- 表格、表单、弹窗等交互
- 查询缓存和请求状态管理
- 按钮级权限展示控制
后端负责:
- 登录认证
- JWT 签发与校验
- 用户管理
- 组织管理
- 角色管理
- 菜单管理
- 权限校验
- 字典管理
- 操作日志
- 开放接口调用日志
- 系统异常日志
- PostgreSQL 连接池
- Redis 客户端
5. 工程结构
建议采用前后端同仓库:
platform-a/
docker-compose.yml
web/
server/
不建议一期引入复杂 monorepo。底座平台先保持清晰、直接、容易部署。
配套组件统一放在项目根目录 docker-compose.yml:
docker-compose.yml
postgres: PostgreSQL 18.3
redis: Redis 8
本地开发默认连接:
PostgreSQL:
host: localhost
port: 5432
database: platform_a
user: platform_a
password: platform_a_password
Redis:
host: localhost
port: 6379
password: platform_a_redis_password
启动配套组件:
docker compose up -d
停止配套组件:
docker compose down
如果需要清空本地数据,再执行:
docker compose down -v
6. 前端架构设计
6.1 前端组织原则
前端按功能组织页面,不在文档中写死完整目录结构。目录和文件根据实际复杂度创建,避免为了满足规范而产生空目录、空文件。
基础原则:
- 页面按功能组织。
- 共享组件放在全局
components目录。 - 页面私有组件、组合逻辑、类型定义放在页面目录内。
- 路由、状态、请求、布局、样式等基础能力保持清晰边界。
- 简单页面可以只保留一个页面文件,复杂页面再拆分私有组件和逻辑。
6.2 页面组织规则
页面组织以可读性为准,不强制固定文件数量。
建议规则:
- 页面入口负责结构和主要交互。
- 页面私有逻辑只服务当前页面时,放在页面目录内。
- 多页面复用的组件、工具、类型再上提到共享目录。
- 表格列、搜索条件、弹窗状态等页面强相关内容优先靠近页面。
6.3 路由设计
路由分为静态路由和动态路由。
静态路由:
/login/403/404/
动态路由:
- 登录后从后端获取菜单。
- 前端根据菜单里的
component字段加载页面。 - 菜单变化后,用户重新登录或刷新权限即可生效。
后端菜单返回示例:
{
"id": "1001",
"parentId": null,
"type": "MENU",
"path": "/system/users",
"name": "SystemUsers",
"component": "system/users/index",
"title": "用户管理",
"icon": "Users",
"permission": "system:user:view",
"sort": 10,
"visible": true,
"keepAlive": true
}
前端页面加载:
const viewModules = import.meta.glob('../features/**/index.vue')
组件路径规则:
system/users/index -> src/features/system/users/index.vue
SPA 页面需要支持页签栏和页面缓存:
- 后台主体使用单页应用模式,页面切换不整页刷新。
- 已打开页面进入页签栏,支持切换、关闭、刷新当前页签。
- 菜单配置中的
keepAlive控制页面是否缓存。 - 页面缓存需要和动态路由、权限刷新配合,用户权限变化后应清理不可访问页面。
- 列表查询数据仍由 TanStack Query 管理,页签缓存主要保留页面组件状态和表单状态。
6.4 Pinia 状态边界
Pinia 只保存全局状态:
- 当前 token
- 当前用户
- 用户权限码
- 用户菜单树
- 当前布局状态
- 字典缓存
不建议放入 Pinia:
- 表格列表数据
- 分页数据
- 搜索条件
- 表单弹窗状态
- 详情数据
这些服务端状态统一交给 TanStack Query 管理。
6.5 TanStack Query 使用规则
TanStack Query 用于:
- 列表查询
- 详情查询
- 字典查询
- 新增、修改、删除后的缓存刷新
- 后台刷新
- 请求错误状态
- 加载状态
建议封装统一 query key:
export const userKeys = {
all: ['system', 'users'] as const,
list: (params: UserQuery) => [...userKeys.all, 'list', params] as const,
detail: (id: string) => [...userKeys.all, 'detail', id] as const,
}
Mutation 成功后按模块刷新:
queryClient.invalidateQueries({ queryKey: userKeys.all })
6.6 HTTP 请求封装
统一响应格式:
export interface ApiResult<T> {
code: string
message: string
data: T
traceId?: string
}
分页格式:
export interface PageResult<T> {
items: T[]
page: number
pageSize: number
total: number
}
HTTP 层职责:
- 自动附加 JWT。
- 处理 401 并跳转登录页。
- 处理 403 并提示无权限。
- 统一处理业务错误。
- 记录
traceId。
6.7 权限控制
前端提供三种权限控制:
- 路由权限:无菜单权限不能进入页面。
- 按钮权限:无操作权限不显示按钮。
- 组件权限:复杂页面局部区域可按权限隐藏。
按钮示例:
<PermissionButton permission="system:user:create">
新增用户
</PermissionButton>
前端权限只负责体验,后端接口必须再次校验权限。
6.8 UI 风格
整体风格:
- 现代
- 极简
- 高级
- 工作台
- 信息密度适中
- 视觉安静
布局建议:
- 左侧导航栏
- 顶部操作栏
- 面包屑
- 页签栏
- 内容区使用浅灰背景
- 页面主体使用白色内容面板
视觉规范:
- 不做花哨渐变背景。
- 不做厚重阴影。
- 不做复杂主题编辑器。
- 卡片只用于真正需要分组的信息。
- 表格页筛选区要轻,不做大面积表单堆叠。
- 图标统一使用 lucide-vue-next。
- 中文字体默认使用系统字体。
推荐 Tailwind 基础风格:
body {
@apply bg-slate-50 text-slate-950 antialiased;
}
推荐主题变量:
:root {
--app-bg: #f8fafc;
--app-surface: #ffffff;
--app-border: #e2e8f0;
--app-text: #0f172a;
--app-muted: #64748b;
--app-primary: #2563eb;
--app-success: #16a34a;
--app-warning: #d97706;
--app-danger: #dc2626;
}
6.9 Naive UI 主题
Naive UI 通过 NConfigProvider 统一配置:
- primary color
- border radius
- font family
- component size
- message provider
- dialog provider
- notification provider
建议默认尺寸:
- 表格:
small - 表单:
medium - 按钮:
medium - 弹窗:保持紧凑
6.10 前端基础页面
一期前端页面:
- 登录
- 工作台首页
- 用户管理
- 组织管理
- 角色管理
- 菜单管理
- 字典管理
- 操作日志
- 开放接口调用日志
7. 后端架构设计
7.1 后端目录结构
当前 server 已经按基础框架骨架整理为:
server/
src/main/kotlin/com/bbit/platform/
Application.kt
common/
ApiResult.kt
BizException.kt
config/
AppConfig.kt
database/
system/
SysUserTable.kt
SysOrgTable.kt
SysRoleTable.kt
SysMenuTable.kt
SysUserRoleTable.kt
SysRoleMenuTable.kt
SysDictTypeTable.kt
SysDictItemTable.kt
SysOperationLogTable.kt
SysApiAccessLogTable.kt
business/
plugins/
CorsPlugin.kt
DatabasePlugin.kt
LoggingPlugin.kt
RedisPlugin.kt
SecurityPlugin.kt
SerializationPlugin.kt
StatusPagesPlugin.kt
当前结构说明:
Application.kt负责应用启动和插件装配。common放统一响应、业务异常等公共模型。config放应用配置读取。plugins放 Ktor 插件和基础设施配置,数据库连接与dbQuery当前也放在DatabasePlugin.kt。database/system放系统基础表定义。database/business预留给后续具体功能表,不在底座阶段预设业务表。
后续只补必要目录,保持结构直接、清晰:
server/
src/main/kotlin/com/bbit/platform/
common/
PageResult.kt
PageQuery.kt
ErrorCode.kt
TraceContext.kt
security/
JwtService.kt
PasswordService.kt
PermissionService.kt
SecurityPrincipal.kt
RequirePermission.kt
bootstrap/
DatabaseInitializer.kt
SeedData.kt
modules/
auth/
system/
user/
org/
role/
menu/
dict/
logs/
operation/
apiaccess/
模块目录内部按需拆分 Routes、Service、Dtos,不提前创建空文件。
7.2 当前默认配置
当前 server/src/main/resources/application.yaml 只保留底座需要的配置:
ktor:
application:
modules:
- com.bbit.platform.ApplicationKt.module
deployment:
port: 8080
app:
name: "Platform A"
env: "local"
database:
url: "jdbc:postgresql://localhost:5432/platform_a"
user: "platform_a"
password: "platform_a_password"
maximumPoolSize: 16
minimumIdle: 4
redis:
url: "redis://127.0.0.1:6379"
password: "platform_a_redis_password"
security:
jwt:
issuer: "platform-a"
audience: "platform-a-admin"
realm: "Platform A"
secret: "change-me-to-a-strong-secret"
accessTokenTtlMinutes: 120
cors:
allowedHosts: "localhost:5173,127.0.0.1:5173"
已经移除的非底座配置:
- 邮件 SMTP
- 阿里云 SDK
- LLM API Key
- 具体业务配置
- 旧项目命名配置
7.3 后端分层规则
每个模块保持简单分层:
Routes:HTTP 路由,处理参数绑定和响应。Service:可供路由复用的服务,承载业务逻辑、权限逻辑、事务边界和必要的数据访问。Dtos:请求和响应对象。
规则:
- Route 不写复杂业务逻辑。
- Service 是事务、业务规则和可复用能力的主要承载层。
- DTO 和数据库实体不要混用。
7.4 统一响应
@Serializable
data class ApiResult<T>(
val code: String,
val message: String,
val data: T? = null,
val traceId: String? = null
)
成功:
{
"code": "0",
"message": "成功",
"data": {}
}
失败:
{
"code": "SYSTEM.USER_NOT_FOUND",
"message": "用户不存在",
"traceId": "..."
}
7.5 认证设计
登录流程:
- 用户提交账号和密码。
- 后端校验用户状态。
- 后端校验密码。
- 后端签发 JWT。
- 前端保存 token。
- 前端调用
/api/auth/me获取用户、菜单、权限。
JWT 内容建议:
{
"sub": "userId",
"username": "admin",
"orgId": "1",
"roles": ["admin"],
"tokenVersion": 1
}
后端需要支持:
- token 过期。
- 用户禁用后拒绝访问。
- 修改密码后通过
tokenVersion使旧 token 失效。
7.6 权限模型
采用 RBAC:
sys_user
sys_role
sys_menu
sys_user_role
sys_role_menu
菜单类型:
CATALOG:目录MENU:页面BUTTON:按钮或接口权限
权限码格式:
system:user:view
system:user:create
system:user:update
system:user:delete
system:role:assign
system:menu:update
system:dict:view
log:operation:view
log:api-access:view
接口权限校验示例:
post("/api/system/users") {
requirePermission("system:user:create")
}
7.7 组织模型
组织使用树结构:
id
parent_id
name
code
sort
status
组织用于:
- 用户归属
- 数据范围预留
- 后续数据范围控制的基础
一期建议先完成组织树和用户归属,数据范围可以预留字段,不做复杂实现。
7.8 字典模型
字典分为:
- 字典类型
- 字典项
字典类型示例:
user_status
org_status
menu_type
log_status
前端登录后可加载常用字典,也可以按需查询。字典适合放入 Pinia 缓存。
字典和代码枚举需要区分:
- 代码枚举用于系统逻辑强依赖的固定值,例如菜单类型、用户状态、角色状态、日志状态。
- 业务字典用于展示、筛选、颜色标识和可配置选项。
- 后端逻辑不能依赖后台随意维护的字典项来决定安全规则。
- 字典项可以影响前端展示,但不能绕过后端权限、状态和数据校验。
7.9 初始化数据
初始化数据放在后端工程内维护,由服务端负责创建和升级基础数据。初始化逻辑应支持重复执行,避免多次启动或多次部署后产生重复数据。
建议初始化内容:
- 默认组织:总部或默认组织。
- 默认管理员用户:admin。
- 默认角色:超级管理员。
- 默认菜单:工作台、系统管理、用户管理、组织管理、角色管理、菜单管理、字典管理、日志管理。
- 默认权限码:页面权限和关键按钮权限。
- 默认字典:用户状态、组织状态、角色状态、菜单类型、日志状态。
实现规则:
- 初始化数据按稳定编码执行 upsert,例如
username、role.code、menu.permission、dict_type.code。 - 管理员账号固定为
admin,初始化密码固定写入后端种子数据,首次登录后可在系统内修改。 - 初始化逻辑不写在路由里,建议放在
bootstrap/SeedData.kt。 - 数据库表初始化和种子数据初始化分开,方便后续迁移到正式迁移工具。
- 初始化失败应让服务启动失败,避免系统处于半初始化状态。
8. 日志设计
日志分三类:
- 系统运行日志
- 操作日志
- 开放接口调用日志
8.1 系统运行日志
系统运行日志通过 Logback 输出。
记录内容:
- 应用启动
- 数据库连接
- 接口异常
- 未捕获异常
- 慢请求
- 关键配置加载结果
建议输出:
- 控制台日志
- 文件日志
- 按日期滚动
- 错误日志单独文件
8.2 操作日志
操作日志记录后台用户在系统内的关键操作。
典型场景:
- 登录成功
- 登录失败
- 新增用户
- 修改用户
- 禁用用户
- 删除用户
- 分配角色
- 修改菜单
- 修改字典
- 重置密码
操作日志字段:
id
trace_id
user_id
username
org_id
operation_type
operation_name
http_method
request_path
request_params
ip
user_agent
status
error_message
cost_ms
created_at
敏感字段处理:
- 密码不记录。
- token 不记录。
- 身份证、手机号等可按规则脱敏。
实现方式:
- 提供
OperationLog注解或路由扩展。 - 对关键接口显式声明操作名称。
- 在请求结束后统一落库。
示例:
post("/api/system/users") {
withOperationLog("新增用户") {
requirePermission("system:user:create")
userService.create(call.receive())
}
}
8.3 开放接口调用日志
开放接口调用日志用于记录平台对外提供的 API 被调用情况。
适用场景:
- 第三方系统调用平台接口。
- 内部其他系统调用平台接口。
- 后续开放 API 网关接入。
字段:
id
trace_id
app_key
app_name
http_method
request_path
request_headers
request_body
response_code
response_body
ip
status
error_message
cost_ms
created_at
敏感字段处理:
- Authorization 不记录原文。
- 签名密钥不记录。
- 请求体和响应体限制长度。
- 大请求体不记录 body。
一期可以先设计数据表和查询页面,开放接口鉴权可后续完善。
9. 数据库设计
9.1 基础表
sys_user
sys_org
sys_role
sys_menu
sys_user_role
sys_role_menu
sys_dict_type
sys_dict_item
sys_operation_log
sys_api_access_log
9.2 通用字段
建议所有业务表保留:
id
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
说明:
id建议使用雪花 ID 或 UUID。created_at、updated_at使用数据库时间或服务端统一时间。- 默认使用软删除,删除时设置
deleted_at和deleted_by。 - 查询默认过滤
deleted_at is null。 - 唯一约束需要考虑软删除数据,避免已删除数据长期占用唯一值。
version用于乐观锁。- 操作日志、开放接口调用日志等纯追加日志表不做软删除,也不需要
updated_at、deleted_at。
软删除规则:
- 普通删除接口默认执行软删除,不直接物理删除数据。
- 删除前需要校验引用关系,例如角色被用户使用、组织下存在用户、菜单被角色引用。
- 系统内置数据可以禁止删除,只允许禁用。
- 物理删除只作为维护操作,不暴露为普通管理端能力。
- 恢复已删除数据不是一期能力,但表结构保留恢复可能性。
9.3 用户表
sys_user
id
username
password_hash
nickname
real_name
phone
email
avatar
org_id
status
token_version
last_login_at
last_login_ip
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
9.4 组织表
sys_org
id
parent_id
name
code
sort
status
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
9.5 角色表
sys_role
id
name
code
description
status
data_scope
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
9.6 菜单表
sys_menu
id
parent_id
type
title
name
path
component
icon
permission
sort
visible
keep_alive
status
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
9.7 字典表
sys_dict_type
id
code
name
status
remark
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
sys_dict_item
id
type_id
label
value
color
sort
status
remark
created_at
created_by
updated_at
updated_by
deleted_at
deleted_by
version
10. 后端接口范围
一期接口范围:
POST /api/auth/login
POST /api/auth/logout
GET /api/auth/me
GET /api/system/users
POST /api/system/users
GET /api/system/users/{id}
PUT /api/system/users/{id}
DELETE /api/system/users/{id}
PUT /api/system/users/{id}/status
PUT /api/system/users/{id}/password
PUT /api/system/users/{id}/roles
GET /api/system/orgs
POST /api/system/orgs
PUT /api/system/orgs/{id}
DELETE /api/system/orgs/{id}
GET /api/system/roles
POST /api/system/roles
GET /api/system/roles/{id}
PUT /api/system/roles/{id}
DELETE /api/system/roles/{id}
PUT /api/system/roles/{id}/menus
GET /api/system/menus
POST /api/system/menus
PUT /api/system/menus/{id}
DELETE /api/system/menus/{id}
GET /api/system/dict-types
POST /api/system/dict-types
PUT /api/system/dict-types/{id}
DELETE /api/system/dict-types/{id}
GET /api/system/dict-items
POST /api/system/dict-items
PUT /api/system/dict-items/{id}
DELETE /api/system/dict-items/{id}
GET /api/logs/operation
GET /api/logs/api-access
接口路径不带版本号,系统规模不大,默认使用稳定路径。为了避免后续频繁破坏前端和已有调用方,需要遵守接口兼容规则:
- 已发布接口路径尽量不改名、不删除。
- 响应字段只增不减,不修改已有字段含义。
- 请求字段新增时必须有默认行为,不能让旧前端请求失败。
- 枚举值可以新增,但前端必须对未知枚举有兜底展示。
- 废弃字段先保留一段时间,代码中标记 deprecated,再择机清理。
- 错误码保持稳定,前端不要依赖错误文案做逻辑判断。
- 破坏性调整优先新增接口,再逐步迁移旧接口。
11. 前端页面范围
一期页面范围:
登录页
工作台首页
用户管理
组织管理
角色管理
菜单管理
字典类型管理
字典项管理
操作日志
开放接口调用日志
403
404
页面标准能力:
- 查询
- 分页
- 新增
- 编辑
- 删除
- 启用/禁用
- 权限控制
- 操作反馈
12. 开发规范
12.1 前端规范
- 统一使用 TypeScript。
- 统一使用
<script setup>。 - 请求统一走
api/http.ts。 - 服务端状态统一使用 TanStack Query。
- 全局状态才进入 Pinia。
- 页面文件夹按功能组织。
- 组件命名使用 PascalCase。
- API 文件按模块拆分。
- 表格列配置尽量靠近页面,不抽成难读的全局配置。
12.2 后端规范
- 统一使用 kotlinx.serialization DTO。
- Route 不写复杂业务逻辑。
- Service 负责事务、业务规则和必要的数据访问。
- 所有接口返回
ApiResult。 - 所有异常进入统一异常处理。
- 所有需要权限的接口必须显式声明权限码。
- 关键操作必须记录操作日志。
- 新增和修改统一维护审计字段。
- 删除默认软删除,普通查询默认排除已删除数据。
- 初始化数据由后端
bootstrap模块负责,必须可重复执行。 - 字典和代码枚举边界清晰,安全规则不能依赖可配置字典。
- 数据库结构先用 Exposed Table 定义表达清楚,正式生产前再补迁移脚本管理机制。
13. 建设顺序
详细建设顺序单独维护在 建设顺序.md。
整体分为四期:
- 一期:基础后端。
- 二期:完整后端。
- 三期:基础前端。
- 四期:完整前端。