手写一个基于SpringBoot的MVC架构,默认实现CRUD和导入导出功能

文章目录

  • 前言
  • 正文
    • 一、项目结构
    • 二、技术点
    • 三、部分核心代码
      • 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进行业务代码的编写,各位在使用本框架时,可以参考这个项目来实现你自己的功能)

二、技术点

组件/技术点版本备注
Java17
SpringBoot3.2.5
Mybatis-Plus3.5.6整和时需要注意单独引入mybatis-spring(3.0.3)的包
Hutool5.8.27
Lombok1.8.32
easyexcel3.3.4项目中导入导出功能使用easyexcel
knife4f-openapi34.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。
生成的文件如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/766135.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手写一个类似@RequestParam的注解(用来接收请求体的参数)

一、本文解决的痛点 按照大众认为的开发规范&#xff0c;一般post类型的请求参数应该传在请求body里面。但是我们有些post接口只需要传入一个字段&#xff0c;我们接受这种参数就得像下面这样单独创建一个类&#xff0c;类中再添加要传入的基本类型字段&#xff0c;配合Reques…

清爽一夏,羊大师伴你健康运动,引领活力生活!

在这个绚烂多彩的夏日&#xff0c;让我们携手踏上一段清爽与健康并行的旅程。阳光炽热&#xff0c;万物生长&#xff0c;正是释放活力、追求健康的最佳时节。“清爽一夏&#xff0c;健康运动引领活力生活&#xff01;”这不仅是一句口号&#xff0c;更是我们向美好生活发出的诚…

模电-运放的供电

模电-运放的供电 Fang XS.1452512966qq.com如果有错误&#xff0c;希望被指出&#xff0c;学习技术的路难免会磕磕绊绊量的积累引起质的变化注&#xff1a;本文章为唐老师讲电赛视频的个人笔记 运放的供电 所有的运放都可以单电源和双电源供电&#xff1b;采用双电源供电的运…

Linux_fileio实现copy文件

参考韦东山老师教程&#xff1a;https://www.bilibili.com/video/BV1kk4y117Tu?p12 目录 1. 通过read方式copy文件2. 通过mmap映射方式copy文件 1. 通过read方式copy文件 copy文件代码&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <…

012-GeoGebra基础篇-构造圆的切线

前边文章对于基础内容已经悉数覆盖了&#xff0c;这一篇我就不放具体的细节&#xff0c;若有需要可以复刻一下 目录 一、成品展示二、算式内容三、正确性检查五、文章最后 一、成品展示 二、算式内容 A(0,0) B(3,0) c: Circle(A,B) C(5,4) sSegment(A,C) DMidpoint(s) d: Circ…

Java日期时间

java.util包提供Date类&#xff0c;该类有两个构造函数&#xff1a; Date()使用当前日期和时间初始化对象Date(long millisec)从1970年1月1日起的毫秒数 获取当前日期时间 import java.util.Date;public class DateDemo{public static void main(String[] args){//初始化Date…

开发笔记:vue3+ts+vant 卡片数据分页,下拉加载,卡片左滑可删除

效果&#xff1a; 实现 使用vantui组件 van-swipe-cell van-card &#xff08;商品卡片&#xff09; 核心代码 const currentPage ref(1) const pageSize ref(4) const totalSize ref(10) const loading ref(false) const finished ref(false) const refreshing ref(…

Java 微信小程序自建平台开发票保存到微信卡包

Java 微信小程序自建平台开发票保存到微信卡包 1 获取Access token2 获取自身的开票平台识别码3 设置商户联系方式4 获取授权页ticket5 获取授权页链接6 小程序打开授权页7 收取授权完成事件推送8 创建发票卡券模板9 上传PDF10 将电子发票卡券插入用户卡包 1 获取Access token …

shopify后台设置为中文

shopify官网&#xff1a;https://link.juejin.cn/?targethttps%3A%2F%2Fshopify.dev%2F 1、点击右上角头像 2、选择个人资料 3、找到Language设置 开发者 1、创建开发商店 2、 3、点击左侧导航在线商店&#xff0c;点击右上角查看你的商店。在线商店链接为https://[商店名…

RK3568驱动指南|第十五篇 I2C-第181章使用GPIO模拟I2C驱动

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

阿里云再次突发故障,高可用形同虚设?

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

轻松拯救手机数据,数据恢复软件推荐这8款!

在现代生活中&#xff0c;手机已成为我们不可或缺的工具&#xff0c;承载着大量重要的个人和工作数据。然而&#xff0c;意外删除、系统崩溃、设备损坏等情况可能导致数据丢失&#xff0c;给我们带来极大的困扰。幸运的是&#xff0c;随着科技的发展&#xff0c;各种手机数据恢…

文生图功能介绍

Stable Diffusion WebUI&#xff08;SD WebUI&#xff09;及文生图功能介绍 一、引言 随着人工智能技术的飞速发展&#xff0c;AI绘画作为一种新兴的艺术形式&#xff0c;逐渐走入人们的视野。Stable Diffusion WebUI&#xff08;简称SD WebUI&#xff09;作为AI绘画领域的重…

[附源码]最新springboot线上电商|前后端分离|界面简洁

一. 前言 今天小编给大家带来了一款可学习&#xff0c;可商用的&#xff0c;线上电商的网站源码&#xff0c;支持二开&#xff0c;无加密。代码的后端是SpringBoot技术栈&#xff08;非jsp&#xff09;&#xff0c;前端是Angular。如果您需要定制需求请找小编。 文章第六小节…

英灵神殿mac能玩吗 英灵神殿对电脑配置要求《英灵神殿》新手攻略查询 PD虚拟机能玩英灵神殿吗

近年来&#xff0c;随着《英灵神殿》&#xff08;Valheim&#xff09;游戏的火热&#xff0c;越来越多的玩家被其独特的北欧神话题材和丰富的生存挑战所吸引。然而&#xff0c;对于Mac用户来说&#xff0c;如何在Mac平台上运行这款游戏可能是一个问题。此外&#xff0c;作为一名…

编译原理3-自底向上的语法分析

自底向上分析 &#xff0c;就是自左至右扫描输入串&#xff0c;自底向上进 行分析&#xff1b;通过反复查找当前句型的 句柄&#xff0c; 并使 用产生式规则 将找到的句柄归约为相应的非终结符 。逐步进行“ 归约 ”&#xff0c;直到至文法的开始符号&#xff1b; 对于规范推导…

【unity实战】在Unity中使用有限状态机制作一个敌人AI

最终效果 文章目录 最终效果前言有限状态机的主要作用和意义素材下载逻辑图敌人动画配置优雅的代码文件目录状态机代码定义敌人不同状态切换创建敌人效果更多的敌人参考源码完结 前言 有限状态机以前的我嗤之以鼻&#xff0c;现在的我逐帧分析。其实之前我就了解过有限状态机&…

day03-主页模块-修改密码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.获取用户资料在Vuex中共享登录成功跳转到主页之后&#xff0c;可以获取用户资料&#xff0c;获取的资料在Vuex中共享&#xff0c;这样用户就可以很方便的获取该信…

Leetcode刷题笔记 | 二叉树基本性质 | 一天的题量 | 5道题目 | 深度优先搜索 | 广度优先搜索 | 递归 | 遍历

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本期毛毛张分享的是LeetCode关于二叉树&#x1f332;的性质的一些基础题&#xff0c;做这些题目的本质还是遍历二叉树&#x1f3c3;‍➡️的过程&#…

计算机组成原理 | 储存子系统(1)概述

三级储存体系 物理与虚拟存储器 &#xff08;抽象逻辑模型&#xff09; 存储器类型 存储器的速度指标