文章目录
- 前言
- 正文
- 一、项目结构
- 二、技术点
- 三、部分核心代码
- 3.1 core-tool 中的核心代码
- 3.1.1 所有实体的通用父类 SuperEntity
- 3.1.2 所有枚举的父接口 BaseEnum
- 3.1.3 所有业务异常的父接口 BaseException
- 3.2 mvc-tool 中的核心代码
- 3.2.1 CrudController 接口定义
- 3.2.2 默认的CRUD控制器实现
- 3.2.3 查询service接口定义
- 3.2.4 修改service接口定义
- 3.2.5 CRUD 的service聚合接口定义
- 3.2.6 基础的manager定义
- 3.2.7 查询转换器定义
- 3.2.8 聚合转换器定义
- 3.3 generator-tool 中的核心代码
- 3.3.1 进行代码生成
- 3.3.2 枚举的freemarker模版文件定义
- 3.3.3 管理类的模版生成
- 3.3.4 mapper.xml文件的模版生成
- 3.3.5 service实现类的模版生成
- 四、代码生成效果展示
前言
日常开发一个项目,经常会写到CRUD和导入导出功能,很多时候都是模版式的代码结构,多次复制粘贴后就完成了。
这次我打算去造一个轮子,替我去做复制粘贴的活!!
目标很简单,使用SpringBoot架构去是实现一个基于Mysql数据库的自带增删改查,导入导出功能的模板代码。并且提供完备的代码生成器,一键生成你想要的代码。
本项目托管在gitte上:https://gitee.com/fengsoshuai/song-tools
欢迎各位点赞收藏,有好的建议也可以留言。
正文
一、项目结构
song-toos||--core-tool (核心工具包,定义了通用实体,枚举,异常等)||--database-tool (数据库工具,目前只配置了数据库的分页插件)||--generator-tool (代码生成工具,依赖mvc-tool,通过数据库表生成代码,包含实体、BO、请求体、响应体、转换器、mapper、service、serviceImpl等)||--mvc-tool (mvc工具,提供通用的mvc接口,controller接口和默认的crud实现,crud转换器、默认的管理类实现、service接口等)||--song-test (单独的项目,主要用于测试本框架,引入mvc-tool进行业务代码的编写,各位在使用本框架时,可以参考这个项目来实现你自己的功能)
二、技术点
组件/技术点 | 版本 | 备注 |
---|---|---|
Java | 17 | |
SpringBoot | 3.2.5 | |
Mybatis-Plus | 3.5.6 | 整和时需要注意单独引入mybatis-spring(3.0.3)的包 |
Hutool | 5.8.27 | |
Lombok | 1.8.32 | |
easyexcel | 3.3.4 | 项目中导入导出功能使用easyexcel |
knife4f-openapi3 | 4.5.0 |
三、部分核心代码
3.1 core-tool 中的核心代码
3.1.1 所有实体的通用父类 SuperEntity
package com.song.tools.core.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;/*** <p>* super实体* </p>** @author song tools* @since 2024-05-15*/
@Data
public class SuperEntity<ID extends Serializable> {/*** 自增主键*/@TableId(value = "id", type = IdType.AUTO)protected ID id;/*** 创建人id*/@TableField(value = "create_user_id")protected ID createUserId;/*** 创建人用户名*/@TableField(value = "create_username")protected String createUsername;/*** 创建时间*/@TableField(value = "create_time")protected LocalDateTime createTime;/*** 更新人id*/@TableField(value = "update_user_id")protected ID updateUserId;/*** 更新人用户名*/@TableField(value = "update_username")protected String updateUsername;/*** 更新时间*/@TableField(value = "update_time")protected LocalDateTime updateTime;/*** 逻辑删除,0表示未删除,1表示已删除*/@TableField(value = "deleted")protected Integer deleted;
}
3.1.2 所有枚举的父接口 BaseEnum
package com.song.tools.core.enums;import com.baomidou.mybatisplus.annotation.IEnum;import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;/*** <p>* 基础枚举接口* </p>** @author song tools* @since 2024-05-16*/
public interface BaseEnum<CODE extends Serializable> extends IEnum<CODE> {/*** 获取编码*/CODE getCode();/*** 获取描述*/String getDesc();default CODE getValue() {return this.getCode();}static <E extends Serializable, T extends BaseEnum<E>> T convertCodeToEnum(E code, Class<T> clazz) {if (code == null) {return null;} else {List<T> values = BaseEnum.getEnumList(clazz);return values.stream().filter(item -> Objects.equals(item.getCode(), code)).findAny().orElse(null);}}static <T extends BaseEnum<?>> T convertDescToEnum(String desc, Class<T> cls) {if (desc == null) {return null;} else {List<T> values = BaseEnum.getEnumList(cls);return values.stream().filter((item) -> Objects.equals(item.getDesc(), desc)).findAny().orElse(null);}}static <T extends BaseEnum<?>> List<T> getEnumList(Class<T> enumClass) {return new ArrayList<T>(Arrays.asList(enumClass.getEnumConstants()));}
}
3.1.3 所有业务异常的父接口 BaseException
package com.song.tools.core.exception;/*** 基础异常** @author song tools* @since 2024-06-06*/
public interface BaseException {/*** 返回异常信息** @return 异常信息*/String getMessage();/*** 返回异常编码** @return 异常编码*/String getCode();
}
3.2 mvc-tool 中的核心代码
com.song.tools.mvc| -- controller (定义了父级controller的接口和对应方法,以及默认的crud实现)| -- convertor (定义了实体之间的转换器,内部使用mapstruct插件实现自动映射,具体的业务字段需要用户自行补充)| -- manager (定义了基础的管理类和基础查询类)| -- request (默认的请求体,比如ID请求体,分页请求体等)| -- response (默认的响应体,比如分页响应体)| -- service (CRUD的service接口定义和方法定义)| --validator (参数校验器工具)
3.2.1 CrudController 接口定义
package com.song.tools.mvc.controller;import com.song.tools.mvc.request.BatchIdRequest;
import com.song.tools.mvc.request.IdRequest;
import com.song.tools.mvc.request.PageRequest;
import com.song.tools.mvc.response.PageResponse;
import com.song.tools.mvc.response.ResultVo;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;import javax.validation.Valid;/*** CRUD 控制器** @author song tools* @since 2024-06-25*/
public interface CrudController<QueryRequest, QueryResponse, SaveRequest, SaveResponse, UpdateRequest, UpdateResponse, BusinessId, DeleteResponse> {/*** 分页查询** @param request 请求* @return 分页响应*/@Operation(summary = "分页查询")@PostMapping("/listPages")ResultVo<PageResponse<QueryResponse>> listPages(@Valid @RequestBody PageRequest<QueryRequest> request);/*** 查询详细信息** @param request 请求* @return 响应*/@PostMapping(value = "/getDetail")@Operation(summary = "查询详细信息")ResultVo<QueryResponse> getDetail(@Valid @RequestBody IdRequest<BusinessId> request);/*** 新增** @param request 请求* @return 响应*/@PostMapping(value = "/save")@Operation(summary = "新增")ResultVo<SaveResponse> save(@Valid @RequestBody SaveRequest request);/*** 修改** @param request 请求* @return 响应*/@PostMapping(value = "/update")@Operation(summary = "修改")ResultVo<UpdateResponse> update(@Valid @RequestBody UpdateRequest request);/*** 删除** @param request 请求* @return 响应*/@PostMapping(value = "/delete")@Operation(summary = "删除")ResultVo<DeleteResponse> delete(@Valid @RequestBody BatchIdRequest<BusinessId> request);
}
3.2.2 默认的CRUD控制器实现
package com.song.tools.mvc.controller;import com.song.tools.mvc.request.BatchIdRequest;
import com.song.tools.mvc.request.IdRequest;
import com.song.tools.mvc.request.PageRequest;
import com.song.tools.mvc.response.PageResponse;
import com.song.tools.mvc.response.ResultVo;
import com.song.tools.mvc.service.CrudService;
import jakarta.annotation.Resource;
import lombok.Getter;import java.io.Serializable;/*** CRUD 控制器的基类(默认实现)** @author song tools* @since 2024-06-25*/
@Getter
public abstract class SuperCrudController<Service extends CrudService<PrimaryKey, BusinessId, QueryRequest, QueryResponse, SaveRequest, SaveResponse, UpdateRequest, UpdateResponse, DeleteResponse>,QueryRequest, QueryResponse, SaveRequest, SaveResponse, UpdateRequest, UpdateResponse, PrimaryKey extends Serializable, BusinessId extends Serializable, DeleteResponse>implements CrudController<QueryRequest, QueryResponse, SaveRequest, SaveResponse, UpdateRequest, UpdateResponse, BusinessId, DeleteResponse> {/*** 业务服务实例*/@Resourceprivate Service service;/*** 分页查询** @param request 请求* @return 分页响应*/@Overridepublic ResultVo<PageResponse<QueryResponse>> listPages(PageRequest<QueryRequest> request) {request.defaultPage();return ResultVo.success(service.listPages(request));}/*** 查询详细信息** @param request 请求* @return 响应*/@Overridepublic ResultVo<QueryResponse> getDetail(IdRequest<BusinessId> request) {return ResultVo.success(service.getOneByBusinessId(request.getId()));}/*** 新增** @param request 请求* @return 响应*/@Overridepublic ResultVo<SaveResponse> save(SaveRequest request) {return ResultVo.success(service.save(request));}/*** 修改** @param request 请求* @return 响应*/@Overridepublic ResultVo<UpdateResponse> update(UpdateRequest request) {return ResultVo.success(service.update(request));}/*** 删除** @param request 请求* @return 响应*/@Overridepublic ResultVo<DeleteResponse> delete(BatchIdRequest<BusinessId> request) {return ResultVo.success(service.deleteByBusinessIds(request.getIds()));}
}
3.2.3 查询service接口定义
package com.song.tools.mvc.service;import com.song.tools.mvc.request.PageRequest;
import com.song.tools.mvc.response.PageResponse;import java.io.Serializable;
import java.util.List;/*** 查询服务** @author song tools* @since 2024-06-06*/
public interface QueryService<PrimaryKey extends Serializable, BusinessId extends Serializable, QueryRequest, QueryResponse> {/*** 通过主键查询单个数据** @param primaryKey 主键* @return 单个数据查询响应结果*/QueryResponse getOneByPrimaryKey(PrimaryKey primaryKey);/*** 通过业务ID查询单个数据** @param businessId 业务ID* @return 单个数据查询响应结果*/QueryResponse getOneByBusinessId(BusinessId businessId);/*** 查询信息(分页)** @param request 请求* @return PageResponse 响应*/PageResponse<QueryResponse> listPages(PageRequest<QueryRequest> request);
}
3.2.4 修改service接口定义
package com.song.tools.mvc.service;import com.song.tools.mvc.validator.ValidationResult;/*** 修改服务** @author song tools* @since 2024-06-19*/
public interface UpdateService<UpdateRequest, UpdateResponse> {/*** 修改** @param request 请求* @return 响应*/UpdateResponse update(UpdateRequest request);/*** 修改前的数据校验** @param request 请求*/default ValidationResult validateOnUpdate(UpdateRequest request) {return new ValidationResult(true, null);}
}
3.2.5 CRUD 的service聚合接口定义
package com.song.tools.mvc.service;import java.io.Serializable;/*** CRUD服务** @author song tools* @since 2024-06-19*/
public interface CrudService<PrimaryKey extends Serializable, BusinessId extends Serializable, QueryRequest, QueryResponse, SaveRequest, SaveResponse, UpdateRequest, UpdateResponse, DeleteResponse>extendsQueryService<PrimaryKey, BusinessId, QueryRequest, QueryResponse>,SaveService<SaveRequest, SaveResponse>,UpdateService<UpdateRequest, UpdateResponse>,DeleteService<PrimaryKey, BusinessId, DeleteResponse> {
}
3.2.6 基础的manager定义
package com.song.tools.mvc.manager;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.song.tools.core.entity.SuperEntity;
import com.song.tools.mvc.request.PageRequest;
import org.springframework.beans.factory.annotation.Autowired;import java.io.Serializable;
import java.util.*;/*** 基础管理类** @author song tools* @since 2024-06-19*/
public abstract class BaseManager<Entity extends SuperEntity<?>, EntityBo, Mapper extends BaseMapper<Entity>, Query extends BaseQuery, PrimaryKey extends Serializable, BusinessId extends Serializable> {@Autowiredprotected Mapper mapper;/*** BaseManager的范型类型*/protected final Class<?>[] typeArguments = GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseManager.class);/*** Entity 的类型*/protected Class<Entity> entityClass = this.currentModelClass();/*** EntityBo 的类型*/protected Class<EntityBo> entityBoClass = this.currentModelBoClass();/*** Mapper 的类型*/protected Class<Mapper> mapperClass = this.currentMapperClass();protected static final String ASC = "asc";protected static final String DESC = "desc";protected static final String DEFAULT_ORDER_BY_FIELD = "id";public BaseManager() {}@SuppressWarnings("unchecked")protected Class<Entity> currentModelClass() {return (Class<Entity>) this.typeArguments[0];}@SuppressWarnings("unchecked")protected Class<EntityBo> currentModelBoClass() {return (Class<EntityBo>) this.typeArguments[1];}@SuppressWarnings("unchecked")protected Class<Mapper> currentMapperClass() {return (Class<Mapper>) this.typeArguments[2];}/*** 保存一条数据** @param entity 实体*/public int save(Entity entity) {return this.mapper.insert(entity);}/*** 批量插入数据** @param entityList 实体列表* @param <E> 实体类型*/public <E extends Entity> void saveBatch(List<E> entityList) {if (CollUtil.isNotEmpty(entityList)) {for (E entity : entityList) {this.mapper.insert(entity);}}}/*** 通过ID修改一条数据** @param entity 实体*/public int updateByPrimaryKey(Entity entity) {return this.mapper.updateById(entity);}/*** 通过业务id修改一条数据** @param entity 实体*/public int updateByBusinessId(Entity entity, BusinessId businessId, SFunction<Entity, ?> column) {LambdaUpdateWrapper<Entity> updateWrapper = Wrappers.lambdaUpdate();updateWrapper.eq(column, businessId);return this.mapper.update(entity, updateWrapper);}/*** 通过业务id列表批量删除** @param businessIds 业务id列表*/public int deleteBatchByBusinessIds(Collection<BusinessId> businessIds, SFunction<Entity, ?> column) {if (CollUtil.isNotEmpty(businessIds)) {LambdaUpdateWrapper<Entity> updateWrapper = Wrappers.lambdaUpdate();updateWrapper.in(column, businessIds);return this.mapper.delete(updateWrapper);}return 0;}/*** 通过业务id删除** @param businessId 业务id*/public int deleteByBusinessId(BusinessId businessId, SFunction<Entity, ?> column) {LambdaUpdateWrapper<Entity> updateWrapper = Wrappers.lambdaUpdate();updateWrapper.eq(column, businessId);return this.mapper.delete(updateWrapper);}/*** 通过主键删除数据** @param primaryKey 主键*/public int deleteByPrimaryKey(PrimaryKey primaryKey) {return this.mapper.deleteById(primaryKey);}/*** 通过主键列表删除数据** @param primaryKeys 主键列表*/public int deleteBatchByPrimaryKeys(Collection<PrimaryKey> primaryKeys) {return this.mapper.deleteBatchIds(primaryKeys);}/*** 通过业务ID查询单条数据** @param businessId 业务ID* @return 业务对象*/public EntityBo getOneByBusinessId(BusinessId businessId, SFunction<Entity, ?> businessColumn) {LambdaQueryWrapper<Entity> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(businessColumn, businessId);return getOne(queryWrapper);}/*** 通过主键查询单条数据** @param primaryKey 主键* @return 业务对象*/public EntityBo getOneByPrimaryKey(PrimaryKey primaryKey) {LambdaQueryWrapper<Entity> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Entity::getId, primaryKey);return getOne(queryWrapper);}/*** 查询单条数据** @param query 查询参数* @return 业务对象*/public EntityBo getOne(Query query) {// 处理业务查询条件LambdaQueryWrapper<Entity> queryWrapper = this.encapsulateQueryWrapper(query);queryWrapper.last(" limit 1");return getOne(queryWrapper);}/*** 通过查询条件查询单条数据** @param queryWrapper 查询条件* @return 业务对象*/protected EntityBo getOne(LambdaQueryWrapper<Entity> queryWrapper) {// 查询单条数据Entity entity = this.mapper.selectOne(queryWrapper);if (Objects.isNull(entity)) {return null;}// 根据查询结果转换业务对象return createBo(Collections.singletonList(entity)).get(0);}/*** 分页查询实现** @param request 请求参数* @return 响应结果*/public IPage<EntityBo> page(PageRequest<Query> request) {// 组装查询条件LambdaQueryWrapper<Entity> queryWrapper = encapsulateQueryWrapper(request.getData());// 分页查数据Page<Entity> entityPage = this.mapper.selectPage(new Page<>(request.getPageNum(), request.getPageSize()), queryWrapper);// 根据查询结果组装业务对象列表List<EntityBo> entityBos = createBo(entityPage.getRecords());// 返回业务分页结果return Page.<EntityBo>of(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal(), entityPage.searchCount()).setRecords(entityBos);}/*** 封装查询条件** @param request 请求参数* @return 查询条件wrapper*/protected LambdaQueryWrapper<Entity> encapsulateQueryWrapper(Query request) {QueryWrapper<Entity> wrapper = new QueryWrapper<>();if (Objects.isNull(request)) {return wrapper.lambda();}// 处理查询字段String[] underLineFields = request.convertToUnderLineFields();if (Objects.nonNull(underLineFields) && underLineFields.length > 0) {wrapper.select(underLineFields);}// 处理排序规则String orderBy = request.getOrderBy();if (StrUtil.isNotBlank(orderBy)) {String[] orderByExpressions = orderBy.split(StrUtil.COMMA);List<String> ascFields = new ArrayList<>();List<String> descFields = new ArrayList<>();for (String orderByExpression : orderByExpressions) {if (orderByExpression.endsWith(ASC)) {ascFields.add(orderByExpression.replace(StrUtil.DASHED + ASC, StrUtil.EMPTY));continue;}if (orderByExpression.endsWith(DESC)) {descFields.add(orderByExpression.replace(StrUtil.DASHED + DESC, StrUtil.EMPTY));}}// 没有排序规则,使用ID倒序排序if (CollUtil.isEmpty(descFields) && CollUtil.isEmpty(ascFields)) {wrapper.orderByDesc(DEFAULT_ORDER_BY_FIELD);return wrapper.lambda();}if (CollUtil.isNotEmpty(ascFields)) {wrapper.orderByAsc(ascFields);}if (CollUtil.isNotEmpty(descFields)) {wrapper.orderByDesc(descFields);}} else {// 没有排序规则,使用ID倒序排序wrapper.orderByDesc(DEFAULT_ORDER_BY_FIELD);}return wrapper.lambda();}/*** 创建实体对应的BO对象(提供默认实现,建议使用者在实际的manager中重写此方法)** @param entities 实体列表* @return BO对象列表*/protected List<EntityBo> createBo(List<Entity> entities) {if (CollUtil.isEmpty(entities)) {return Collections.emptyList();}List<EntityBo> entityBos = new ArrayList<>();for (Entity entity : entities) {EntityBo entityBo = BeanUtil.copyProperties(entity, entityBoClass);if (Objects.nonNull(entityBo)) {entityBos.add(entityBo);}}return entityBos;}
}
3.2.7 查询转换器定义
package com.song.tools.mvc.convertor;/*** 查询类转换器** @author song tools* @since 2024-06-21*/
public interface QueryConvertor<EntityBo, Query, QueryRequest, QueryResponse> {QueryResponse entityBoToQueryResponse(EntityBo entityBo);Query queryRequestToQuery(QueryRequest queryRequest);
}
3.2.8 聚合转换器定义
package com.song.tools.mvc.convertor;/*** 聚合转换器** @author song tools* @since 2024-06-21*/
public interface AggregationConvertor<Entity, EntityBo, Query, QueryRequest, QueryResponse, SaveRequest, SaveResponse, UpdateRequest, UpdateResponse, ImportDto, ExportDto>extends QueryConvertor<EntityBo, Query, QueryRequest, QueryResponse>,SaveConvertor<EntityBo, SaveRequest, SaveResponse>,UpdateConvertor<EntityBo, UpdateRequest, UpdateResponse>,EntityConvertor<Entity, EntityBo>,PoiConvertor<EntityBo, ImportDto, ExportDto>{
}
3.3 generator-tool 中的核心代码
这个模块主要用来进行代码生成,使用如下的表结构为例:
/*Navicat Premium Data TransferSource Server : pc-密码:rootSource Server Type : MySQLSource Server Version : 80022Source Host : localhost:3306Source Schema : pine_dictTarget Server Type : MySQLTarget Server Version : 80022File Encoding : 65001Date: 29/06/2024 10:46:12
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for pine_dictionary
-- ----------------------------
DROP TABLE IF EXISTS `pine_dictionary`;
CREATE TABLE `pine_dictionary` (`id` bigint(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',`dictionary_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典ID',`code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编码',`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称',`status` int(0) NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=禁用',`remark` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '备注',`create_user_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '创建人id',`create_username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'system' COMMENT '创建人用户名',`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',`update_user_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '更新人id',`update_username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'system' COMMENT '更新人用户名',`update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',`deleted` int(0) NOT NULL DEFAULT 0 COMMENT '逻辑删除,0表示未删除,1表示已删除',PRIMARY KEY (`id`) USING BTREE,INDEX `index_code`(`code`) USING BTREE,INDEX `index_dictionary_id`(`dictionary_id`) USING BTREE,INDEX `index_name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '字典表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of pine_dictionary
-- ----------------------------
INSERT INTO `pine_dictionary` VALUES (2, '1791361496488505344', 'sex', '性别', 0, '性别只有男和女', 0, 'system', '2024-05-17 14:53:57', 0, 'system', '2024-05-17 16:21:00', 0);
INSERT INTO `pine_dictionary` VALUES (3, '1794204680356581376', 'enable_disable_status', '启用/禁用状态', 1, '状态只有启用或禁用', 0, 'system', '2024-05-25 11:11:45', 0, 'system', '2024-05-25 11:12:43', 0);-- ----------------------------
-- Table structure for pine_dictionary_detail
-- ----------------------------
DROP TABLE IF EXISTS `pine_dictionary_detail`;
CREATE TABLE `pine_dictionary_detail` (`id` bigint(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',`dictionary_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典ID',`dictionary_detail_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典明细ID',`dictionary_value` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值',`dictionary_value_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值编码',`dictionary_value_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值名称',`status` int(0) NOT NULL DEFAULT 1 COMMENT '状态:1=启用,0=禁用',`remark` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '备注',`ordered` int(0) NOT NULL DEFAULT 0 COMMENT '排序',`create_user_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '创建人id',`create_username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'system' COMMENT '创建人用户名',`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',`update_user_id` bigint(0) NOT NULL DEFAULT 0 COMMENT '更新人id',`update_username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'system' COMMENT '更新人用户名',`update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',`deleted` int(0) NOT NULL DEFAULT 0 COMMENT '逻辑删除,0表示未删除,1表示已删除',PRIMARY KEY (`id`) USING BTREE,INDEX `index_dictionary_id`(`dictionary_id`) USING BTREE,INDEX `index_dictionary_detail_id`(`dictionary_detail_id`) USING BTREE,INDEX `index_dictionary_value_code`(`dictionary_value_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '字典明细表' ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of pine_dictionary_detail
-- ----------------------------
INSERT INTO `pine_dictionary_detail` VALUES (1, '1791361496488505344', '1794207204081881088', '0', 'male', '男', 1, '暂无', 1, 0, 'system', '2024-05-25 11:21:46', 0, 'system', '2024-05-25 11:21:46', 0);
INSERT INTO `pine_dictionary_detail` VALUES (2, '1791361496488505344', '1794207283383586816', '1', 'female', '女', 1, '暂无', 2, 0, 'system', '2024-05-25 11:22:05', 0, 'system', '2024-05-25 11:22:05', 0);
INSERT INTO `pine_dictionary_detail` VALUES (3, '1794204680356581376', '1794209609565552640', '1', 'enabled', '启用', 1, '暂无', 1, 0, 'system', '2024-05-25 11:31:20', 0, 'system', '2024-05-25 11:31:20', 0);
INSERT INTO `pine_dictionary_detail` VALUES (4, '1794204680356581376', '1794209705384427520', '0', 'disabled', '禁用', 1, '暂无', 2, 0, 'system', '2024-05-25 11:31:43', 0, 'system', '2024-05-25 11:31:43', 0);SET FOREIGN_KEY_CHECKS = 1;
3.3.1 进行代码生成
package com.song.tools.generator;import com.song.tools.generator.config.GeneratorConfig;
import com.song.tools.generator.config.MysqlConnectionConfig;import java.util.List;
import java.util.Map;/*** 生成器client-入口** @author song tools* @since 2024-06-24*/
public class GeneratorClient {private static final String DB_URL = "jdbc:mysql://localhost:3306/pine_dict?serverTimezone=UTC&useSSL=false";private static final String DB_USERNAME = "root";private static final String DB_PASSWORD = "root";public static void main(String[] args) {// 获取当前项目对应的目录String userDir = System.getProperty("user.dir");// 数据库连接信息配置MysqlConnectionConfig mysqlConnectionConfig = new MysqlConnectionConfig(DB_URL, DB_USERNAME, DB_PASSWORD);// 生成器的通用配置信息GeneratorConfig generatorConfig = new GeneratorConfig().setMysqlConnectionConfig(mysqlConnectionConfig).setAuthor("song tools").setEnableSpringDoc(true).setProjectDir(userDir + "/generator-tool/").setParent("org.feng").setLogicDeleteColumnName("deleted").setTablePrefixList(List.of("pine_")).setTemplate("mybatis-templates");CodeGenerator codeGenerator = new CodeGenerator();// 生成字典表代码codeGenerator.execute(generatorConfig.toBuilder().tableName("pine_dictionary").moduleName("dict").businessIdName("dictionaryId").dbName("dict").businessIdType("String").primaryKeyType("Long").insertBatchData(true).poiServiceImpl(true).enumNameMap(Map.ofEntries(Map.entry("status", "StatusEnum"))) // 配置枚举的数据库字段名和类名.build());// 生成字典明细表代码
// codeGenerator.execute(generatorConfig.toBuilder()
// .tableName("pine_dictionary_detail")
// .moduleName("dict")
// .businessIdName("dictionaryDetailId")
// .dbName("dict")
// .businessIdType("String")
// .primaryKeyType("Long")
// .insertBatchData(true)
// .poiServiceImpl(true)
// .build());}
}
3.3.2 枚举的freemarker模版文件定义
package ${package.Enum};import com.baomidou.mybatisplus.annotation.EnumValue;
import com.song.tools.core.enums.${enumCodeType}CodeBaseEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;import java.util.Arrays;
import java.util.Objects;/*** <p>* ${table.comment!}-${enumChineseName} 枚举* </p>** @author ${author}* @since ${date}*/
@AllArgsConstructor
@Getter
public enum ${EnumName} implements ${enumCodeType}CodeBaseEnum {
<#list enumCodeAndDescList as enumCodeAndDesc><#if enumCodeType == "Integer">${enumCodeAndDesc.codeName}(${enumCodeAndDesc.code}, "${enumCodeAndDesc.desc}"),<#elseif enumCodeType == "String">${enumCodeAndDesc.codeName}("${enumCodeAndDesc.code}", "${enumCodeAndDesc.desc}"),</#if>
</#list>;@EnumValueprivate final ${enumCodeType} code;private final String desc;public static ${EnumName} of(${enumCodeType} code) {return Arrays.stream(${EnumName}.values()).filter(${lowerFirstCharEnumName} -> Objects.equals(${lowerFirstCharEnumName}.code, code)).findAny().orElse(null);}
}
3.3.3 管理类的模版生成
package ${package.Manager};import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.song.tools.core.util.ObjectUtils;
import com.song.tools.mvc.manager.BaseManager;
import ${package.Convertor}.${ConvertorName};
import ${package.Bo}.${BoName};
import ${package.Entity}.${EntityName};
import ${package.Query}.${QueryName};
import ${package.Mapper}.${EntityName}Mapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;import java.util.Collections;
import java.util.List;/*** <p>* ${table.comment!} 管理类* </p>** @author ${author}* @since ${date}*/
@Component
public class ${EntityName}Manager extends BaseManager<${EntityName}, ${BoName}, ${EntityName}Mapper, ${QueryName}, ${PrimaryKeyType}, ${BusinessIdType}> {@Resourceprivate ${ConvertorName} ${entityLowerCaseFirstOne}Convertor;@Overrideprotected LambdaQueryWrapper<${EntityName}> encapsulateQueryWrapper(${QueryName} query) {LambdaQueryWrapper<${EntityName}> queryWrapper = super.encapsulateQueryWrapper(query);if (query == null) {return queryWrapper;}// 组装查询条件<#list QueryFields as field><#if field.date>queryWrapper.ge(ObjectUtils.isNotEmpty(query.get${field.capitalName}Start()), ${entity}::get${field.capitalName}, query.get${field.capitalName}Start());queryWrapper.le(ObjectUtils.isNotEmpty(query.get${field.capitalName}End()), ${entity}::get${field.capitalName}, query.get${field.capitalName}End());<#else>queryWrapper.eq(ObjectUtils.isNotEmpty(query.get${field.capitalName}()), ${entity}::get${field.capitalName}, query.get${field.capitalName}());</#if></#list>return queryWrapper;}@Overrideprotected List<${BoName}> createBo(List<${EntityName}> entities) {if (CollUtil.isEmpty(entities)) {return Collections.emptyList();}return ${entityLowerCaseFirstOne}Convertor.entityToEntityBo(entities);}<#if insertBatchData>/*** 批量插入数据** @param entityList 实体列表* @param <E> 实体类型*/@Overridepublic <E extends ${EntityName}> void saveBatch(List<E> entityList) {if (CollUtil.isNotEmpty(entityList)) {mapper.insertBatchData(entityList);}}</#if>}
3.3.4 mapper.xml文件的模版生成
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}"><#if enableCache><!-- 开启二级缓存 --><cache type="${cacheClassName}"/></#if>
<#if baseResultMap><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位--><id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 --><result column="${field.name}" property="${field.propertyName}" />
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 --><result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list></resultMap></#if>
<#if baseColumnList><!-- 通用查询结果列 --><sql id="Base_Column_List">
<#list table.commonFields as field>${field.columnName},
</#list>${table.fieldNames}</sql></#if><#if insertBatchData><!-- 批量插入 --><insert id="insertBatchData">insert into ${schemaName}${table.name} (${EntityFieldsColumnNameJoin})values<foreach collection="entityList" item="entity" separator=",">(${EntityFieldsPropertyNameJoin})</foreach></insert>
</#if>
</mapper>
3.3.5 service实现类的模版生成
package ${package.ServiceImpl};import cn.hutool.core.collection.CollUtil;
<#if poiServiceImpl??>
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import jakarta.servlet.http.HttpServletResponse;
</#if>
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.song.tools.core.exception.BusinessException;
import com.song.tools.core.exception.ExceptionCodeEnum;
import com.song.tools.mvc.request.PageRequest;
import com.song.tools.mvc.response.DeleteResponse;
import com.song.tools.mvc.response.PageResponse;
import com.song.tools.mvc.validator.ValidationException;
import com.song.tools.mvc.validator.ValidationResult;
import ${package.Bo}.${BoName};
<#if poiServiceImpl??>
import ${package.ExportDto}.${ExportDtoName};
import ${package.ImportDto}.${ImportDtoName};
</#if>
import ${package.Entity}.${entity};
import ${package.Query}.${QueryName};
import ${package.QueryRequest}.${QueryRequestName};
import ${package.SaveRequest}.${SaveRequestName};
import ${package.UpdateRequest}.${UpdateRequestName};
import ${package.DeleteResponse}.${DeleteResponseName};
import ${package.QueryResponse}.${QueryResponseName};
import ${package.SaveResponse}.${SaveResponseName};
import ${package.UpdateResponse}.${UpdateResponseName};
import ${package.Convertor}.${ConvertorName};
import ${package.Manager}.${ManagerName};
import ${package.Service}.${ServiceName};
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
<#if poiServiceImpl??>
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
</#if>
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;/*** <p>* ${table.comment!} 服务实现类* </p>** @author ${author}* @since ${date}*/
@Slf4j
@Service
public class ${ServiceImplName} implements ${ServiceName} {@Resourceprivate ${ManagerName} ${entityLowerCaseFirstOne}Manager;@Resourceprivate ${ConvertorName} ${entityLowerCaseFirstOne}Convertor;/*** 通过主键列表批量删除** @param primaryKeys 主键列表* @return 删除结果*/@Override@Transactional(rollbackFor = Exception.class)public ${DeleteResponseName} deleteByPrimaryKeys(List<${PrimaryKeyType}> primaryKeys) {if (CollUtil.isEmpty(primaryKeys)) {throw new BusinessException(ExceptionCodeEnum.PARAM_NOT_NULL);}// 如果列表很大,考虑分批删除以减轻数据库压力int batchSize = 100; // 每批处理100条int totalDeleted = 0; // 记录总共删除的行数for (int i = 0; i < primaryKeys.size(); i += batchSize) {int toIndex = Math.min(i + batchSize, primaryKeys.size());List<${PrimaryKeyType}> batchKeys = primaryKeys.subList(i, toIndex);int rows = ${entityLowerCaseFirstOne}Manager.deleteBatchByPrimaryKeys(batchKeys);totalDeleted += rows;}${DeleteResponseName} response = new ${DeleteResponseName}();response.setDeleted(totalDeleted > 0 ? DeleteResponse.DELETED_SUCCESS : DeleteResponse.DELETED_FAIL);return response;}/*** 根据业务ID列表删除业务数据。** @param businessIds 业务ID列表,用于删除对应的业务数据。* @return DictionaryDeleteResponse 对象,包含删除结果。* 如果删除成功,deleted字段为{@link DeleteResponse#DELETED_SUCCESS};* 如果删除失败,deleted字段为{@link DeleteResponse#DELETED_FAIL}。*/@Override@Transactional(rollbackFor = Exception.class)public ${DeleteResponseName} deleteByBusinessIds(List<${BusinessIdType}> businessIds) {if (CollUtil.isEmpty(businessIds)) {throw new BusinessException(ExceptionCodeEnum.PARAM_NOT_NULL);}// 如果列表很大,考虑分批删除以减轻数据库压力int batchSize = 100; // 每批处理100条int totalDeleted = 0; // 记录总共删除的行数for (int i = 0; i < businessIds.size(); i += batchSize) {int toIndex = Math.min(i + batchSize, businessIds.size());List<${BusinessIdType}> batchKeys = businessIds.subList(i, toIndex);int rows = ${entityLowerCaseFirstOne}Manager.deleteBatchByBusinessIds(batchKeys, ${EntityName}::get${businessIdNameUpperCaseFirstOne});totalDeleted += rows;}${DeleteResponseName} response = new ${DeleteResponseName}();response.setDeleted(totalDeleted > 0 ? DeleteResponse.DELETED_SUCCESS : DeleteResponse.DELETED_FAIL);return response;}/*** 通过主键查询单个数据** @param primaryKey 主键* @return 单个数据查询响应结果*/@Overridepublic ${QueryResponseName} getOneByPrimaryKey(${PrimaryKeyType} primaryKey) {${BoName} ${entityLowerCaseFirstOne}Bo = ${entityLowerCaseFirstOne}Manager.getOneByPrimaryKey(primaryKey);return ${entityLowerCaseFirstOne}Convertor.entityBoToQueryResponse(${entityLowerCaseFirstOne}Bo);}/*** 通过业务ID查询单个数据** @param businessId 业务ID* @return 单个数据查询响应结果*/@Overridepublic ${QueryResponseName} getOneByBusinessId(${BusinessIdType} businessId) {// 根据业务ID查询${BoName} ${entityLowerCaseFirstOne}Bo = ${entityLowerCaseFirstOne}Manager.getOneByBusinessId(businessId, ${EntityName}::get${businessIdNameUpperCaseFirstOne});return ${entityLowerCaseFirstOne}Convertor.entityBoToQueryResponse(${entityLowerCaseFirstOne}Bo);}/*** 查询信息(分页)** @param request 请求* @return PageResponse 响应*/@Overridepublic PageResponse<${QueryResponseName}> listPages(PageRequest<${QueryRequestName}> request) {try {// 分页查询IPage<${BoName}> ${entityLowerCaseFirstOne}Page = ${entityLowerCaseFirstOne}Manager.page(transformToQuery(request));// 安全地处理分页转换逻辑return safelyConvertPage(${entityLowerCaseFirstOne}Page);} catch (Exception e) {log.error("查询失败", e);throw new BusinessException(ExceptionCodeEnum.SELECT_ERROR);}}/*** 安全地将分页数据转换为响应对象** @param ${entityLowerCaseFirstOne}Page 分页查询结果* @return 分页响应对象*/private PageResponse<${QueryResponseName}> safelyConvertPage(IPage<${BoName}> ${entityLowerCaseFirstOne}Page) {if (${entityLowerCaseFirstOne}Page == null || ${entityLowerCaseFirstOne}Page.getRecords() == null) {return new PageResponse<>();}// 使用并行流进行转换以提高效率,但需确保线程安全List<${QueryResponseName}> responses = ${entityLowerCaseFirstOne}Page.getRecords().parallelStream().map(${entityLowerCaseFirstOne}Bo -> ${entityLowerCaseFirstOne}Convertor.entityBoToQueryResponse(${entityLowerCaseFirstOne}Bo)).collect(Collectors.toList());return PageResponse.convertPage(${entityLowerCaseFirstOne}Page, responses);}/*** 将请求 request 转换成 manager 的 query 对象** @param request 请求参数* @return query 对象*/private PageRequest<${QueryName}> transformToQuery(PageRequest<${QueryRequestName}> request) {${QueryName} ${entityLowerCaseFirstOne}Query = ${entityLowerCaseFirstOne}Convertor.queryRequestToQuery(request.getData());return new PageRequest<>(request.getPageNum(), request.getPageSize(), ${entityLowerCaseFirstOne}Query);}/*** 新增** @param saveRequest 请求入参* @return 响应*/@Override@Transactional(rollbackFor = Exception.class)public ${SaveResponseName} save(${SaveRequestName} saveRequest) {// 校验请求入参ValidationResult validationResult = validateOnSave(saveRequest);if (!validationResult.isValid()) {throw ValidationException.message(validationResult.getErrorMessage());}// 根据入参封装 BO对象${BoName} ${entityLowerCaseFirstOne}Bo = ${entityLowerCaseFirstOne}Convertor.saveRequestToEntityBo(saveRequest);// 数据库操作:插入数据int rows = ${entityLowerCaseFirstOne}Manager.save(${entityLowerCaseFirstOne}Bo);// 处理插入结果boolean saveSuccess = rows > 0;// 新增数据成功,返回结果if (saveSuccess) {// 修改后的数据通过数据库查询返回${BoName} saved${BoName} = ${entityLowerCaseFirstOne}Manager.getOneByBusinessId(${entityLowerCaseFirstOne}Bo.get${businessIdNameUpperCaseFirstOne}(), ${EntityName}::get${businessIdNameUpperCaseFirstOne});return ${entityLowerCaseFirstOne}Convertor.entityBoToSaveResponse(saved${BoName});}// 新增数据失败,抛出异常throw new BusinessException(ExceptionCodeEnum.CREATE_ERROR);}/*** 修改** @param updateRequest 请求* @return 响应*/@Override@Transactional(rollbackFor = Exception.class)public ${UpdateResponseName} update(${UpdateRequestName} updateRequest) {// 校验请求入参ValidationResult validationResult = validateOnUpdate(updateRequest);if (!validationResult.isValid()) {throw ValidationException.message(validationResult.getErrorMessage());}// 根据入参封装 BO对象${BoName} ${entityLowerCaseFirstOne}Bo = ${entityLowerCaseFirstOne}Convertor.updateRequestToEntityBo(updateRequest);// 数据库操作:修改数据int rows = ${entityLowerCaseFirstOne}Manager.updateByBusinessId(${entityLowerCaseFirstOne}Bo, ${entityLowerCaseFirstOne}Bo.get${businessIdNameUpperCaseFirstOne}(), ${EntityName}::get${businessIdNameUpperCaseFirstOne});// 处理修改结果boolean updateSuccess = rows > 0;// 修改数据成功,返回结果if (updateSuccess) {// 修改后的数据通过数据库查询返回${BoName} updated${BoName} = ${entityLowerCaseFirstOne}Manager.getOneByBusinessId(${entityLowerCaseFirstOne}Bo.get${businessIdNameUpperCaseFirstOne}(), ${EntityName}::get${businessIdNameUpperCaseFirstOne});return ${entityLowerCaseFirstOne}Convertor.entityBoToUpdateResponse(updated${BoName});}// 修改数据失败,抛出异常throw new BusinessException(ExceptionCodeEnum.UPDATE_ERROR);}<#if poiServiceImpl??>@Override@Transactional(rollbackFor = Exception.class)public void importExcel(MultipartFile file) {try {// 默认每次会读取100条数据EasyExcel.read(file.getInputStream(), ${ImportDtoName}.class, new PageReadListener<${ImportDtoName}>(importDtos -> {ValidationResult validationResult = validateOnImport(importDtos);if (!validationResult.isValid()) {throw ValidationException.message(validationResult.getErrorMessage());}// 数据转换List<${BoName}> ${entityLowerCaseFirstOne}Bos = ${entityLowerCaseFirstOne}Convertor.importDtoToEntityBo(importDtos);// 保存到数据库${entityLowerCaseFirstOne}Manager.saveBatch(${entityLowerCaseFirstOne}Bos);})).sheet().doRead();} catch (Exception e) {log.warn("上传文件异常 {}", e.getLocalizedMessage(), e);throw new BusinessException(ExceptionCodeEnum.IMPORT_ERROR);}}@Overridepublic void importTpl(HttpServletResponse response) {try {// 设置相应格式response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);response.setCharacterEncoding("utf-8");// URLEncoder.encode 可以防止中文乱码String filename = "${table.comment}_导入模版_" + LocalDateTime.now();filename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=" + filename + ".xlsx");try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ${ImportDtoName}.class).autoCloseStream(false).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();excelWriter.write(Collections.emptyList(), writeSheet);}} catch (Exception e) {log.warn("导出模版异常 {}", e.getLocalizedMessage(), e);throw new RuntimeException(e);}}@Overridepublic void export(HttpServletResponse response, ${QueryRequestName} queryRequest) throws IOException {try {// 设置相应格式response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);response.setCharacterEncoding("utf-8");// 分页请求PageRequest<${QueryRequestName}> pageRequest = new PageRequest<>();pageRequest.setData(queryRequest);pageRequest.setPageSize(500);pageRequest.setPageNum(1);PageRequest<${QueryName}> pageQuery = transformToQuery(pageRequest);// URLEncoder.encode 可以防止中文乱码String filename = "${table.comment}_" + LocalDateTime.now();filename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=" + filename + ".xlsx");try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ${ExportDtoName}.class).autoCloseStream(false).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();long totalPage = 0;while (true) {// 从数据库中读取数据IPage<${BoName}> ${entityLowerCaseFirstOne}Page = ${entityLowerCaseFirstOne}Manager.page(pageQuery);if (${entityLowerCaseFirstOne}Page.getTotal() > 100_000) {throw new BusinessException(ExceptionCodeEnum.EXPORT_TOO_MUCH);}if (${entityLowerCaseFirstOne}Page.getPages() != 0) {totalPage = ${entityLowerCaseFirstOne}Page.getPages();}// 查到的结果为空if (${entityLowerCaseFirstOne}Page.getRecords().isEmpty()) {break;}// 写入到 excel 中List<${ExportDtoName}> exportVos = ${entityLowerCaseFirstOne}Page.getRecords().stream().map(${entityLowerCaseFirstOne}Convertor::entityBoToExportDto).collect(Collectors.toList());excelWriter.write(exportVos, writeSheet);// 没有下一页,跳出循环if (${entityLowerCaseFirstOne}Page.getCurrent() >= totalPage) {break;}// 请求下一页pageRequest.setPageNum(pageRequest.getPageNum() + 1);}}} catch (Exception e) {log.warn("导出文件异常 {}", e.getLocalizedMessage(), e);throw new BusinessException(ExceptionCodeEnum.EXPORT_ERROR);}}</#if>
}
四、代码生成效果展示
运行generator-tool中的GeneratorClient。
生成的文件如下: