# 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年 **维护者**:开发团队