# UniPal **Repository Path**: Kuaima-tech/uni-pal ## Basic Information - **Project Name**: UniPal - **Description**: UniPal 陪玩系统:「服务提供者 — 平台 — 消费者」双边撮合系统。覆盖游戏陪玩、健身陪练、才艺陪练、医疗陪诊、家教陪学、宠物陪玩六大场景,为供需双方提供发布—匹配—交易—履约—评价的一站式陪伴服务平台。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-09-04 - **Last Updated**: 2026-10-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # peiw 基于 **HonoX + SQLite** 的角色权限(RBAC)管理后台 + **陪玩(陪伴服务撮合)交易闭环**。在用户、角色、权限、部门、菜单、会话、通知、审计等底座之上,已实现 P0 业务域:类目/资质等级配置、服务发布、下单支付(模拟)、订单状态机、双币钱包/流水、平台分账、提现审核、实名与资质认证、评价。 行业差异沉淀在「类目 + 资质等级 + 计费方式 + 标签」配置中,游戏陪玩 / 健身陪练 / 陪诊等行业可纯配置接入,方案见 [陪玩系统快速落地方案(多行业通用版)](./docs/陪玩系统快速落地方案_多行业通用版.md)。 界面文案为中文,视觉采用青绿品牌色板(WorkBuddy 风格),支持明暗多主题与侧栏折叠。 规划文档见 [`docs/`](./docs/)(含 [全面升级方案](./docs/Plan/upgrade-plan.md))。 ## 功能概览 ### 身份与访问 | 能力 | 说明 | | --- | --- | | 登录 / 登出 | 邮箱 + 密码;HttpOnly Cookie 会话,有效期 7 天 | | 自助注册 | `/register` 开放注册;新用户为消费者分型、无角色(仅公开菜单) | | 登录防爆破 | 同一邮箱连续失败 5 次锁定 5 分钟(进程内计数) | | 账号状态 | `active` / `disabled`;停用账号立即拒绝会话 | | 修改密码 | 校验当前密码;改密后注销其它设备会话 | | 会话管理 | 查看本账号全部设备会话,可注销其它设备 | ### RBAC | 能力 | 说明 | | --- | --- | | 用户管理 | 创建 / 编辑 / 删除;分配角色与部门;启用停用;批量操作(含撤销停用) | | 角色管理 | 创建 / 编辑 / 删除;勾选权限;角色继承(多层闭包 + 环检测) | | 权限点 | `resource:action` 命名(支持多级,如 `org:department:manage`);树形分组目录 | | 防锁死 | 不能停用 / 删除自己;不能剥离最后一个管理员;内置 `admin` 角色不可改名或删除 | | 权限守卫 | 未登录重定向登录页(API/JSON 请求返回 401);缺权限返回统一 403 页面 | | 审计日志 | 管理端写操作(用户/角色/权限/菜单/部门)落 `audit_logs`,操作人删除后记录保留 | ### 组织与导航 | 能力 | 说明 | | --- | --- | | 部门管理 | 树形部门(总公司 → 大区 → 团队);防环;有子部门或成员时禁止删除 | | 菜单管理 | DB 驱动侧栏:分组 + 菜单项;按权限过滤;排序 / 显隐 / 级联删除 | | 工作台 | 登录后首页,按权限展示快捷入口与当前权限列表 | | 仪表盘 | 用户 / 角色 / 权限数量统计与模块入口 | ### 陪玩业务(P0 交易闭环) | 能力 | 说明 | | --- | --- | | 类目管理 | 树形服务类目(游戏陪玩 / 健身陪练 / 陪诊…),行业配置核心,纯配置接入新行业 | | 资质等级 | 类目下挂等级:认证方式(上传 / 考核)+ 价格区间权益 | | 服务管理 | 发布 / 上下架 / 推荐 / 删除;计费方式(按单 / 按小时 / 按次)+ 标签 + 评分聚合;服务方可自助发布(准入:实名通过 + 已通过资质组合),推荐位仅后台可设 | | 订单管理 | 状态机:待支付 → 待接单 → 进行中 → 已完成 → 已评价;取消 / 退款原路退回;后台可强制退款;超时 30 分钟未接单自动退款(惰性清扫) | | 钱包 + 支付 | 双币账户:站内币(消费)+ 可提现余额;模拟充值;所有资金变动只走 `pay_flows` 一张流水表(前后余额快照) | | 分账结算 | 订单完成自动分账:平台抽成比例走 `PLATFORM_FEE_RATE`(默认 0.2),结算时快照进 `settlements` | | 提现审核 | 固定档位(¥10 / ¥50 / ¥100 / ¥200 / ¥500 / ¥1000)+ 每日限 3 次;申请冻结 → 人工审核 → 线下打款标记 / 驳回解冻 | | 认证管理 | 实名认证(通过自动升级服务方)+ 资质认证(按类目等级),审核结果站内通知 | | 评价 | 订单完成后消费者评分,自动聚合到服务评分 | | C 端页面 | 服务大厅、服务详情下单、我的订单(消费者 / 服务方双视角)、我的钱包、认证中心、我的服务(服务方自助发布) | ### 交互与体验 - **命令面板**(`⌘K`):按权限可见的导航项 + 新建用户 / 切换主题 / 登出 - **通知中心**:当前用户最近 20 条站内通知,支持全部已读 - **主题**:light / dark / corporate / cupcake / nord / synthwave,本地持久化,避免闪屏 - **侧栏**:可折叠为图标模式;分组可收起;移动端抽屉 - **Toast**:URL `?flash=success:消息` 反馈(统一由 `flashRedirect` 生成,全量 URL 编码);批量停用支持一键撤销 - **搜索 / 分页 / 排序**:用户、角色、权限列表(统一的筛选栏 `FilterBar` 与排序表头 `SortHeader`) - **行编辑弹窗单例**:列表页共用 1 个弹窗 DOM,打开时由 `row-modal` island 按行数据填充,不随行数膨胀 - **空状态 / 面包屑 / 确认对话框 / 表单内联错误** ## 技术栈 | 层 | 选型 | | --- | --- | | 运行时 | Node.js,ESM | | Web 框架 | [Hono](https://hono.dev) 4 + [HonoX](https://github.com/honojs/honox) 0.1(文件系统路由、SSR、Islands) | | UI | Hono JSX(`hono/jsx`),无 React 运行时 | | 样式 | Tailwind CSS 4 + daisyUI 5;Geist / Noto Sans SC 可变字体 | | 数据库 | SQLite([better-sqlite3](https://github.com/WiseLibs/better-sqlite3))+ [Drizzle ORM](https://orm.drizzle.team) | | 构建 | Vite 8(client + SSR 两步构建,`@hono/vite-build/node`) | | 测试 | Vitest 4,内存 SQLite | 交互热点(命令面板、通知中心、权限选择器、批量操作、确认按钮、行编辑弹窗、Toast)以 HonoX **Islands** 形式在客户端水合;页面主体走服务端渲染 + 原生 `
` POST,避免引入重量级前端状态库。 ## 架构 ``` 浏览器 │ GET 页面(SSR JSX) / POST 表单 / JSON API ▼ HonoX 路由(app/routes) ├─ 全局中间件:注入 db、请求日志、CSRF、禁止缓存 ├─ requireAuth:校验 Cookie 会话,注入 user + permissions └─ requirePermission:动作级权限校验 │ ▼ RBAC 核心(app/lib/rbac) │ 角色继承闭包 → 权限名集合 ▼ 领域服务(app/lib/biz + app/lib/finance) │ 订单状态机 / 认证审核 / 钱包记账 / 分账 / 提现 ▼ Drizzle + better-sqlite3(WAL + 外键) │ ▼ sqlite.db(启动时自动 migrate + 幂等 ensure* 内置数据) ``` ### 请求处理要点 - **文件系统路由**:`app/routes/*.tsx` 对应页面;`export const POST` 处理写操作。 - **Intent 模式**:同一 POST 入口用隐藏域 `intent` 区分 create / update / delete / 批量操作。 - **Islands**:仅对需要浏览器状态的组件加水合,其余保持 SSR。 - **无缓存**:管理页响应带 `Cache-Control: no-store`,避免列表在增删后不刷新。 - **CSRF**:Hono `csrf()`;本机任意端口默认放行,生产用 `ALLOWED_ORIGINS`。 ### RBAC 模型 ``` User ──< user_roles >── Role ──< role_permissions >── Permission │ └── role_parents(继承,多层闭包,环检测) User ── department(树) Permission ── permission_group(树,按 resource 前缀回填) Menu(分组 / 子项,required_permission 过滤可见性) ``` 权限解析:用户直接角色 ∪ 祖先角色 → 权限名 `Set`。侧栏、工作台卡片、按钮均按该集合过滤;写接口再做一次动作级校验。 内置权限点: | 权限名 | 含义 | | --- | --- | | `user:read` / `user:create` / `user:update` / `user:delete` | 用户 CRUD 与角色分配 | | `role:read` / `role:create` / `role:update` / `role:delete` | 角色与权限点维护 | | `org:department:manage` | 部门树 | | `menu:manage` | 导航菜单 | | `service:read` / `service:manage` | 服务查看 / 上下架·推荐·删除 | | `category:manage` / `level:manage` | 类目 / 资质等级配置 | | `order:read` / `order:manage` | 订单查看 / 取消·退款 | | `cert:review` | 实名 / 资质认证审核 | | `finance:withdraw:review` / `finance:flow:read` | 提现审核 / 财务流水查看与导出 | 内置角色:`admin`(全部权限,启动时回填新权限)、`user`(仅 `user:read`、`role:read`)、`operator`(运营,授予 `biz` 组 9 个业务权限,启动时幂等创建)。 内置数据在进程启动时 **幂等注册**(`ensureBuiltin*`):已存在则跳过,不会清掉 UI 新增项;被删的内置项会在下次启动恢复。 ## 目录结构 ``` app/ client.ts # Islands 客户端入口 server.ts # 启动:ensure* + createApp style.css # Tailwind / daisyUI 主题与密度 components/ # 无状态展示组件(图标、筛选栏、排序表头、分页、Modal…) islands/ # 客户端水合组件(含 row-modal 行编辑单例弹窗) lib/ env.ts # 零依赖 .env 加载 admin/helpers.ts # 列表页公共助手:flashRedirect / parseListParams / buildQueryHref / parseIds / forbidUnless / fmtDate(Time) admin/audit.ts # 审计埋点:audit(c, action, targetType, targetId?, detail?) auth/ # 密码(scrypt)、会话、守卫 db/ # schema、连接、迁移、seed、演示数据(seed-demo)、SQL 日志 rbac/ # 权限点、角色闭包、菜单树 biz/ # 领域服务:订单状态机(order)、认证审核流(cert)、站内通知(notify) finance/ # 资金域:钱包/记账原语(wallet)、分账(settlement)、提现审核(withdraw) routes/ _middleware.ts # 全局中间件 _renderer.tsx # 后台布局(侧栏 / 顶栏 / 面包屑) login.tsx / logout.tsx index.tsx # 工作台 admin/ # 仪表盘、用户、角色、权限、部门、菜单、资料、会话 # + 业务页:类目、资质等级、服务、订单、认证审核、提现审核、财务流水(finance/flows) services/ # C 端:服务大厅、服务详情下单([id].tsx) orders.tsx # C 端:我的订单(消费者 / 服务方双视角) wallet.tsx # C 端:我的钱包(充值 / 提现 / 流水) certs.tsx # C 端:认证中心(实名 + 资质) api/notifications.ts drizzle/ # 迁移 SQL public/ # favicon、theme.js、预设头像 tests/ # Vitest:认证、RBAC、菜单、守卫、SQL 日志、订单状态机、钱包/分账/提现、认证审核 ``` ### 列表页开发套件 新增列表页(如陪玩业务的类目 / 服务 / 订单)直接复用现成套件,以 `admin/users.tsx` 为样板: - `app/lib/admin/helpers.ts`:`parseListParams`(q/page/sort/dir 归一化)、`buildQueryHref`(翻页与排序链接)、`flashRedirect`(操作反馈)、`parseIds`(批量 ids)、`forbidUnless`(POST 权限检查一行式) - `app/components/filter-bar.tsx`:`FilterBar` + `FilterField` 筛选表单 - `app/components/data-table.tsx`:`SortHeader` 排序表头 + `EmptyRow` 空态行 - `app/islands/row-modal.tsx` + `RowModalOpenButton`(`components/modal.tsx`):行编辑单例弹窗,按钮 `data-values` 携带行数据,island 打开时填充表单 ## 快速开始 需要 Node.js(建议 20+)与 npm / pnpm。 ```bash # 安装依赖 npm install # 生成并应用迁移(首次;dev 启动也会自动 migrate) npm run db:generate npm run db:migrate # 写入演示账号(已存在 admin 则跳过) npm run db:seed # 可选:写入陪玩业务演示数据(类目/服务/订单/提现等,幂等) npm run db:seed:demo # 开发 npm run dev ``` 浏览器打开提示的本地地址(通常 `http://localhost:5173`)。 内置账号(`db:seed`): | 邮箱 | 密码 | 角色 | | --- | --- | --- | | `admin@example.com` | `admin123` | admin(全部权限) | | `user@example.com` | `user123` | user(只读) | 业务演示账号(`db:seed:demo`,密码均为 `demo1234`): | 邮箱 | 姓名 | 视角 | | --- | --- | --- | | `xiaofeng@example.com` | 小风 | 服务方(王者陪玩 / 陪诊),有待接单与进行中订单、待审核提现 | | `akai@example.com` | 阿凯 | 服务方(LOL),有一笔已驳回提现与已驳回资质认证 | | `coach-li@example.com` | 李教练 | 服务方(健身私教),含一个已下架服务 | | `boss-wang@example.com` | 王老板 | 消费者,已充值,订单覆盖全部状态 | | `ming@example.com` | 阿明 | 消费者,实名认证待审核、有待支付订单 | 演示数据覆盖:3 个行业类目树、6 个资质等级、6 个服务、8 个订单(7 种状态全覆盖)、3 笔提现(待审 / 打款 / 驳回)、认证多状态。重复执行会自动跳过;重造请删除 `sqlite.db*` 后依次执行 `db:migrate` → `db:seed` → `db:seed:demo`。 密码策略:至少 8 位,且同时包含字母与数字。 ### 生产构建 ```bash npm run build # 先打 client,再打 SSR(共享 dist,不清空) npm run preview # node dist/index.js ``` ### 测试 ```bash npm test # vitest run,使用内存 SQLite + seed ``` 覆盖认证与 CSRF、登录防爆破、改密踢会话、RBAC 守卫、角色继承环、管理员防锁死、权限-菜单引用一致性、菜单幂等与过滤、SQL 日志、审计日志,以及业务域:订单状态机全链(下单/支付/接单/完成/分账/评价/取消退款)、钱包流水、提现审核、认证审核。 ## 环境变量 项目自带零依赖 `.env` 加载器(不覆盖已有系统变量)。可将下列项写入项目根目录 `.env`(已 gitignore): | 变量 | 默认 | 说明 | | --- | --- | --- | | `DB_FILE` | `sqlite.db` | SQLite 文件路径;测试强制 `:memory:` | | `DB_LOG` | 关闭 | `1` / `true` / `on` / `yes` 时打印 SQL(含参数;session token 脱敏);测试环境强制静默 | | `ALLOWED_ORIGINS` | 空 | 生产 CSRF 允许的 Origin,逗号分隔;本机 `localhost` / `127.0.0.1` 始终允许 | | `NODE_ENV` | — | `production` 时会话 Cookie 加 `Secure` | | `PLATFORM_FEE_RATE` | `0.2` | 平台分账抽成比例(0-1),结算时快照进 `settlements.platformRate` | 数据库启用 WAL 与外键。启动时自动执行 `drizzle/` 下迁移。 常用脚本: | 命令 | 作用 | | --- | --- | | `npm run dev` | Vite 开发服务器 | | `npm run build` | client + SSR 两步构建 | | `npm run preview` | 运行 `dist/index.js` | | `npm run db:generate` | 根据 schema 生成迁移 | | `npm run db:migrate` | 应用迁移 | | `npm run db:seed` | 种子数据(内置账号) | | `npm run db:seed:demo` | 陪玩业务演示数据(幂等) | | `npm test` | 跑测试 | ## 安全设计 - 密码:`scrypt` + 随机 salt,`timingSafeEqual` 校验 - 会话:32 字节随机 token,HttpOnly / SameSite=Lax Cookie;过期自动清理 - CSRF:所有状态变更 POST 校验 Origin - 停用账号:拒绝会话并删除该用户 session - 改密:踢掉其它设备 - 表单提交中按钮 loading + disabled,降低重复提交 - 管理页禁止浏览器缓存 ## UI 约定 - 布局:登录态的 `/`、`/admin/*` 与 C 端页面(`/services`、`/orders`、`/wallet`、`/certs`、`/provider/*`)共用侧栏 + 顶栏;登录 / 注册页独立居中卡片 - 密度:根字号 14px;卡片细边框、弱阴影 - 无障碍:焦点环、`prefers-reduced-motion` 关闭动画、确认框 `aria-modal` - 错误页:自定义 404 / 500 卡片页 - 主题脚本 `public/theme.js` 在 CSS 前同步执行,避免主题与侧栏状态闪屏 ## 扩展方向 P0 交易闭环已就绪(见上方「陪玩业务」)。后续迭代按方案文档分期: - **P1(增长与运营)**:分销/邀请返佣、社区动态、敏感词审核、签到、派单管理、护航套餐 - **P2(增值变现)**:爵位/会员、虚拟礼物、工会/机构深化(基于 `departments` 扩展)、语音房、页面装修 DIY - **新行业接入**:后台配置「类目 + 资质等级 + 标签 + 计费方式」即可,无需改代码 新增业务模块时: 1. 在 `PERMISSIONS` / `PERMISSION_GROUPS` 增加业务权限点(启动时幂等注册) 2. 用菜单管理把新路由挂到侧栏,并设置 `requiredPermission` 3. 在 `schema.ts` 增加业务表,走 Drizzle 迁移 4. 列表页直接用「列表页开发套件」(`lib/admin/helpers.ts` + `FilterBar` + `SortHeader` + `row-modal`),以 `admin/users.tsx` 为样板 5. 资金变动只走 `app/lib/finance/` 的记账原语(`pay_flows` 单一流水表);事件通知用 `app/lib/biz/notify.ts` 的 `notify()` 部门树可映射为「机构 / 工作室」;角色继承适合「运营主管继承运营专员权限」这类层级。