文章目录
- 前言
- 正文
- 一、项目结构
- 二、技术点
- 三、部分核心代码
- 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> {
/**
* 业务服务实例
*/
@Resource
private Service service;
/**
* 分页查询
*
* @param request 请求
* @return 分页响应
*/
@Override
public ResultVo<PageResponse<QueryResponse>> listPages(PageRequest<QueryRequest> request) {
request.defaultPage();
return ResultVo.success(service.listPages(request));
}
/**
* 查询详细信息
*
* @param request 请求
* @return 响应
*/
@Override
public ResultVo<QueryResponse> getDetail(IdRequest<BusinessId> request) {
return ResultVo.success(service.getOneByBusinessId(request.getId()));
}
/**
* 新增
*
* @param request 请求
* @return 响应
*/
@Override
public ResultVo<SaveResponse> save(SaveRequest request) {
return ResultVo.success(service.save(request));
}
/**
* 修改
*
* @param request 请求
* @return 响应
*/
@Override
public ResultVo<UpdateResponse> update(UpdateRequest request) {
return ResultVo.success(service.update(request));
}
/**
* 删除
*
* @param request 请求
* @return 响应
*/
@Override
public 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>
extends
QueryService<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> {
@Autowired
protected 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 Transfer
Source Server : pc-密码:root
Source Server Type : MySQL
Source Server Version : 80022
Source Host : localhost:3306
Source Schema : pine_dict
Target Server Type : MySQL
Target Server Version : 80022
File Encoding : 65001
Date: 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>
;
@EnumValue
private 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}> {
@Resource
private ${ConvertorName} ${entityLowerCaseFirstOne}Convertor;
@Override
protected 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;
}
@Override
protected List<${BoName}> createBo(List<${EntityName}> entities) {
if (CollUtil.isEmpty(entities)) {
return Collections.emptyList();
}
return ${entityLowerCaseFirstOne}Convertor.entityToEntityBo(entities);
}
<#if insertBatchData>
/**
* 批量插入数据
*
* @param entityList 实体列表
* @param <E> 实体类型
*/
@Override
public <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} {
@Resource
private ${ManagerName} ${entityLowerCaseFirstOne}Manager;
@Resource
private ${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 单个数据查询响应结果
*/
@Override
public ${QueryResponseName} getOneByPrimaryKey(${PrimaryKeyType} primaryKey) {
${BoName} ${entityLowerCaseFirstOne}Bo = ${entityLowerCaseFirstOne}Manager.getOneByPrimaryKey(primaryKey);
return ${entityLowerCaseFirstOne}Convertor.entityBoToQueryResponse(${entityLowerCaseFirstOne}Bo);
}
/**
* 通过业务ID查询单个数据
*
* @param businessId 业务ID
* @return 单个数据查询响应结果
*/
@Override
public ${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 响应
*/
@Override
public 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);
}
}
@Override
public 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);
}
}
@Override
public 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。
生成的文件如下: