diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..5431975
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,248 @@
+# Lyzsys 项目文档导航
+
+欢迎使用 Lyzsys 企业级后台管理系统!本文档导航将帮助您快速找到所需的文档资源。
+
+---
+
+## 📚 文档目录
+
+### 🚀 快速开始
+
+| 文档 | 说明 | 适用人群 |
+|-----|------|---------|
+| [项目介绍](./项目介绍.md) | 项目概述、技术栈、核心特性 | 所有人 |
+| [部署指南](./部署指南.md) | 环境搭建、安装部署、配置说明 | 运维、开发 |
+| [常见问题](./常见问题.md) | FAQ、故障排查、解决方案 | 所有人 |
+
+### 🏗️ 架构设计
+
+| 文档 | 说明 | 适用人群 |
+|-----|------|---------|
+| [架构设计](./架构设计.md) | 系统架构、技术选型、设计思路 | 架构师、开发 |
+| [数据库设计](./数据库设计.md) | 数据库表结构、字段说明、ER图 | 开发、DBA |
+
+### 💻 开发文档
+
+| 文档 | 说明 | 适用人群 | 重要程度 |
+|-----|------|---------|---------|
+| [后端开发文档](./后端开发文档.md) | 后端开发指南、核心功能、代码示例 | 后端开发 | ⭐⭐⭐⭐⭐ |
+| [前端开发文档](./前端开发文档.md) | 前端开发指南、组件使用、最佳实践 | 前端开发 | ⭐⭐⭐⭐⭐ |
+| [编码规范](./编码规范.md) | 命名规范、代码规范、最佳实践 | 所有开发 | ⭐⭐⭐⭐⭐ |
+| [新模块开发指南](./新模块开发指南.md) | 新模块开发完整教程(含 Demo 示例) | 所有开发 | ⭐⭐⭐⭐⭐ |
+
+---
+
+## 🎯 按场景查找文档
+
+### 场景 1:我是新加入的开发人员
+
+**推荐阅读顺序**:
+1. [项目介绍](./项目介绍.md) - 了解项目概况
+2. [部署指南](./部署指南.md) - 搭建开发环境
+3. [编码规范](./编码规范.md) - 熟悉编码规范 ⚠️ **必读**
+4. [后端开发文档](./后端开发文档.md) 或 [前端开发文档](./前端开发文档.md) - 根据岗位选择
+5. [新模块开发指南](./新模块开发指南.md) - 学习如何开发新功能
+
+### 场景 2:我要开发一个新的业务模块
+
+**推荐步骤**:
+1. **必读** → [编码规范](./编码规范.md) - 了解命名规范和菜单结构规范
+2. **必读** → [新模块开发指南](./新模块开发指南.md) - 按照步骤创建模块
+3. **参考** → Demo 模块代码(`lyzsys-module-demo`)- 作为实现参考
+4. **参考** → [后端开发文档](./后端开发文档.md) - 查阅具体技术实现
+5. **参考** → [前端开发文档](./前端开发文档.md) - 前端页面开发
+
+### 场景 3:我遇到了问题
+
+**查找方式**:
+1. [常见问题](./常见问题.md) - 先查看 FAQ
+2. [后端开发文档](./后端开发文档.md) - 查看附录的常见问题
+3. GitHub Issues - 搜索或提交问题
+
+### 场景 4:我要了解系统架构
+
+**推荐阅读**:
+1. [项目介绍](./项目介绍.md) - 快速了解
+2. [架构设计](./架构设计.md) - 详细架构说明
+3. [数据库设计](./数据库设计.md) - 数据模型
+4. [后端开发文档](./后端开发文档.md) - 核心功能实现
+
+---
+
+## 📖 重点文档说明
+
+### 🔥 编码规范(必读)
+
+**文件**:[编码规范.md](./编码规范.md)
+
+**核心内容**:
+- ⚠️ **驼峰命名规范**:第一个单词不能只有一个字母(重要!)
+- 📋 **菜单结构规范**:业务菜单优先,系统管理放最后
+- 🏗️ **代码结构规范**:Controller-Service-DAL 三层架构
+- 💾 **数据库设计规范**:表名、字段名、索引规范
+- 🔌 **API 接口规范**:RESTful 风格、统一响应格式
+- 🎨 **前端开发规范**:组件规范、权限控制
+
+**为什么重要**:
+- 避免 MyBatis Plus 字段映射问题
+- 提高代码可读性和可维护性
+- 统一团队开发风格
+
+### 🚀 新模块开发指南(必读)
+
+**文件**:[新模块开发指南.md](./新模块开发指南.md)
+
+**核心内容**:
+- ✅ 完整的开发流程(从数据库设计到前端页面)
+- 📝 逐步操作指南(包含完整代码示例)
+- 🎯 以 **Demo 模块**为参考案例
+- ✔️ 开发检查清单
+
+**包含内容**:
+1. 创建后端模块(Maven 配置、实体类、Service、Controller)
+2. 创建前端页面(API 接口、列表页、表单组件)
+3. 配置菜单权限(一级菜单、二级菜单、按钮权限)
+4. 常见问题解决
+
+### 📦 Demo 示例模块
+
+**模块位置**:
+- 后端:`lyzsys_backend/lyzsys-module-demo`
+- 前端:`lyzsys-ui-admin/src/views/demo/project`
+- SQL:`lyzsys_backend/sql/mysql/demo_*.sql`
+
+**功能展示**:
+- ✅ 项目管理的完整 CRUD 功能
+- ✅ 分页查询、批量删除、Excel 导出
+- ✅ 遵循所有编码规范的标准实现
+- ✅ 前后端完整联调示例
+
+**如何使用**:
+1. 查看 Demo 模块的代码结构
+2. 参考其命名方式和代码风格
+3. 复制并修改为自己的业务模块
+4. 详细步骤见 [新模块开发指南](./新模块开发指南.md)
+
+---
+
+## 📂 文档结构
+
+```
+doc/
+├── README.md # 📖 本文档(文档导航)
+├── 编码规范.md # ⭐⭐⭐⭐⭐ 命名规范、开发规范
+├── 新模块开发指南.md # ⭐⭐⭐⭐⭐ 新模块开发教程
+├── 后端开发文档.md # 后端开发指南
+├── 前端开发文档.md # 前端开发指南
+├── 项目介绍.md # 项目概述
+├── 架构设计.md # 架构设计文档
+├── 数据库设计.md # 数据库设计文档
+├── 部署指南.md # 部署运维文档
+└── 常见问题.md # FAQ 文档
+```
+
+---
+
+## 🔑 关键规范速查
+
+### 驼峰命名规范
+
+⚠️ **核心原则**:第一个单词不能只有一个字母
+
+```java
+// ✅ 正确
+projectId, projectName, establishDate
+
+// ❌ 错误
+pId, pName, eDate
+```
+
+**详细说明**:[编码规范 - 命名规范](./编码规范.md#1-命名规范)
+
+### 菜单结构规范
+
+```
+1-90. 业务菜单(优先展示)
+98. 系统管理
+99. 基础设施
+```
+
+**详细说明**:[编码规范 - 菜单结构规范](./编码规范.md#2-菜单结构规范)
+
+### 权限标识格式
+
+```
+{模块名}:{业务}:{操作}
+
+示例:
+demo:project:query
+demo:project:create
+demo:project:update
+```
+
+**详细说明**:[编码规范 - 菜单配置规范](./编码规范.md#23-权限标识规范)
+
+---
+
+## 🛠️ 开发工具推荐
+
+### 后端开发
+- **IDE**:IntelliJ IDEA(推荐)
+- **JDK**:1.8+
+- **Maven**:3.6+
+- **数据库工具**:Navicat、DataGrip
+
+### 前端开发
+- **IDE**:VSCode、WebStorm
+- **Node.js**:16+
+- **包管理器**:npm、pnpm
+
+### 版本管理
+- **Git 客户端**:Git、SourceTree、GitKraken
+- **代码托管**:GitHub、GitLab
+
+---
+
+## 📞 获取帮助
+
+### 文档问题
+- 查看 [常见问题](./常见问题.md)
+- 搜索 GitHub Issues
+
+### 技术支持
+- 提交 GitHub Issue
+- 联系开发团队
+
+### 参与贡献
+- Fork 项目
+- 提交 Pull Request
+- 完善文档
+
+---
+
+## 📝 文档更新记录
+
+| 日期 | 版本 | 更新内容 | 作者 |
+|-----|------|---------|------|
+| 2024-01 | v1.0 | 创建文档导航,新增编码规范和新模块开发指南 | 开发团队 |
+| 2024-01 | v1.0 | 添加 Demo 示例模块说明 | 开发团队 |
+
+---
+
+## 🎉 开始使用
+
+如果您是第一次使用 Lyzsys,建议按以下顺序阅读文档:
+
+1. ✅ [项目介绍](./项目介绍.md) - 5分钟了解项目
+2. ✅ [部署指南](./部署指南.md) - 搭建开发环境
+3. ✅ [编码规范](./编码规范.md) - **必读**,熟悉编码规范
+4. ✅ [新模块开发指南](./新模块开发指南.md) - 开发第一个模块
+5. ✅ [后端开发文档](./后端开发文档.md) / [前端开发文档](./前端开发文档.md) - 深入学习
+
+祝您使用愉快!🚀
+
+---
+
+**文档维护**:Lyzsys 开发团队
+**最后更新**:2024年
+**文档版本**:v1.0
diff --git a/doc/demo_menu.sql b/doc/demo_menu.sql
new file mode 100644
index 0000000..18b59b8
--- /dev/null
+++ b/doc/demo_menu.sql
@@ -0,0 +1,43 @@
+-- ----------------------------
+-- 菜单配置 SQL - 项目管理模块
+-- ----------------------------
+
+-- 一级菜单:项目管理(放在最前面)
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3000, '项目管理', '', 1, 1, 0, '/demo', 'ep:files', NULL, NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 二级菜单:项目列表
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3001, '项目列表', '', 2, 1, 3000, 'project', 'ep:document', 'demo/project/index', 'DemoProject', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 按钮权限:查询
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3002, '项目查询', 'demo:project:query', 3, 1, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 按钮权限:新增
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3003, '项目新增', 'demo:project:create', 3, 2, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 按钮权限:修改
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3004, '项目修改', 'demo:project:update', 3, 3, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 按钮权限:删除
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3005, '项目删除', 'demo:project:delete', 3, 4, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 按钮权限:导出
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (3006, '项目导出', 'demo:project:export', 3, 5, 3001, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- ----------------------------
+-- 调整系统管理和基础设施菜单的排序(放到最后)
+-- 注意:这里假设系统管理菜单 id=1,基础设施菜单 id=2
+-- 如果 id 不同,请根据实际情况修改
+-- ----------------------------
+
+-- 将系统管理菜单 sort 调整为 98
+UPDATE `system_menu` SET `sort` = 98, `updater` = '1', `update_time` = NOW() WHERE `id` = 1 AND `deleted` = b'0';
+
+-- 将基础设施菜单 sort 调整为 99
+UPDATE `system_menu` SET `sort` = 99, `updater` = '1', `update_time` = NOW() WHERE `id` = 2 AND `deleted` = b'0';
diff --git a/doc/demo_project.sql b/doc/demo_project.sql
new file mode 100644
index 0000000..72478b3
--- /dev/null
+++ b/doc/demo_project.sql
@@ -0,0 +1,25 @@
+-- ----------------------------
+-- Table structure for demo_project
+-- ----------------------------
+DROP TABLE IF EXISTS `demo_project`;
+CREATE TABLE `demo_project` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '项目ID',
+ `project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目名称',
+ `project_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '项目编号',
+ `establish_date` datetime NULL DEFAULT NULL COMMENT '立项时间',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '项目管理表';
+
+-- ----------------------------
+-- Records of demo_project (示例数据)
+-- ----------------------------
+BEGIN;
+INSERT INTO `demo_project` (`id`, `project_name`, `project_code`, `establish_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`)
+VALUES (1, '示例项目一', 'PROJ-2024-001', '2024-01-01 00:00:00', '1', '2024-01-01 10:00:00', '1', '2024-01-01 10:00:00', b'0', 1);
+COMMIT;
diff --git a/doc/demo_部署说明.md b/doc/demo_部署说明.md
new file mode 100644
index 0000000..354b5c2
--- /dev/null
+++ b/doc/demo_部署说明.md
@@ -0,0 +1,259 @@
+# 项目管理模块部署说明
+
+## 一、概述
+
+本文档说明如何部署 lyzsys-module-demo 项目管理模块。
+
+### 模块信息
+- **模块名称**: lyzsys-module-demo
+- **功能**: 项目管理(增删改查导出)
+- **数据库表**: demo_project
+- **菜单结构**:
+ - 一级菜单:项目管理
+ - 二级菜单:项目列表
+
+### 命名规范遵循
+✅ 所有字段名遵循驼峰命名规范,第一个单词不能只有一个字母
+- `projectName`(而非 `pName`)
+- `projectCode`(而非 `pCode`)
+- `establishDate`(而非 `eDate`)
+
+## 二、部署步骤
+
+### 1. 数据库部署
+
+按顺序执行以下 SQL 脚本:
+
+```bash
+# 1. 创建项目管理表
+mysql> source e:/workspace/lyzsys/lyzsys_backend/sql/mysql/demo_project.sql
+
+# 2. 创建菜单配置
+mysql> source e:/workspace/lyzsys/lyzsys_backend/sql/mysql/demo_menu.sql
+```
+
+**说明**:
+- `demo_project.sql` - 创建项目管理表,包含示例数据
+- `demo_menu.sql` - 创建菜单配置,包含一级菜单、二级菜单和按钮权限
+
+### 2. 后端部署
+
+后端代码已自动集成到项目中,无需额外配置。
+
+**后端文件清单**:
+
+```
+lyzsys_backend/lyzsys-module-demo/
+├── pom.xml # Maven 配置
+└── src/main/java/cn/iocoder/lyzsys/module/demo/
+ ├── controller/admin/project/
+ │ ├── ProjectController.java # 控制器
+ │ └── vo/
+ │ ├── ProjectPageReqVO.java # 分页查询请求
+ │ ├── ProjectRespVO.java # 响应 VO
+ │ └── ProjectSaveReqVO.java # 保存请求 VO
+ ├── service/project/
+ │ ├── ProjectService.java # 服务接口
+ │ └── ProjectServiceImpl.java # 服务实现
+ ├── dal/
+ │ ├── dataobject/project/
+ │ │ └── ProjectDO.java # 数据对象
+ │ └── mysql/project/
+ │ └── ProjectMapper.java # Mapper 接口
+ └── enums/
+ └── ErrorCodeConstants.java # 错误码常量
+```
+
+**验证后端部署**:
+1. Maven 重新导入项目依赖
+2. 确认 `lyzsys-server/pom.xml` 包含 `lyzsys-module-demo` 依赖
+3. 启动后端项目,查看日志确认模块加载成功
+
+### 3. 前端部署
+
+前端代码已生成,无需额外配置。
+
+**前端文件清单**:
+
+```
+lyzsys-ui-admin/src/
+├── api/demo/project/
+│ └── index.ts # API 接口定义
+└── views/demo/project/
+ ├── index.vue # 项目列表页面
+ └── ProjectForm.vue # 项目表单组件
+```
+
+**验证前端部署**:
+1. 重新启动前端项目(如果正在运行)
+2. 使用管理员账号登录系统
+3. 在左侧菜单栏查看"项目管理"菜单
+
+## 三、功能验证
+
+### 1. 菜单验证
+
+登录后台管理系统,应该看到以下菜单结构:
+
+```
+项目管理(一级菜单)
+ └── 项目列表(二级菜单)
+系统管理(已调整到最后)
+基础设施(已调整到最后)
+```
+
+### 2. 权限验证
+
+项目列表页面应包含以下操作按钮:
+- ✅ 搜索
+- ✅ 重置
+- ✅ 新增
+- ✅ 导出
+- ✅ 批量删除
+- ✅ 修改(每行)
+- ✅ 删除(每行)
+
+### 3. 功能验证
+
+测试以下功能:
+
+#### 查询功能
+- [ ] 按项目名称模糊查询
+- [ ] 按项目编号模糊查询
+- [ ] 按立项时间范围查询
+- [ ] 分页查询
+
+#### 新增功能
+- [ ] 点击"新增"按钮打开表单
+- [ ] 必填字段验证(项目名称、项目编号)
+- [ ] 项目编号唯一性校验
+- [ ] 保存成功后刷新列表
+
+#### 修改功能
+- [ ] 点击"修改"按钮打开表单
+- [ ] 回显原有数据
+- [ ] 修改后保存成功
+
+#### 删除功能
+- [ ] 单个删除确认提示
+- [ ] 删除成功后刷新列表
+- [ ] 批量删除功能
+
+#### 导出功能
+- [ ] 点击"导出"按钮
+- [ ] 下载 Excel 文件
+- [ ] 验证导出数据正确
+
+## 四、接口文档
+
+### API 端点
+
+**Base URL**: `/demo/project`
+
+| 接口 | 方法 | 说明 | 权限 |
+|-----|------|------|------|
+| `/create` | POST | 创建项目 | demo:project:create |
+| `/update` | PUT | 修改项目 | demo:project:update |
+| `/delete` | DELETE | 删除项目 | demo:project:delete |
+| `/delete-list` | DELETE | 批量删除 | demo:project:delete |
+| `/page` | GET | 分页查询 | demo:project:query |
+| `/get` | GET | 查询详情 | demo:project:query |
+| `/export-excel` | GET | 导出Excel | demo:project:export |
+
+### 请求示例
+
+**创建项目**:
+```json
+POST /demo/project/create
+{
+ "projectName": "示例项目",
+ "projectCode": "PROJ-2024-001",
+ "establishDate": "2024-01-01 00:00:00"
+}
+```
+
+**查询列表**:
+```
+GET /demo/project/page?pageNo=1&pageSize=10&projectName=示例
+```
+
+## 五、常见问题
+
+### 1. 菜单不显示
+
+**原因**:可能是权限问题
+**解决**:
+1. 确认已执行 `demo_menu.sql`
+2. 检查当前登录用户的角色权限
+3. 在"系统管理 > 菜单管理"中确认菜单已创建
+4. 在"系统管理 > 角色管理"中给当前角色分配菜单权限
+
+### 2. 后端接口 404
+
+**原因**:模块未启动或路由配置错误
+**解决**:
+1. 检查 `lyzsys-server/pom.xml` 是否包含 demo 模块依赖
+2. 重新编译启动项目
+3. 查看启动日志确认模块加载
+
+### 3. 前端页面报错
+
+**原因**:API 接口路径不匹配
+**解决**:
+1. 检查 `src/api/demo/project/index.ts` 中的接口路径
+2. 确认后端 Controller 的 `@RequestMapping` 路径
+3. 查看浏览器控制台错误信息
+
+### 4. 项目编号重复错误
+
+**原因**:唯一性校验生效
+**解决**:这是正常的业务校验,请使用不同的项目编号
+
+## 六、扩展开发
+
+### 添加新字段
+
+1. **修改数据库表**:
+```sql
+ALTER TABLE demo_project ADD COLUMN new_field VARCHAR(100) COMMENT '新字段';
+```
+
+2. **修改后端代码**:
+- 在 `ProjectDO.java` 中添加字段
+- 在 `ProjectRespVO.java` 和 `ProjectSaveReqVO.java` 中添加字段
+- 根据需要修改 `ProjectMapper.java` 查询条件
+
+3. **修改前端代码**:
+- 在 `api/demo/project/index.ts` 中添加字段定义
+- 在 `ProjectForm.vue` 中添加表单项
+- 在 `index.vue` 中添加表格列
+
+### 添加新功能
+
+参考现有的增删改查实现,遵循以下开发流程:
+1. 在 Service 层添加业务方法
+2. 在 Controller 层添加接口
+3. 在前端 API 文件中添加接口调用
+4. 在前端页面中实现 UI 交互
+
+## 七、技术栈
+
+### 后端
+- Spring Boot 2.7.18
+- MyBatis Plus 3.5.15
+- MySQL 8.0+
+
+### 前端
+- Vue 3.5.12
+- Element Plus 2.11.1
+- TypeScript 5.3.3
+- Vite 5.1.4
+
+## 八、联系方式
+
+如有问题,请联系开发团队或查阅项目文档。
+
+---
+
+**生成时间**: 2024年
+**版本**: v1.0
diff --git a/doc/新模块开发指南.md b/doc/新模块开发指南.md
new file mode 100644
index 0000000..95e58ba
--- /dev/null
+++ b/doc/新模块开发指南.md
@@ -0,0 +1,931 @@
+# Lyzsys 新模块开发指南
+
+## 目录
+
+- [1. 快速开始](#1-快速开始)
+- [2. 创建后端模块](#2-创建后端模块)
+- [3. 创建前端页面](#3-创建前端页面)
+- [4. 配置菜单权限](#4-配置菜单权限)
+- [5. 完整示例](#5-完整示例)
+- [6. 常见问题](#6-常见问题)
+
+---
+
+## 1. 快速开始
+
+本指南以 **lyzsys-module-demo** 项目管理模块为参考,演示如何快速创建一个新的业务模块。
+
+### 1.1 开发流程
+
+```
+1. 设计数据库表
+ ↓
+2. 创建后端模块
+ ├── 创建 Maven 模块
+ ├── 编写实体类(DO)
+ ├── 编写 Mapper 接口
+ ├── 编写 VO 类
+ ├── 编写 Service 层
+ └── 编写 Controller 层
+ ↓
+3. 创建前端页面
+ ├── 编写 API 接口
+ ├── 编写列表页面
+ └── 编写表单组件
+ ↓
+4. 配置菜单权限
+ ├── 创建一级菜单
+ ├── 创建二级菜单
+ └── 创建按钮权限
+ ↓
+5. 测试验证
+```
+
+### 1.2 前置准备
+
+- [ ] 确认数据库表设计
+- [ ] 确认菜单结构(一级、二级菜单名称)
+- [ ] 确认业务字段和验证规则
+- [ ] 准备好模块名称和权限标识
+
+---
+
+## 2. 创建后端模块
+
+### 2.1 创建 Maven 模块
+
+#### 步骤 1:创建模块目录
+
+在 `lyzsys_backend` 下创建新模块:
+
+```
+lyzsys_backend/
+└── lyzsys-module-{模块名}/
+ ├── pom.xml
+ └── src/main/java/cn/iocoder/lyzsys/module/{模块名}/
+```
+
+#### 步骤 2:编写 pom.xml
+
+参考 `lyzsys-module-demo/pom.xml`:
+
+```xml
+
+
+
+ cn.iocoder.boot
+ lyzsys
+ ${revision}
+
+ 4.0.0
+ lyzsys-module-{模块名}
+ jar
+
+ ${project.artifactId}
+
+ {模块说明}
+
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-module-infra
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-mybatis
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-redis
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-test
+ test
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-excel
+
+
+
+
+```
+
+#### 步骤 3:添加模块到父 pom.xml
+
+在 `lyzsys_backend/pom.xml` 中添加:
+
+```xml
+
+
+ lyzsys-module-system
+ lyzsys-module-infra
+
+ lyzsys-module-{模块名}
+
+```
+
+#### 步骤 4:添加模块到 server
+
+在 `lyzsys-server/pom.xml` 中添加依赖:
+
+```xml
+
+ cn.iocoder.boot
+ lyzsys-module-{模块名}
+ ${revision}
+
+```
+
+### 2.2 创建数据库表
+
+参考 `sql/mysql/demo_project.sql`:
+
+```sql
+DROP TABLE IF EXISTS `{表名}`;
+CREATE TABLE `{表名}` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ -- 业务字段(注意:遵循驼峰命名规范,第一个单词不能只有一个字母)
+ `business_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务名称',
+ `business_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '业务编号',
+ -- 标准字段(所有表必备)
+ `creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '业务表';
+```
+
+### 2.3 创建实体类(DO)
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/dal/dataobject/{业务}/{业务}DO.java`
+
+参考 `ProjectDO.java`:
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务};
+
+import cn.iocoder.lyzsys.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * {业务名称} DO
+ *
+ * @author {作者}
+ */
+@TableName("{表名}")
+@KeySequence("{表名}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class {业务}DO extends BaseDO {
+
+ /**
+ * 主键ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 业务字段(注意驼峰命名)
+ */
+ private String businessName;
+ private String businessCode;
+ private LocalDateTime businessDate;
+
+}
+```
+
+### 2.4 创建 Mapper 接口
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/dal/mysql/{业务}/{业务}Mapper.java`
+
+参考 `ProjectMapper.java`:
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.dal.mysql.{业务};
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * {业务名称} Mapper
+ *
+ * @author {作者}
+ */
+@Mapper
+public interface {业务}Mapper extends BaseMapperX<{业务}DO> {
+
+ default PageResult<{业务}DO> selectPage({业务}PageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<{业务}DO>()
+ .likeIfPresent({业务}DO::getBusinessName, reqVO.getBusinessName())
+ .likeIfPresent({业务}DO::getBusinessCode, reqVO.getBusinessCode())
+ .betweenIfPresent({业务}DO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc({业务}DO::getId));
+ }
+
+ default {业务}DO selectByBusinessCode(String businessCode) {
+ return selectOne({业务}DO::getBusinessCode, businessCode);
+ }
+
+}
+```
+
+### 2.5 创建 VO 类
+
+#### 分页查询请求 VO
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/vo/{业务}PageReqVO.java`
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo;
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - {业务名称}分页列表 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class {业务}PageReqVO extends PageParam {
+
+ @Schema(description = "业务名称,模糊匹配", example = "示例")
+ private String businessName;
+
+ @Schema(description = "业务编号,模糊匹配", example = "CODE-001")
+ private String businessCode;
+
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Schema(description = "创建时间")
+ private LocalDateTime[] createTime;
+
+}
+```
+
+#### 响应 VO
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/vo/{业务}RespVO.java`
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - {业务名称}信息 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class {业务}RespVO {
+
+ @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty("ID")
+ private Long id;
+
+ @Schema(description = "业务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例")
+ @ExcelProperty("业务名称")
+ private String businessName;
+
+ @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CODE-001")
+ @ExcelProperty("业务编号")
+ private String businessCode;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
+```
+
+#### 保存请求 VO
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/vo/{业务}SaveReqVO.java`
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - {业务名称}创建/修改 Request VO")
+@Data
+public class {业务}SaveReqVO {
+
+ @Schema(description = "主键ID", example = "1")
+ private Long id;
+
+ @Schema(description = "业务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例")
+ @NotBlank(message = "业务名称不能为空")
+ @Size(max = 100, message = "业务名称长度不能超过100个字符")
+ private String businessName;
+
+ @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CODE-001")
+ @NotBlank(message = "业务编号不能为空")
+ @Size(max = 50, message = "业务编号长度不能超过50个字符")
+ private String businessCode;
+
+}
+```
+
+### 2.6 创建 Service 层
+
+#### Service 接口
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/service/{业务}/{业务}Service.java`
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.service.{业务};
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}SaveReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
+
+import java.util.List;
+
+/**
+ * {业务名称} Service 接口
+ *
+ * @author {作者}
+ */
+public interface {业务}Service {
+
+ /**
+ * 创建{业务}
+ */
+ Long create{业务}({业务}SaveReqVO createReqVO);
+
+ /**
+ * 更新{业务}
+ */
+ void update{业务}({业务}SaveReqVO updateReqVO);
+
+ /**
+ * 删除{业务}
+ */
+ void delete{业务}(Long id);
+
+ /**
+ * 批量删除{业务}
+ */
+ void delete{业务}List(List ids);
+
+ /**
+ * 获得{业务}分页列表
+ */
+ PageResult<{业务}DO> get{业务}Page({业务}PageReqVO pageReqVO);
+
+ /**
+ * 获得{业务}详情
+ */
+ {业务}DO get{业务}(Long id);
+
+}
+```
+
+#### Service 实现类
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/service/{业务}/{业务}ServiceImpl.java`
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.service.{业务};
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}SaveReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
+import cn.iocoder.lyzsys.module.{模块名}.dal.mysql.{业务}.{业务}Mapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.lyzsys.module.{模块名}.enums.ErrorCodeConstants.*;
+
+/**
+ * {业务名称} Service 实现类
+ *
+ * @author {作者}
+ */
+@Service
+@Validated
+public class {业务}ServiceImpl implements {业务}Service {
+
+ @Resource
+ private {业务}Mapper {业务}Mapper;
+
+ @Override
+ public Long create{业务}({业务}SaveReqVO createReqVO) {
+ // 校验业务编号的唯一性
+ validateBusinessCodeUnique(null, createReqVO.getBusinessCode());
+
+ // 插入
+ {业务}DO {业务} = BeanUtils.toBean(createReqVO, {业务}DO.class);
+ {业务}Mapper.insert({业务});
+ return {业务}.getId();
+ }
+
+ @Override
+ public void update{业务}({业务}SaveReqVO updateReqVO) {
+ // 校验存在
+ validate{业务}Exists(updateReqVO.getId());
+ // 校验业务编号的唯一性
+ validateBusinessCodeUnique(updateReqVO.getId(), updateReqVO.getBusinessCode());
+
+ // 更新
+ {业务}DO updateObj = BeanUtils.toBean(updateReqVO, {业务}DO.class);
+ {业务}Mapper.updateById(updateObj);
+ }
+
+ @Override
+ public void delete{业务}(Long id) {
+ // 校验存在
+ validate{业务}Exists(id);
+ // 删除
+ {业务}Mapper.deleteById(id);
+ }
+
+ @Override
+ public void delete{业务}List(List ids) {
+ // 校验存在
+ ids.forEach(this::validate{业务}Exists);
+ // 批量删除
+ {业务}Mapper.deleteBatchIds(ids);
+ }
+
+ @Override
+ public PageResult<{业务}DO> get{业务}Page({业务}PageReqVO pageReqVO) {
+ return {业务}Mapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public {业务}DO get{业务}(Long id) {
+ return {业务}Mapper.selectById(id);
+ }
+
+ // ==================== 校验方法 ====================
+
+ private void validate{业务}Exists(Long id) {
+ if ({业务}Mapper.selectById(id) == null) {
+ throw exception({业务大写}_NOT_EXISTS);
+ }
+ }
+
+ private void validateBusinessCodeUnique(Long id, String businessCode) {
+ {业务}DO {业务} = {业务}Mapper.selectByBusinessCode(businessCode);
+ if ({业务} == null) {
+ return;
+ }
+ if (id == null) {
+ throw exception({业务大写}_CODE_DUPLICATE);
+ }
+ if (!{业务}.getId().equals(id)) {
+ throw exception({业务大写}_CODE_DUPLICATE);
+ }
+ }
+
+}
+```
+
+### 2.7 创建 Controller 层
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/controller/admin/{业务}/{业务}Controller.java`
+
+参考 `ProjectController.java`:
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务};
+
+import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
+import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
+import cn.iocoder.lyzsys.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}PageReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}RespVO;
+import cn.iocoder.lyzsys.module.{模块名}.controller.admin.{业务}.vo.{业务}SaveReqVO;
+import cn.iocoder.lyzsys.module.{模块名}.dal.dataobject.{业务}.{业务}DO;
+import cn.iocoder.lyzsys.module.{模块名}.service.{业务}.{业务}Service;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - {业务名称}管理")
+@RestController
+@RequestMapping("/{模块名}/{业务}")
+@Validated
+public class {业务}Controller {
+
+ @Resource
+ private {业务}Service {业务}Service;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建{业务}")
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:create')")
+ public CommonResult create{业务}(@Valid @RequestBody {业务}SaveReqVO createReqVO) {
+ Long id = {业务}Service.create{业务}(createReqVO);
+ return success(id);
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改{业务}")
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:update')")
+ public CommonResult update{业务}(@Valid @RequestBody {业务}SaveReqVO updateReqVO) {
+ {业务}Service.update{业务}(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除{业务}")
+ @Parameter(name = "id", description = "编号", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:delete')")
+ public CommonResult delete{业务}(@RequestParam("id") Long id) {
+ {业务}Service.delete{业务}(id);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "批量删除{业务}")
+ @Parameter(name = "ids", description = "编号列表", required = true)
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:delete')")
+ public CommonResult delete{业务}List(@RequestParam("ids") List ids) {
+ {业务}Service.delete{业务}List(ids);
+ return success(true);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得{业务}分页列表")
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:query')")
+ public CommonResult> get{业务}Page(@Valid {业务}PageReqVO pageReqVO) {
+ PageResult<{业务}DO> pageResult = {业务}Service.get{业务}Page(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, {业务}RespVO.class));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得{业务}详情")
+ @Parameter(name = "id", description = "编号", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:query')")
+ public CommonResult<{业务}RespVO> get{业务}(@RequestParam("id") Long id) {
+ {业务}DO {业务} = {业务}Service.get{业务}(id);
+ return success(BeanUtils.toBean({业务}, {业务}RespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出{业务}")
+ @PreAuthorize("@ss.hasPermission('{模块名}:{业务}:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void export{业务}(HttpServletResponse response, @Valid {业务}PageReqVO exportReqVO) throws IOException {
+ exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List<{业务}DO> list = {业务}Service.get{业务}Page(exportReqVO).getList();
+ ExcelUtils.write(response, "{业务名称}.xls", "数据", {业务}RespVO.class,
+ BeanUtils.toBean(list, {业务}RespVO.class));
+ }
+
+}
+```
+
+### 2.8 创建错误码常量
+
+文件位置:`src/main/java/cn/iocoder/lyzsys/module/{模块名}/enums/ErrorCodeConstants.java`
+
+```java
+package cn.iocoder.lyzsys.module.{模块名}.enums;
+
+import cn.iocoder.lyzsys.framework.common.exception.ErrorCode;
+
+/**
+ * {模块名称} 错误码枚举类
+ *
+ * {模块名} 模块,使用 1-0XX-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== {业务名称}模块 1-0XX-001-000 ==========
+ ErrorCode {业务大写}_NOT_EXISTS = new ErrorCode(1_0XX_001_000, "{业务}不存在");
+ ErrorCode {业务大写}_CODE_DUPLICATE = new ErrorCode(1_0XX_001_001, "已经存在该{业务}编号");
+
+}
+```
+
+---
+
+## 3. 创建前端页面
+
+### 3.1 创建 API 接口
+
+文件位置:`src/api/{模块名}/{业务}/index.ts`
+
+参考 `src/api/demo/project/index.ts`:
+
+```typescript
+import request from '@/config/axios'
+
+export interface {业务}VO {
+ id: number
+ businessName: string
+ businessCode: string
+ createTime: Date
+}
+
+export interface {业务}PageReqVO extends PageParam {
+ businessName?: string
+ businessCode?: string
+ createTime?: Date[]
+}
+
+// 查询分页
+export const get{业务}Page = (params: {业务}PageReqVO) => {
+ return request.get({ url: '/{模块名}/{业务}/page', params })
+}
+
+// 查询详情
+export const get{业务} = (id: number) => {
+ return request.get({ url: '/{模块名}/{业务}/get', params: { id } })
+}
+
+// 新增
+export const create{业务} = (data: {业务}VO) => {
+ return request.post({ url: '/{模块名}/{业务}/create', data })
+}
+
+// 修改
+export const update{业务} = (data: {业务}VO) => {
+ return request.put({ url: '/{模块名}/{业务}/update', data })
+}
+
+// 删除
+export const delete{业务} = (id: number) => {
+ return request.delete({ url: '/{模块名}/{业务}/delete', params: { id } })
+}
+
+// 批量删除
+export const delete{业务}List = (ids: number[]) => {
+ return request.delete({ url: '/{模块名}/{业务}/delete-list', params: { ids } })
+}
+
+// 导出
+export const export{业务} = (params) => {
+ return request.download({ url: '/{模块名}/{业务}/export-excel', params })
+}
+```
+
+### 3.2 创建列表页面
+
+文件位置:`src/views/{模块名}/{业务}/index.vue`
+
+参考 `src/views/demo/project/index.vue`(详细代码见 demo 模块)
+
+**关键点**:
+- 使用 `defineOptions({ name: '{模块}{业务}' })`
+- 搜索表单 + 列表表格 + 分页组件
+- 权限控制:`v-hasPermi="['{模块名}:{业务}:操作']"`
+
+### 3.3 创建表单组件
+
+文件位置:`src/views/{模块名}/{业务}/{业务}Form.vue`
+
+参考 `src/views/demo/project/ProjectForm.vue`(详细代码见 demo 模块)
+
+**关键点**:
+- 使用 `defineOptions({ name: '{模块}{业务}Form' })`
+- 使用 `defineExpose({ open })` 暴露打开方法
+- 表单验证规则
+- 提交成功后 `emit('success')`
+
+---
+
+## 4. 配置菜单权限
+
+### 4.1 创建菜单 SQL
+
+文件位置:`sql/mysql/{模块名}_menu.sql`
+
+参考 `sql/mysql/demo_menu.sql`:
+
+```sql
+-- 一级菜单
+INSERT INTO `system_menu` (...) VALUES (
+ {ID}, '{一级菜单名}', '', 1, {排序}, 0,
+ '/{模块名}', '{图标}', NULL, NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+);
+
+-- 二级菜单
+INSERT INTO `system_menu` (...) VALUES (
+ {ID+1}, '{二级菜单名}', '', 2, 1, {一级菜单ID},
+ '{业务}', '{图标}', '{模块名}/{业务}/index', '{模块}{业务}',
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+);
+
+-- 按钮权限:查询、新增、修改、删除、导出
+INSERT INTO `system_menu` (...) VALUES
+ ({ID+2}, '{业务}查询', '{模块名}:{业务}:query', 3, 1, {二级菜单ID}, ...), ({ID+3}, '{业务}新增', '{模块名}:{业务}:create', 3, 2, {二级菜单ID}, ...),
+ ({ID+4}, '{业务}修改', '{模块名}:{业务}:update', 3, 3, {二级菜单ID}, ...),
+ ({ID+5}, '{业务}删除', '{模块名}:{业务}:delete', 3, 4, {二级菜单ID}, ...),
+ ({ID+6}, '{业务}导出', '{模块名}:{业务}:export', 3, 5, {二级菜单ID}, ...);
+
+-- 调整系统管理和基础设施菜单排序
+UPDATE `system_menu` SET `sort` = 98, `updater` = '1', `update_time` = NOW() WHERE `id` = 1 AND `deleted` = b'0';
+UPDATE `system_menu` SET `sort` = 99, `updater` = '1', `update_time` = NOW() WHERE `id` = 2 AND `deleted` = b'0';
+```
+
+---
+
+## 5. 完整示例
+
+### 5.1 Demo 模块文件清单
+
+以下是 `lyzsys-module-demo` 的完整文件结构,可作为新模块开发的参考:
+
+```
+lyzsys-module-demo/
+├── pom.xml
+└── src/main/java/cn/iocoder/lyzsys/module/demo/
+ ├── controller/admin/project/
+ │ ├── ProjectController.java
+ │ └── vo/
+ │ ├── ProjectPageReqVO.java
+ │ ├── ProjectRespVO.java
+ │ └── ProjectSaveReqVO.java
+ ├── service/project/
+ │ ├── ProjectService.java
+ │ └── ProjectServiceImpl.java
+ ├── dal/
+ │ ├── dataobject/project/
+ │ │ └── ProjectDO.java
+ │ └── mysql/project/
+ │ └── ProjectMapper.java
+ └── enums/
+ └── ErrorCodeConstants.java
+```
+
+### 5.2 前端文件清单
+
+```
+src/
+├── api/demo/project/
+│ └── index.ts
+└── views/demo/project/
+ ├── index.vue
+ └── ProjectForm.vue
+```
+
+### 5.3 SQL 文件清单
+
+```
+sql/mysql/
+├── demo_project.sql # 数据库表
+└── demo_menu.sql # 菜单配置
+```
+
+---
+
+## 6. 常见问题
+
+### 6.1 Maven 编译错误
+
+**问题**:找不到父 POM
+**解决**:检查 parent 的 groupId、artifactId、version 是否正确
+
+### 6.2 菜单不显示
+
+**问题**:前端菜单不显示
+**解决**:
+1. 确认已执行菜单 SQL
+2. 检查当前用户角色权限
+3. 清除浏览器缓存
+
+### 6.3 接口 404
+
+**问题**:前端调用接口返回 404
+**解决**:
+1. 检查 Controller 的 @RequestMapping 路径
+2. 确认模块已添加到 lyzsys-server/pom.xml
+3. 重启后端服务
+
+### 6.4 MyBatis Plus 字段映射错误
+
+**问题**:字段映射错误,查询结果为 null
+**解决**:检查字段命名是否遵循驼峰规范,确保第一个单词不是单字母
+
+### 6.5 权限验证失败
+
+**问题**:操作提示无权限
+**解决**:
+1. 检查 @PreAuthorize 权限标识是否正确
+2. 确认菜单 SQL 中的权限标识与代码一致
+3. 给当前角色分配权限
+
+---
+
+## 7. 检查清单
+
+开发完成后,请检查:
+
+**后端**
+- [ ] Maven 模块配置正确
+- [ ] 数据库表创建成功
+- [ ] 实体类字段遵循命名规范
+- [ ] Mapper 接口方法正确
+- [ ] VO 类字段完整
+- [ ] Service 业务逻辑正确
+- [ ] Controller 权限配置正确
+- [ ] 错误码定义完整
+
+**前端**
+- [ ] API 接口定义正确
+- [ ] 列表页面功能完整
+- [ ] 表单组件验证规则正确
+- [ ] 权限控制配置正确
+
+**菜单**
+- [ ] 一级菜单创建成功
+- [ ] 二级菜单创建成功
+- [ ] 按钮权限创建成功
+- [ ] 菜单排序正确
+
+**测试**
+- [ ] 列表查询正常
+- [ ] 新增功能正常
+- [ ] 修改功能正常
+- [ ] 删除功能正常
+- [ ] 导出功能正常
+- [ ] 权限控制正常
+
+---
+
+**文档版本**:v1.0
+**参考模块**:lyzsys-module-demo
+**最后更新**:2024年
+**维护者**:开发团队
diff --git a/doc/编码规范.md b/doc/编码规范.md
new file mode 100644
index 0000000..4aa088b
--- /dev/null
+++ b/doc/编码规范.md
@@ -0,0 +1,647 @@
+# Lyzsys 项目编码规范
+
+## 目录
+
+- [1. 命名规范](#1-命名规范)
+- [2. 菜单结构规范](#2-菜单结构规范)
+- [3. 代码结构规范](#3-代码结构规范)
+- [4. 数据库设计规范](#4-数据库设计规范)
+- [5. API 接口规范](#5-api-接口规范)
+- [6. 前端开发规范](#6-前端开发规范)
+
+---
+
+## 1. 命名规范
+
+### 1.1 驼峰命名规范
+
+**核心原则**:所有字段名、类名、文件名、变量名的驼峰命名中,**第一个单词不能只有一个字母**。
+
+#### 为什么要遵循这个规范?
+
+1. **MyBatis Plus 兼容性**:MyBatis Plus 在映射单字母开头的驼峰字段时可能出现问题
+2. **代码可读性**:完整单词更具可读性,便于理解业务含义
+3. **避免错误**:减少字段读取错误和调试困难
+
+#### 正确示例 ✅
+
+```java
+// 字段命名
+private String projectId; // 而不是 pId
+private String projectName; // 而不是 pName
+private String projectCode; // 而不是 pCode
+private LocalDateTime establishDate; // 而不是 eDate
+
+// 特殊情况:如果首字母缩写必须使用,请用完整单词
+private String majorType; // 而不是 mType
+private BigDecimal leaderRatio; // 而不是 lRatio
+private Double kOneCoefficient; // 而不是 k1Coefficient(k1 是业务术语)
+```
+
+#### 错误示例 ❌
+
+```java
+// 错误:单字母开头
+private String pId; // ❌ 应该是 projectId
+private String mType; // ❌ 应该是 majorType
+private String lRatio; // ❌ 应该是 leaderRatio
+private String k1Coefficient; // ❌ 应该是 kOneCoefficient
+```
+
+### 1.2 Java 类命名规范
+
+#### 实体类(DO)
+
+```java
+// 格式:{业务名称}DO
+ProjectDO.java // 项目实体
+UserDO.java // 用户实体
+OrderDO.java // 订单实体
+```
+
+#### VO 类
+
+```java
+// 分页查询请求
+{业务名称}PageReqVO.java
+ProjectPageReqVO.java
+
+// 响应 VO
+{业务名称}RespVO.java
+ProjectRespVO.java
+
+// 保存请求 VO(新增和修改共用)
+{业务名称}SaveReqVO.java
+ProjectSaveReqVO.java
+
+// 简单响应 VO(下拉选项等)
+{业务名称}SimpleRespVO.java
+ProjectSimpleRespVO.java
+```
+
+#### Mapper 接口
+
+```java
+// 格式:{业务名称}Mapper
+ProjectMapper.java
+```
+
+#### Service 层
+
+```java
+// 接口:{业务名称}Service
+ProjectService.java
+
+// 实现:{业务名称}ServiceImpl
+ProjectServiceImpl.java
+```
+
+#### Controller 层
+
+```java
+// 格式:{业务名称}Controller
+ProjectController.java
+```
+
+### 1.3 数据库命名规范
+
+#### 表名
+
+```sql
+-- 格式:{模块前缀}_{业务名称}(全小写,下划线分隔)
+demo_project -- demo 模块的项目表
+system_user -- system 模块的用户表
+infra_config -- infra 模块的配置表
+```
+
+#### 字段名
+
+```sql
+-- 使用下划线分隔的小写单词
+project_name -- 项目名称(对应 Java 中的 projectName)
+project_code -- 项目编号
+establish_date -- 立项时间
+
+-- 标准字段(所有表必备)
+id -- 主键
+creator -- 创建者
+create_time -- 创建时间
+updater -- 更新者
+update_time -- 更新时间
+deleted -- 删除标记
+tenant_id -- 租户ID(多租户表必备)
+```
+
+### 1.4 包名规范
+
+```
+cn.iocoder.lyzsys.module.{模块名}.{分层}
+
+示例:
+cn.iocoder.lyzsys.module.demo.controller.admin.project
+cn.iocoder.lyzsys.module.demo.service.project
+cn.iocoder.lyzsys.module.demo.dal.dataobject.project
+cn.iocoder.lyzsys.module.demo.dal.mysql.project
+```
+
+### 1.5 前端命名规范
+
+#### Vue 组件名
+
+```javascript
+// 页面组件(defineOptions name)
+DemoProject // 项目列表页
+DemoProjectForm // 项目表单组件
+
+// 文件名(kebab-case)
+index.vue // 列表页
+ProjectForm.vue // 表单组件
+```
+
+#### API 文件
+
+```typescript
+// 文件路径
+src/api/demo/project/index.ts
+
+// 接口命名(驼峰)
+export const getProjectPage = () => {}
+export const createProject = () => {}
+export const updateProject = () => {}
+export const deleteProject = () => {}
+```
+
+---
+
+## 2. 菜单结构规范
+
+### 2.1 菜单层级规范
+
+**原则**:业务功能菜单优先展示,系统管理功能放在最后。
+
+#### 推荐的菜单顺序
+
+```
+1. 业务菜单(sort: 1-90)
+ ├── 项目管理(一级菜单,sort: 1)
+ │ └── 项目列表(二级菜单,sort: 1)
+ ├── 客户管理(一级菜单,sort: 2)
+ │ └── 客户列表(二级菜单,sort: 1)
+ └── ...
+
+98. 系统管理(sort: 98)
+ ├── 用户管理
+ ├── 角色管理
+ ├── 菜单管理
+ └── ...
+
+99. 基础设施(sort: 99)
+ ├── 代码生成
+ ├── 文件管理
+ └── ...
+```
+
+### 2.2 菜单配置规范
+
+#### 一级菜单(目录)
+
+```sql
+INSERT INTO `system_menu` (
+ `id`, `name`, `permission`, `type`, `sort`, `parent_id`,
+ `path`, `icon`, `component`, `component_name`,
+ `status`, `visible`, `keep_alive`, `always_show`,
+ `creator`, `create_time`, `updater`, `update_time`, `deleted`
+) VALUES (
+ 3000, -- ID(建议按模块分配段,如 demo: 3000-3999)
+ '项目管理', -- 名称
+ '', -- 权限标识(一级菜单为空)
+ 1, -- 类型:1=目录
+ 1, -- 排序(业务菜单建议 1-90)
+ 0, -- 父级ID(0表示顶级)
+ '/demo', -- 路由路径
+ 'ep:files', -- 图标
+ NULL, -- 组件路径(目录为NULL)
+ NULL, -- 组件名称(目录为NULL)
+ 0, -- 状态:0=正常
+ b'1', -- 是否可见
+ b'1', -- 是否缓存
+ b'1', -- 是否总是显示
+ '1', -- 创建者
+ NOW(), -- 创建时间
+ '1', -- 更新者
+ NOW(), -- 更新时间
+ b'0' -- 是否删除
+);
+```
+
+#### 二级菜单(菜单页面)
+
+```sql
+INSERT INTO `system_menu` (...) VALUES (
+ 3001, -- ID
+ '项目列表', -- 名称
+ '', -- 权限标识(菜单页面为空)
+ 2, -- 类型:2=菜单
+ 1, -- 排序
+ 3000, -- 父级ID(对应一级菜单ID)
+ 'project', -- 路由路径
+ 'ep:document', -- 图标
+ 'demo/project/index', -- 组件路径
+ 'DemoProject', -- 组件名称(对应 Vue defineOptions name)
+ 0, b'1', b'1', b'1',
+ '1', NOW(), '1', NOW(), b'0'
+);
+```
+
+#### 三级菜单(按钮权限)
+
+```sql
+-- 查询权限
+INSERT INTO `system_menu` (...) VALUES (
+ 3002, '项目查询', 'demo:project:query', 3, 1, 3001,
+ '', '', '', NULL, 0, b'1', b'1', b'1',
+ '1', NOW(), '1', NOW(), b'0'
+);
+
+-- 新增权限
+INSERT INTO `system_menu` (...) VALUES (
+ 3003, '项目新增', 'demo:project:create', 3, 2, 3001,
+ '', '', '', NULL, 0, b'1', b'1', b'1',
+ '1', NOW(), '1', NOW(), b'0'
+);
+
+-- 修改权限
+INSERT INTO `system_menu` (...) VALUES (
+ 3004, '项目修改', 'demo:project:update', 3, 3, 3001,
+ '', '', '', NULL, 0, b'1', b'1', b'1',
+ '1', NOW(), '1', NOW(), b'0'
+);
+
+-- 删除权限
+INSERT INTO `system_menu` (...) VALUES (
+ 3005, '项目删除', 'demo:project:delete', 3, 4, 3001,
+ '', '', '', NULL, 0, b'1', b'1', b'1',
+ '1', NOW(), '1', NOW(), b'0'
+);
+
+-- 导出权限
+INSERT INTO `system_menu` (...) VALUES (
+ 3006, '项目导出', 'demo:project:export', 3, 5, 3001,
+ '', '', '', NULL, 0, b'1', b'1', b'1',
+ '1', NOW(), '1', NOW(), b'0'
+);
+```
+
+### 2.3 权限标识规范
+
+```
+格式:{模块名}:{业务}:{操作}
+
+示例:
+demo:project:query -- demo模块,项目业务,查询操作
+demo:project:create -- demo模块,项目业务,新增操作
+demo:project:update -- demo模块,项目业务,修改操作
+demo:project:delete -- demo模块,项目业务,删除操作
+demo:project:export -- demo模块,项目业务,导出操作
+```
+
+### 2.4 菜单 ID 分配规范
+
+为避免 ID 冲突,建议按模块分配 ID 段:
+
+| 模块 | ID 范围 | 说明 |
+|-----|---------|------|
+| system | 1-999 | 系统核心模块 |
+| infra | 1000-1999 | 基础设施模块 |
+| member | 2000-2999 | 会员模块 |
+| demo | 3000-3999 | Demo 示例模块 |
+| bpm | 4000-4999 | 工作流模块 |
+| mall | 5000-5999 | 商城模块 |
+| ... | ... | 其他业务模块 |
+
+---
+
+## 3. 代码结构规范
+
+### 3.1 模块目录结构
+
+```
+lyzsys-module-{模块名}/
+├── pom.xml # Maven 配置
+└── src/main/java/cn/iocoder/lyzsys/module/{模块名}/
+ ├── controller/ # 控制器层
+ │ └── admin/ # 管理后台接口
+ │ └── {业务}/ # 业务模块
+ │ ├── {业务}Controller.java # 控制器
+ │ └── vo/ # VO 对象
+ │ ├── {业务}PageReqVO.java # 分页请求
+ │ ├── {业务}RespVO.java # 响应 VO
+ │ └── {业务}SaveReqVO.java # 保存请求
+ ├── service/ # 服务层
+ │ └── {业务}/
+ │ ├── {业务}Service.java # 服务接口
+ │ └── {业务}ServiceImpl.java # 服务实现
+ ├── dal/ # 数据访问层
+ │ ├── dataobject/ # 数据对象
+ │ │ └── {业务}/
+ │ │ └── {业务}DO.java # 实体类
+ │ └── mysql/ # Mapper 接口
+ │ └── {业务}/
+ │ └── {业务}Mapper.java # Mapper
+ ├── convert/ # 对象转换(可选)
+ │ └── {业务}Convert.java
+ ├── enums/ # 枚举类
+ │ └── ErrorCodeConstants.java # 错误码
+ └── api/ # 对外 API(可选)
+ └── {业务}Api.java
+```
+
+### 3.2 分层职责
+
+#### Controller 层
+
+- 接收 HTTP 请求
+- 参数校验(使用 @Valid)
+- 权限控制(使用 @PreAuthorize)
+- 调用 Service 层
+- 返回统一响应
+
+#### Service 层
+
+- 业务逻辑处理
+- 数据校验
+- 事务控制
+- 调用 Mapper 层
+
+#### DAL 层
+
+- 数据访问
+- SQL 操作
+- 不包含业务逻辑
+
+---
+
+## 4. 数据库设计规范
+
+### 4.1 表设计规范
+
+#### 标准字段(所有表必备)
+
+```sql
+CREATE TABLE `{表名}` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ -- 业务字段
+ ...
+ -- 标准字段
+ `creator` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='表注释';
+```
+
+#### 多租户表额外字段
+
+```sql
+`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
+```
+
+### 4.2 字段类型规范
+
+| Java 类型 | MySQL 类型 | 说明 |
+|-----------|-----------|------|
+| Long | bigint | ID、数量等 |
+| String | varchar | 字符串 |
+| Integer | int | 状态、类型等 |
+| LocalDateTime | datetime | 日期时间 |
+| LocalDate | date | 日期 |
+| BigDecimal | decimal | 金额、精确数值 |
+| Boolean | bit(1) | 布尔值 |
+
+### 4.3 索引规范
+
+```sql
+-- 主键索引
+PRIMARY KEY (`id`)
+
+-- 唯一索引(业务唯一字段)
+UNIQUE KEY `uk_project_code` (`project_code`)
+
+-- 普通索引(常用查询字段)
+KEY `idx_project_name` (`project_name`)
+
+-- 组合索引(多字段联合查询)
+KEY `idx_tenant_status` (`tenant_id`, `status`)
+```
+
+---
+
+## 5. API 接口规范
+
+### 5.1 RESTful 风格
+
+| 操作 | HTTP 方法 | URL | 说明 |
+|-----|----------|-----|------|
+| 查询列表 | GET | /{模块}/{业务}/page | 分页查询 |
+| 查询详情 | GET | /{模块}/{业务}/get | 根据ID查询 |
+| 新增 | POST | /{模块}/{业务}/create | 创建 |
+| 修改 | PUT | /{模块}/{业务}/update | 更新 |
+| 删除 | DELETE | /{模块}/{业务}/delete | 删除 |
+| 批量删除 | DELETE | /{模块}/{业务}/delete-list | 批量删除 |
+| 导出 | GET | /{模块}/{业务}/export-excel | 导出Excel |
+
+### 5.2 统一响应格式
+
+```java
+// 成功响应
+{
+ "code": 0,
+ "data": {...},
+ "msg": ""
+}
+
+// 失败响应
+{
+ "code": 1001,
+ "data": null,
+ "msg": "错误信息"
+}
+
+// 分页响应
+{
+ "code": 0,
+ "data": {
+ "list": [...],
+ "total": 100
+ },
+ "msg": ""
+}
+```
+
+### 5.3 接口注解规范
+
+```java
+@Tag(name = "管理后台 - 项目管理")
+@RestController
+@RequestMapping("/demo/project")
+@Validated
+public class ProjectController {
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目")
+ @PreAuthorize("@ss.hasPermission('demo:project:create')")
+ public CommonResult createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
+ // ...
+ }
+}
+```
+
+---
+
+## 6. 前端开发规范
+
+### 6.1 目录结构
+
+```
+src/
+├── api/ # API 接口
+│ └── {模块}/
+│ └── {业务}/
+│ └── index.ts # API 定义
+└── views/ # 页面
+ └── {模块}/
+ └── {业务}/
+ ├── index.vue # 列表页
+ └── {业务}Form.vue # 表单组件
+```
+
+### 6.2 组件规范
+
+#### 列表页面
+
+```vue
+
+
+
+ ...
+
+
+
+
+ ...
+
+
+
+
+ <{业务}Form ref="formRef" @success="getList" />
+
+
+
+```
+
+#### 表单组件
+
+```vue
+
+
+
+
+
+```
+
+### 6.3 权限控制
+
+```vue
+
+
+ 新增
+
+
+
+```
+
+---
+
+## 7. 最佳实践
+
+### 7.1 代码注释
+
+```java
+/**
+ * 项目管理 Service 接口
+ *
+ * @author lyzsys
+ */
+public interface ProjectService {
+
+ /**
+ * 创建项目
+ *
+ * @param createReqVO 项目信息
+ * @return 项目编号
+ */
+ Long createProject(ProjectSaveReqVO createReqVO);
+}
+```
+
+### 7.2 异常处理
+
+```java
+// 使用统一的业务异常
+throw exception(PROJECT_NOT_EXISTS);
+throw exception(PROJECT_CODE_DUPLICATE);
+```
+
+### 7.3 日志规范
+
+```java
+@Slf4j
+public class ProjectServiceImpl implements ProjectService {
+
+ @Override
+ public Long createProject(ProjectSaveReqVO createReqVO) {
+ log.info("[createProject] 创建项目,项目编号:{}", createReqVO.getProjectCode());
+ // ...
+ }
+}
+```
+
+---
+
+## 8. 检查清单
+
+在提交代码前,请检查:
+
+- [ ] 所有字段命名遵循驼峰规范,无单字母开头
+- [ ] 菜单配置完整(一级、二级、按钮权限)
+- [ ] 业务菜单排序在系统管理之前
+- [ ] 权限标识格式正确({模块}:{业务}:{操作})
+- [ ] 数据库表包含标准字段
+- [ ] API 接口遵循 RESTful 风格
+- [ ] 代码注释完整
+- [ ] 前端权限控制正确
+
+---
+
+**文档版本**:v1.0
+**最后更新**:2024年
+**维护者**:开发团队
diff --git a/lyzsys-module-demo/pom.xml b/lyzsys-module-demo/pom.xml
new file mode 100644
index 0000000..db73a90
--- /dev/null
+++ b/lyzsys-module-demo/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+ cn.iocoder.boot
+ lyzsys
+ ${revision}
+
+ 4.0.0
+ lyzsys-module-demo
+ jar
+
+ ${project.artifactId}
+
+ demo 模块,演示业务功能。
+ 例如:项目管理等
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-module-infra
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-mybatis
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-redis
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-test
+ test
+
+
+
+
+ cn.iocoder.boot
+ lyzsys-spring-boot-starter-excel
+
+
+
+
+
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/ProjectController.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/ProjectController.java
new file mode 100644
index 0000000..c662151
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/ProjectController.java
@@ -0,0 +1,102 @@
+package cn.iocoder.lyzsys.module.demo.controller.admin.project;
+
+import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
+import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
+import cn.iocoder.lyzsys.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectRespVO;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectSaveReqVO;
+import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
+import cn.iocoder.lyzsys.module.demo.service.project.ProjectService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 项目管理")
+@RestController
+@RequestMapping("/demo/project")
+@Validated
+public class ProjectController {
+
+ @Resource
+ private ProjectService projectService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建项目")
+ @PreAuthorize("@ss.hasPermission('demo:project:create')")
+ public CommonResult createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
+ Long projectId = projectService.createProject(createReqVO);
+ return success(projectId);
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改项目")
+ @PreAuthorize("@ss.hasPermission('demo:project:update')")
+ public CommonResult updateProject(@Valid @RequestBody ProjectSaveReqVO updateReqVO) {
+ projectService.updateProject(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除项目")
+ @Parameter(name = "id", description = "编号", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('demo:project:delete')")
+ public CommonResult deleteProject(@RequestParam("id") Long id) {
+ projectService.deleteProject(id);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete-list")
+ @Operation(summary = "批量删除项目")
+ @Parameter(name = "ids", description = "编号列表", required = true)
+ @PreAuthorize("@ss.hasPermission('demo:project:delete')")
+ public CommonResult deleteProjectList(@RequestParam("ids") List ids) {
+ projectService.deleteProjectList(ids);
+ return success(true);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得项目分页列表")
+ @PreAuthorize("@ss.hasPermission('demo:project:query')")
+ public CommonResult> getProjectPage(@Valid ProjectPageReqVO pageReqVO) {
+ PageResult pageResult = projectService.getProjectPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, ProjectRespVO.class));
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得项目详情")
+ @Parameter(name = "id", description = "编号", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('demo:project:query')")
+ public CommonResult getProject(@RequestParam("id") Long id) {
+ ProjectDO project = projectService.getProject(id);
+ return success(BeanUtils.toBean(project, ProjectRespVO.class));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "导出项目")
+ @PreAuthorize("@ss.hasPermission('demo:project:export')")
+ @ApiAccessLog(operateType = EXPORT)
+ public void exportProject(HttpServletResponse response, @Valid ProjectPageReqVO exportReqVO) throws IOException {
+ exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+ List list = projectService.getProjectPage(exportReqVO).getList();
+ // 导出
+ ExcelUtils.write(response, "项目管理.xls", "数据", ProjectRespVO.class,
+ BeanUtils.toBean(list, ProjectRespVO.class));
+ }
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectPageReqVO.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectPageReqVO.java
new file mode 100644
index 0000000..61430a7
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectPageReqVO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.lyzsys.module.demo.controller.admin.project.vo;
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 项目管理分页列表 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProjectPageReqVO extends PageParam {
+
+ @Schema(description = "项目名称,模糊匹配", example = "示例项目")
+ private String projectName;
+
+ @Schema(description = "项目编号,模糊匹配", example = "PROJ-2024-001")
+ private String projectCode;
+
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Schema(description = "立项时间")
+ private LocalDateTime[] establishDate;
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectRespVO.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectRespVO.java
new file mode 100644
index 0000000..e22de31
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectRespVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.lyzsys.module.demo.controller.admin.project.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 项目管理信息 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ProjectRespVO {
+
+ @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @ExcelProperty("项目ID")
+ private Long id;
+
+ @Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例项目")
+ @ExcelProperty("项目名称")
+ private String projectName;
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "PROJ-2024-001")
+ @ExcelProperty("项目编号")
+ private String projectCode;
+
+ @Schema(description = "立项时间", example = "2024-01-01 00:00:00")
+ @ExcelProperty("立项时间")
+ private LocalDateTime establishDate;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectSaveReqVO.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectSaveReqVO.java
new file mode 100644
index 0000000..ff7d879
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/controller/admin/project/vo/ProjectSaveReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.lyzsys.module.demo.controller.admin.project.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 项目管理创建/修改 Request VO")
+@Data
+public class ProjectSaveReqVO {
+
+ @Schema(description = "项目ID", example = "1")
+ private Long id;
+
+ @Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例项目")
+ @NotBlank(message = "项目名称不能为空")
+ @Size(max = 100, message = "项目名称长度不能超过100个字符")
+ private String projectName;
+
+ @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "PROJ-2024-001")
+ @NotBlank(message = "项目编号不能为空")
+ @Size(max = 50, message = "项目编号长度不能超过50个字符")
+ private String projectCode;
+
+ @Schema(description = "立项时间", example = "2024-01-01 00:00:00")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime establishDate;
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/dataobject/project/ProjectDO.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/dataobject/project/ProjectDO.java
new file mode 100644
index 0000000..1cb84d4
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/dataobject/project/ProjectDO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.lyzsys.module.demo.dal.dataobject.project;
+
+import cn.iocoder.lyzsys.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * 项目管理 DO
+ *
+ * @author lyzsys
+ */
+@TableName("demo_project")
+@KeySequence("demo_project_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ProjectDO extends BaseDO {
+
+ /**
+ * 项目ID
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 项目名称
+ */
+ private String projectName;
+
+ /**
+ * 项目编号
+ */
+ private String projectCode;
+
+ /**
+ * 立项时间
+ */
+ private LocalDateTime establishDate;
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/mysql/project/ProjectMapper.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/mysql/project/ProjectMapper.java
new file mode 100644
index 0000000..fe5ad3f
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/dal/mysql/project/ProjectMapper.java
@@ -0,0 +1,30 @@
+package cn.iocoder.lyzsys.module.demo.dal.mysql.project;
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
+import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 项目管理 Mapper
+ *
+ * @author lyzsys
+ */
+@Mapper
+public interface ProjectMapper extends BaseMapperX {
+
+ default PageResult selectPage(ProjectPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(ProjectDO::getProjectName, reqVO.getProjectName())
+ .likeIfPresent(ProjectDO::getProjectCode, reqVO.getProjectCode())
+ .betweenIfPresent(ProjectDO::getEstablishDate, reqVO.getEstablishDate())
+ .orderByDesc(ProjectDO::getId));
+ }
+
+ default ProjectDO selectByProjectCode(String projectCode) {
+ return selectOne(ProjectDO::getProjectCode, projectCode);
+ }
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/enums/ErrorCodeConstants.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000..f3ef318
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/enums/ErrorCodeConstants.java
@@ -0,0 +1,16 @@
+package cn.iocoder.lyzsys.module.demo.enums;
+
+import cn.iocoder.lyzsys.framework.common.exception.ErrorCode;
+
+/**
+ * Demo 错误码枚举类
+ *
+ * demo 模块,使用 1-010-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 项目管理模块 1-010-001-000 ==========
+ ErrorCode PROJECT_NOT_EXISTS = new ErrorCode(1_010_001_000, "项目不存在");
+ ErrorCode PROJECT_CODE_DUPLICATE = new ErrorCode(1_010_001_001, "已经存在该项目编号");
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/ProjectService.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/ProjectService.java
new file mode 100644
index 0000000..72acb6c
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/ProjectService.java
@@ -0,0 +1,62 @@
+package cn.iocoder.lyzsys.module.demo.service.project;
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectSaveReqVO;
+import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
+
+import java.util.List;
+
+/**
+ * 项目管理 Service 接口
+ *
+ * @author lyzsys
+ */
+public interface ProjectService {
+
+ /**
+ * 创建项目
+ *
+ * @param createReqVO 项目信息
+ * @return 项目编号
+ */
+ Long createProject(ProjectSaveReqVO createReqVO);
+
+ /**
+ * 更新项目
+ *
+ * @param updateReqVO 项目信息
+ */
+ void updateProject(ProjectSaveReqVO updateReqVO);
+
+ /**
+ * 删除项目
+ *
+ * @param id 项目编号
+ */
+ void deleteProject(Long id);
+
+ /**
+ * 批量删除项目
+ *
+ * @param ids 项目编号列表
+ */
+ void deleteProjectList(List ids);
+
+ /**
+ * 获得项目分页列表
+ *
+ * @param pageReqVO 分页请求
+ * @return 项目分页列表
+ */
+ PageResult getProjectPage(ProjectPageReqVO pageReqVO);
+
+ /**
+ * 获得项目详情
+ *
+ * @param id 项目编号
+ * @return 项目详情
+ */
+ ProjectDO getProject(Long id);
+
+}
diff --git a/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/ProjectServiceImpl.java b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/ProjectServiceImpl.java
new file mode 100644
index 0000000..b21f501
--- /dev/null
+++ b/lyzsys-module-demo/src/main/java/cn/iocoder/lyzsys/module/demo/service/project/ProjectServiceImpl.java
@@ -0,0 +1,102 @@
+package cn.iocoder.lyzsys.module.demo.service.project;
+
+import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
+import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectPageReqVO;
+import cn.iocoder.lyzsys.module.demo.controller.admin.project.vo.ProjectSaveReqVO;
+import cn.iocoder.lyzsys.module.demo.dal.dataobject.project.ProjectDO;
+import cn.iocoder.lyzsys.module.demo.dal.mysql.project.ProjectMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.lyzsys.module.demo.enums.ErrorCodeConstants.*;
+
+/**
+ * 项目管理 Service 实现类
+ *
+ * @author lyzsys
+ */
+@Service
+@Validated
+public class ProjectServiceImpl implements ProjectService {
+
+ @Resource
+ private ProjectMapper projectMapper;
+
+ @Override
+ public Long createProject(ProjectSaveReqVO createReqVO) {
+ // 校验项目编号的唯一性
+ validateProjectCodeUnique(null, createReqVO.getProjectCode());
+
+ // 插入项目
+ ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class);
+ projectMapper.insert(project);
+ return project.getId();
+ }
+
+ @Override
+ public void updateProject(ProjectSaveReqVO updateReqVO) {
+ // 校验项目存在
+ validateProjectExists(updateReqVO.getId());
+ // 校验项目编号的唯一性
+ validateProjectCodeUnique(updateReqVO.getId(), updateReqVO.getProjectCode());
+
+ // 更新项目
+ ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class);
+ projectMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteProject(Long id) {
+ // 校验项目存在
+ validateProjectExists(id);
+ // 删除项目
+ projectMapper.deleteById(id);
+ }
+
+ @Override
+ public void deleteProjectList(List ids) {
+ // 校验项目存在
+ ids.forEach(this::validateProjectExists);
+ // 批量删除
+ projectMapper.deleteBatchIds(ids);
+ }
+
+ @Override
+ public PageResult getProjectPage(ProjectPageReqVO pageReqVO) {
+ return projectMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public ProjectDO getProject(Long id) {
+ return projectMapper.selectById(id);
+ }
+
+ // ==================== 校验方法 ====================
+
+ private void validateProjectExists(Long id) {
+ if (projectMapper.selectById(id) == null) {
+ throw exception(PROJECT_NOT_EXISTS);
+ }
+ }
+
+ private void validateProjectCodeUnique(Long id, String projectCode) {
+ ProjectDO project = projectMapper.selectByProjectCode(projectCode);
+ if (project == null) {
+ return;
+ }
+ // 如果 id 为空,说明是新增,项目编号已存在则报错
+ if (id == null) {
+ throw exception(PROJECT_CODE_DUPLICATE);
+ }
+ // 如果有 id,说明是修改,判断是否是自己
+ if (!project.getId().equals(id)) {
+ throw exception(PROJECT_CODE_DUPLICATE);
+ }
+ }
+
+}