Files
lyzsys_backend/doc/新模块开发指南.md

932 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>lyzsys-module-{模块名}</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
{模块说明}
</description>
<dependencies>
<!-- 依赖 infra 模块 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-module-infra</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-redis</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-excel</artifactId>
</dependency>
</dependencies>
</project>
```
#### 步骤 3添加模块到父 pom.xml
`lyzsys_backend/pom.xml` 中添加:
```xml
<modules>
<!-- 已有模块 -->
<module>lyzsys-module-system</module>
<module>lyzsys-module-infra</module>
<!-- 新增模块 -->
<module>lyzsys-module-{模块名}</module>
</modules>
```
#### 步骤 4添加模块到 server
`lyzsys-server/pom.xml` 中添加依赖:
```xml
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-module-{模块名}</artifactId>
<version>${revision}</version>
</dependency>
```
### 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<Long> 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<Long> 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<Long> create{业务}(@Valid @RequestBody {业务}SaveReqVO createReqVO) {
Long id = {业务}Service.create{业务}(createReqVO);
return success(id);
}
@PutMapping("/update")
@Operation(summary = "修改{业务}")
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:update')")
public CommonResult<Boolean> 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<Boolean> 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<Boolean> delete{业务}List(@RequestParam("ids") List<Long> ids) {
{业务}Service.delete{业务}List(ids);
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得{业务}分页列表")
@PreAuthorize("@ss.hasPermission('{模块名}:{业务}:query')")
public CommonResult<PageResult<{业务}RespVO>> 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年
**维护者**:开发团队