# Plus-One-System **Repository Path**: lkShi/plus-one-system ## Basic Information - **Project Name**: Plus-One-System - **Description**: Plus One System —— 基于 Spring Boot + MyBatis Plus + Sa-Token 的后台管理系统脚手架,包含用户/角色/部门/菜单管理、Quartz 定时任务、代码生成、MinIO 文件存储、Prometheus 监控等模块。 - **Primary Language**: Java - **License**: MIT - **Default Branch**: main - **Homepage**: https://www.plus-one.cn/ - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-12-11 - **Last Updated**: 2026-07-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Plus One System > 一个面向"AI 辅助开发"的中后台脚手架 —— 配套 Claude Skills,**让 AI 在 5 分钟内写出符合项目规范的代码**。 [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-4.1.0--M1-brightgreen)](https://spring.io/projects/spring-boot) [![Java](https://img.shields.io/badge/Java-25-orange)](https://openjdk.org/) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-17-blue)](https://www.postgresql.org/) [![License](https://img.shields.io/badge/License-MIT-yellow)](./LICENSE) 配套前端:[Plus-One UI](https://gitee.com/lt-st/plus-one-ui.git)(React + Semi Design) --- ## 目录 - [Part 1 · 外部使用者指南](#part-1--外部使用者指南) - [项目简介](#项目简介) - [功能特性](#功能特性) - [技术栈](#技术栈) - [快速开始](#快速开始) - [部署上线](#部署上线) - [API 速查](#api-速查) - [常见问题](#常见问题) - [Part 2 · 内部开发者指南](#part-2--内部开发者指南) - [整体架构](#整体架构) - [模块划分](#模块划分) - [四层结构详解](#四层结构详解) - [公共组件清单](#公共组件清单) - [新建业务模块(5 步)](#新建业务模块5-步) - [数据库约定](#数据库约定) - [AI 辅助开发(Claude Skills)](#ai-辅助开发claude-skills) - [贡献指南](#贡献指南) - [许可证](#许可证) --- # Part 1 · 外部使用者指南 > 面向:用本系统搭建自己业务的人、二次集成者、运维。 ## 项目简介 Plus-One-System 是基于 **Spring Boot 4 + MyBatis-Plus + PostgreSQL + Sa-Token** 的中后台管理脚手架。 核心定位: - **拿来即用**:内置用户/角色/权限/菜单/部门等基础模块,docker-compose 一键起 - **AI 友好**:所有公共组件封装为 `.claude/skills/plus-one-system-skills/`,Claude 可作为资深架构师参与编码 - **生产可用**:内置 Sa-Token 鉴权、Caffeine+Redis 二级缓存、Quartz 调度、SSE 推送、MinIO 文件存储、在线用户监控、操作日志等企业级特性 ## 功能特性 ### 基础能力 - 用户名密码登录(`/user/login`)+ Sa-Token JWT 鉴权 - 用户 / 角色 / 权限 / 部门 / 菜单 五张基础表 + CRUD 接口 - 系统编码(字典)维护 + 通用下拉框接口 `/common/code/*` - 操作日志自动写入(`@Log` 注解 + AOP) ### 进阶能力 - **站内信**:支持全体 / 指定用户 / 指定部门三种发送范围,未读数 SSE 实时推送 - **在线用户**:登录即记录,拦截器只写 Redis 热路径,Quartz 周期落库 - **Quartz 调度**:可视化维护(`/schedule/*`),按 `beanName.method(args)` 反射调用任意 Spring Bean - **文件上传**:本地磁盘 / MinIO 两套实现,`plus-one.upload.type` 切换 - **异步任务**:`ThreadPoolTaskExecutor` 自动维护 `sys_task` 状态,支持进度上报与取消 - **SSE 通用接入**:新增业务事件只需实现 `SseTopic`,零样板代码 ### 运维能力 - `/actuator/health`、`/actuator/prometheus`(Micrometer) - 日志写入 `/var/log/plus-one/app.log`,由 Promtail 采集 - 全局异常统一返回 `Result.failed`,HTTP 200 + `code=500` ## 技术栈 | 层 | 选型 | 版本 | | --- | --- | --- | | 语言 | Java | 25 | | 框架 | Spring Boot | 4.1.0-M1 | | 持久层 | MyBatis-Plus | 3.5.15 | | 鉴权 | Sa-Token(JWT + Redis) | 1.44.0 | | 缓存 | Caffeine(一级)+ Redis(二级) | — | | 调度 | Quartz(JDBC 存储) | — | | 实时 | Spring WebFlux SseEmitter | — | | 对象存储 | MinIO SDK | 8.5.2 | | 工具集 | Hutool | 5.8.40 | | 数据库 | PostgreSQL | 17 | | 中间件 | Redis | 7.2 | | 监控 | Micrometer + Prometheus | — | | 镜像 | azul/zulu-openjdk | 25 | ## 快速开始 ### 环境要求 - JDK 25(或 21+,与 `pom.xml` 一致) - Maven 3.9+(与 `pom.xml` 的 `modelVersion` 4.1.0 匹配;需自行安装) - Docker + Docker Compose(启动 Postgres + Redis) ### 一键启动(推荐) ```bash # 1. 克隆 git clone https://gitee.com/lkShi/plus-one-system.git cd plus-one-system # 2. 启动 Postgres + Redis(首次启动自动初始化 init_db.sql) docker compose up -d # 3. 打包并启动 mvn spring-boot:run ``` 应用启动后监听 `http://localhost:8080/plus-one`。 ### 默认账号 | 字段 | 值 | | --- | --- | | 用户名 | `admin` | | 密码 | `123456`(SHA-256 + 盐) | | 登录类型 | `sys_password` | ### 环境变量(生产必改) | 变量 | 默认 | 用途 | | --- | --- | --- | | `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` | localhost / 5432 / postgres / 123456 | PostgreSQL 连接 | | `DB_SCHEMA` | `plus_one_base` | Schema 名 | | `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD` | localhost / 6379 / 123456 | Redis | | `SA_TOKEN_JWT_KEY` | `plus-one` | JWT 签名密钥(**生产必须改**) | | `MINIO_URL_INT` / `MINIO_URL_EXT` / `MINIO_AK` / `MINIO_SK` / `MINIO_BUCKET_NAME` | 本地默认 | MinIO | | `SERVERIO_MNTPATH` / `SERVERIO_HTTPPRE` / `SERVER_HTTPUPLOAD` | `/data/plus-one/file` 等 | 本地上传 | | `PLUS_ONE_PROJECT_TYPE` | `show` | **必须设为 `pro`/`dev`/`test`,否则写接口被拦截** | ## 部署上线 ### 构建镜像 ```bash mvn package # 产出 target/plus-one-system-0.1.jar ./deploy.sh # 通过 docker buildx 构建 linux/amd64 镜像并 scp 到远端 ``` > 部署脚本中 `user@user-server-ip` 占位符需替换为实际值。 ### Dockerfile 概要 ```dockerfile FROM docker.1ms.run/azul/zulu-openjdk:25-latest ADD target/plus-one-system-0.1.jar plus-one-system.jar EXPOSE 8080 ENTRYPOINT ["java", "-Duser.timezone=Asia/Shanghai", "-jar", "/plus-one-system.jar"] ``` ### docker-compose 服务 | 服务 | 镜像 | 端口 | 用途 | | --- | --- | --- | --- | | postgres | `postgres:17.5` | 5432 | 数据库(自动执行 `db-file/*.sql`) | | redis | `redis:7.2` | 6379 | 缓存 / Sa-Token / Quartz | ### 监控 - Prometheus 抓取:`GET /actuator/prometheus` - 健康检查:`GET /actuator/health` - 日志:`/var/log/plus-one/app.log` ## API 速查 ### 登录 ```bash curl -X POST http://localhost:8080/plus-one/user/login \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"123456","loginType":"sys_password"}' ``` 返回 `token.tokenValue`,后续请求 Header 加 `satoken: `。 ### 常用接口 | 模块 | URL | 说明 | | --- | --- | --- | | 用户信息 | `GET /sys/userinfo` | 当前用户 | | 用户菜单 | `GET /sys/usermenu` | 权限过滤后的菜单 | | 系统编码 | `POST /sys/code/list` | 字典分页 | | 字典下拉 | `POST /common/code` | 通用字典 | | 角色下拉 | `POST /common/code/role/list` | 角色列表 | | 文件上传 | `POST /oss/url` | 拿上传地址 | | 在线用户 | `POST /monitor/online/list` | 当前在线 | | 站内信 | `POST /msg/list` | 收件箱 | | 调度任务 | `POST /schedule/list` | Quartz Job 列表 | > 详细 API 契约见 [`.claude/skills/plus-one-system-skills/`](.claude/skills/plus-one-system-skills/)。 ## 常见问题
Q: 启动后所有写接口报"演示系统禁止操作"? A: 默认 `plus-one.project.type=show`,演示环境拦截所有写操作。生产部署必须设置: ```bash export PLUS_ONE_PROJECT_TYPE=pro # 或 dev/test ``` 或在 `application.yml` 显式覆盖。
Q: docker-compose 启动后 Postgres 没有数据? A: 首次启动会自动执行 `src/main/resources/db-file/*.sql`。如果是已有 volume 的复用环境,需要手动清理 volume: ```bash docker compose down -v docker compose up -d ```
Q: SSE 接口鉴权失败? A: EventSource 不能自定义 Header,token 必须放 query:`/plus-one/sse/xxx/stream?satoken=xxx`。项目通过 `QueryTokenFilter` 自动把 query 中的 token 复制到 header,Sa-Token 才能识别。
--- # Part 2 · 内部开发者指南 > 面向:参与本项目代码维护、二次开发、贡献代码的人。 ## 整体架构 包根目录 `cn.plus.one`,严格遵循四层结构: ``` Ctrl ← HTTP 边界(.ctrl) └→ Domain ← 门面 / 编排层(.domain) └→ Service ← 业务逻辑(.domain.service) └→ Mapper ← MyBatis-Plus(common.mybatis.db.system.mapper) ``` **铁律**: - Ctrl **不得**直接注入 Service / Mapper,必须经 Domain - Domain **不得**直接注入 Mapper - 业务模块**可**依赖 `common.*`,**禁止**模块间循环依赖 - 危险写接口(CRUD / 调度 / 站内信)首行必须 `CommonUtils.checkProjectType()` ## 模块划分 | 包 | 职责 | 关键类 | | --- | --- | --- | | `auth/` | 登录 / 登出 / 鉴权策略 | `LoginCtrl`、`AuthStrategyService`、`SysUserPasswordAuth` | | `common/` | 公共组件(Result、缓存、Quartz、SSE、上传等) | 见下节 | | `demo/` | 示例模块(新模块模板) | `DemoCtrl`、`DemoTask` | | `monitor/` | 在线用户监控 | `MonitorCtrl`、`OnlineUserService`、`OnlineUserActivityInterceptor` | | `system/` | 系统管理(用户/菜单/部门/角色/编码/日志/公告/站内信/调度/上传) | `SysCtrl`、`OrgCtrl`、`MsgCtrl`、`ScheduleCtrl`、`OssCtrl` | ## 四层结构详解 ### Ctrl 层 ```java @RestController @RequestMapping("/order") public class OrderCtrl { @Resource OrderDomain orderDomain; // ⬅️ 仅注入 Domain @PostMapping("/list") public PageResult list(@RequestBody PageQuery q) { return PageResult.success(orderDomain.page(q)); } @PostMapping("/save") public Result save(@RequestBody Order order) { CommonUtils.checkProjectType(); // ⬅️ 写操作必加 return Result.success(orderDomain.save(order)); } } ``` ### Domain 层(门面) ```java @Component public class OrderDomain { @Resource OrderService orderService; @Resource UserService userService; // ⬅️ 可注入其他 Service(跨模块也行) public Order save(Order order) { Assert.isTrue(CommonUtils.checkLogin(), () -> new BizException("未登录")); return orderService.save(order); } } ``` ### Service 层 ```java @Service public class OrderService { @Resource OrderMapper orderMapper; @Resource OrderService self; // ⬅️ 自注入触发 @Cacheable 代理 @Cacheable(value = "orders", key = "#id", unless = "#result == null") public Order getById(Long id) { ... } @CacheEvict(value = "orders", key = "#order.id") public Order save(Order order) { order.setUpdateOperator(CommonUtils.getLoginId()); order.setUpdateTime(CommonUtils.now()); orderMapper.insertOrUpdate(order); return order; } } ``` ### Mapper 层 ```java public interface OrderMapper extends BaseMapper { // MyBatis-Plus 自动提供 selectList / selectById / insert / updateById 等 } ``` ## 公共组件清单 | 组件 | 路径 | 用途 | | --- | --- | --- | | `Result` / `PageResult` | `common.result` | 统一返回 | | `PageQuery` | `common.query` | 分页请求体 | | `BizException` + `GlobalExceptionHandler` | `common.config.exception` | 异常处理 | | `CommonUtils.checkProjectType()` | `common.util` | 演示环境拦截 | | `@Log` + `LogAspect` | `common.log` | 操作日志 | | 二级缓存 `CacheConfig` | `common.config.cache` | Caffeine + Redis(驱动 `@Cacheable`) | | `PlusOneCacheService` | `common.cache` | 缓存运行统计门面(hitRate / keyCount / 内存) | | `QuartzConfig` + `CommonQuartzJob` | `common.config.quartz` | 任务调度 | | `FileUploadManager` + 两实现 | `common.config.upload` | 文件上传 | | `ThreadPoolTaskExecutor` | `common.threadpool` | 异步任务 | | `SseCtrl` + `SseTopic` | `common.sse` | SSE 通用接入 | | `JsonbTypeHandler` | `common.config.mybatis` | PostgreSQL JSONB | 每个组件的详细用法见 [`.claude/skills/plus-one-system-skills/`](.claude/skills/plus-one-system-skills/)(**强烈推荐 Claude AI 阅读**)。 ### 缓存抽象(`PlusOneCacheService`) > 项目内的缓存读写由 Spring `@Cacheable` 走 `CacheConfig.caffeineCacheManager` Bean;运行时统计(命中率 / key 数 / 内存用量)则统一通过 `PlusOneCacheService` 门面拉取,**业务代码不能直接 `@Qualifier("caffeineCacheManager")` 或调 `StringRedisTemplate.serverCommands()`**。 ```java // ❌ 错误:把底层实现写死,切换 cache.type 时会 NPE 或行为不一致 @Autowired @Qualifier("caffeineCacheManager") private CacheManager caffeineCacheManager; // ✅ 正确:注入门面,由 plus-one.cache.type 自动选择实现 @Resource private PlusOneCacheService cacheService; double hr = cacheService.hitRate(); // 0-100,无法获取返回 0 long keys = cacheService.keyCount(); // 粗略 key 数 ``` 切换后端的步骤: 1. 改 `application.yml` 的 `plus-one.cache.type`(`caffeine` / `redis`) 2. 同包下放一个 `XxxPlusOneCacheService implements PlusOneCacheService`,加 `@ConditionalOnProperty(name="plus-one.cache.type", havingValue="xxx")` 3. 业务代码**零改动** 现有实现在 `common.cache.support`:`CaffeinePlusOneCacheService` / `RedisPlusOneCacheService`。 ## 新建业务模块(5 步) 以新增"订单"模块为例: 1. **建包**:`cn.plus.one.order.{ctrl, domain, domain.service}` 2. **写 Mapper**(如需新表):`common.mybatis.db.system.mapper.OrderMapper extends BaseMapper` 3. **写 Entity**:字段含 `id`、`isActive(@TableLogic)`、`createTime`、`createOperator`、`updateTime`、`updateOperator` 4. **写 Service**:注入 `self` + `OrderMapper`,写 `@Cacheable` / `@CacheEvict` / `Assert.isTrue` 5. **写 Domain + Ctrl**:Ctrl 仅注入 Domain,写操作首行 `CommonUtils.checkProjectType()` 完整模板与代码示例见 [`.claude/skills/plus-one-system-skills/architecture.md`](.claude/skills/plus-one-system-skills/architecture.md)。 ## 数据库约定 - **Schema**:`plus_one_base` - **字段命名**:`is_active`(逻辑删除,`true`=未删)/ `create_time` / `create_operator` / `update_time` / `update_operator` - **主键**:`bigserial` + MyBatis-Plus `IdType.AUTO` - **初始化脚本**:`src/main/resources/db-file/init_db.sql`(业务表)+ `quartz_init_db.sql`(Quartz) - **17 张业务表** + **11 张 Quartz 表**(前缀 `QRTZ_`),详见 [skills/db-schema.md](.claude/skills/plus-one-system-skills/db-schema.md) ## AI 辅助开发(Claude Skills) > **本项目最大的差异化**:所有公共组件都被封装为 Claude 可读的 Skill。 ### Skills 目录结构 ``` .claude/skills/plus-one-system-skills/ ├── SKILL.md # 总索引(自动加载) ├── architecture.md # 四层架构 + new-module 模板 ├── result-and-page.md # Result / PageResult / PageQuery ├── auth-and-sa-token.md # Sa-Token + AuthStrategy ├── exception-and-guard.md # BizException + 演示环境拦截 ├── log-audit.md # @Log + LogAspect ├── cache.md # 二级缓存 ├── quartz.md # Quartz 调度 ├── upload.md # 文件上传策略 ├── async-task.md # 异步任务线程池 ├── sse.md # SSE 通用接入 ├── online-user.md # 在线用户监控 ├── mybatis-plus.md # MyBatis-Plus + 代码生成器 ├── select-code.md # 下拉框接口 ├── db-schema.md # 数据库表约定 └── utils-constants-enums.md # 工具 / 常量 / 枚举 ``` ### 如何使用 当 Claude(Cursor / Claude Code / 其他 AI 工具)在本项目下工作时: 1. 自动加载 `.claude/CLAUDE.md`(项目稳定规范) 2. 主动打开 `.claude/skills/plus-one-system-skills/SKILL.md`(总索引) 3. 按需打开具体 skill 文件作为参考 **效果**:让 Claude 写出符合本项目"四层架构 + 公共组件复用"约定的代码,而不是通用 Spring Boot 代码。 ### 使用示例 > "帮我新增一个订单模块,支持创建 / 查询 / 分页。" Claude 会: 1. 读取 `architecture.md` 了解四层结构 2. 读取 `result-and-page.md` 知道用 `Result` / `PageResult` / `PageQuery` 3. 读取 `exception-and-guard.md` 知道写操作要 `checkProjectType()` 4. 读取 `db-schema.md` 知道表字段约定 5. 生成 `OrderCtrl / OrderDomain / OrderService / OrderMapper / Order` → 全程符合本项目规范,无需人工 review 风格问题。 ## 贡献指南 1. **遵循四层结构**:Ctrl → Domain → Service → Mapper 2. **公共组件优先**:能复用 `common.*` 的不重写 3. **危险写操作加 `checkProjectType()`**:CRUD / 调度 / 站内信 / 用户踢出等 4. **数据库变更同步 `init_db.sql`** 5. **新增公共组件**:建议同时写一个 skill 文件(在 `.claude/skills/plus-one-system-skills/`) 6. **完成任务后运行 `/save-memory`** 把关键决策同步到 `.claude/memories/` 7. **Commit 规范**:建议 `: `(如 `feat: 新增订单模块`、`fix: 修复 SSE 心跳泄露`) ### 本地调试 ```bash # 启动开发依赖 docker compose up -d # 本地运行(自动加载 application.yml + config/common.yml + config/log.yml) mvn spring-boot:run # 打包 mvn package # 查看日志 tail -f /var/log/plus-one/app.log ``` ### 调试技巧 | 场景 | 命令 | | --- | --- | | 看活跃连接 | `GET /actuator/metrics/tomcat.connections` | | 看缓存命中率 | `GET /actuator/metrics/cache.gets` | | 看线程池状态 | `GET /actuator/metrics/executor.completed` | | 看 Prometheus 指标 | `GET /actuator/prometheus` | | 看 sys_logs | `SELECT * FROM sys_logs ORDER BY id DESC LIMIT 100;` | | 看 Quartz 任务 | `POST /schedule/list` | --- ## 许可证 本项目使用 [MIT 许可证](./LICENSE)。