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

news2024/11/24 19:55:17

文章目录

  • 前言
  • 正文
    • 一、项目结构
    • 二、技术点
    • 三、部分核心代码
      • 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.coloradmin.cn/o/1876451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文心一言 VS 讯飞星火 VS chatgpt (290)-- 算法导论21.3 3题

三、给出一个包含 m 个 MAKE-SET 、UNION 和 FINDSET 操作的序列(其中有 n 个是 MAKE-SET 操作)&#xff0c;当仅使用按秩合并时&#xff0c;需要 Ω(mlgn) 的时间。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 在并查集&#xff08;Union-Find&#xff09;数…

STM32使用PWM驱动WS2812_RGB灯珠

项目场景&#xff1a; 使用STM32标准库产生PWM实现RGB灯珠控制。 芯片型号&#xff1a;stm32f405rgt6 设计优点&#xff1a;不需要使用定时器中断资源&#xff0c;可以使得STM32在驱动RGB灯珠的同时能够执行其他任务。 RGB灯珠简介 项目所使用的RGB灯珠如下所示&#xff0c;封装…

76. UE5 RPG 实现场景阻挡剔除功能

在俯视角游戏中&#xff0c;我们总会碰到一个问题就是&#xff0c;建筑会遮挡住角色的问题。遇到这种问题有多种解决方案&#xff0c;厂商经常使用的一种方案是&#xff0c;如果角色被遮挡&#xff0c;则使用一种纯色或者增加一些菲涅尔的效果来实现 这种效果我之前在unity内实…

免费使用文心一言会员教程

领取&安装链接&#xff1a;Baidu Comate 领取季卡 有图有真相 原理&#xff1a;百度comate使用文心一言最新的4.0模型。百度comate目前免费使用&#xff0c;可以借助comate达到免费使用4.0模型目的。 如何获得 点击「Baidu Comate 领取季卡 -> 领取权益」&#xff0…

Cesium Model 中的剪裁平面 (ClippingPlane)

Cesium Model 中的剪裁平面 (ClippingPlane) 参考: https://www.cnblogs.com/webgl-angela/p/9197672.html Cesium Model 中的剪裁平面 (ClippingPlane) // 相关类: class ClippingPlaneCollection {} class ClippingPlane {}// 剪裁的整体流程: Model.prototype.update () …

Mathematica训练课(45)-- 一些常用的函数Abs[],Max[]等函数用法

①绝对值函数&#xff1a;Abs[]函数 ②最大值和最小值函数 ③反函数

SAP ATP可用性检查简介

Availability Check,就是可用性检查,指的是要检查一下此物料是否能满足我的需求。 接到一张销售订单(SALES ORDER),客户要求数量为100PC,并且客户要求的出货日期是2024-07-01,此时我们的销售人员肯定会想到底能否出货给客人呢?系统中建立此单时,SAP就会做一个所谓的检…

实验八 T_SQL编程

题目 以电子商务系统数据库ecommerce为例 1、在ecommerce数据库&#xff0c;针对会员表member首先创建一个“呼和浩特地区”会员的视图view_hohhot&#xff0c;然后通过该视图查询来自“呼和浩特”地区的会员信息&#xff0c;用批处理命令语句将问题进行分割&#xff0c;并分…

17859划分准则小结

17859《划分准则》 发布时间&#xff1a;1999.9.13 实施时间&#xff1a;2001.1.1 计算机信息系统安全保护能力的五个等级&#xff1a; 第一级&#xff1a;用户自主保护级 第二级…

Java知识点整理 15 — MyBatis框架

一. 什么是 MyBatis MyBatis 是一款优秀的持久层框架&#xff0c;支持自定义 SQL、存储过程以及高级映射。它免除了几乎所有 JDBC代码以及手动设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff08;普通老式 Jav…

人脑网络的多层建模与分析

摘要 了解人类大脑的结构及其与功能的关系&#xff0c;对于各种应用至关重要&#xff0c;包括但不限于预防、处理和治疗脑部疾病(如阿尔茨海默病或帕金森病)&#xff0c;以及精神疾病(如精神分裂症)的新方法。结构和功能神经影像学方面的最新进展&#xff0c;以及计算机科学等…

决定佛蒙特州版图的关键历史事件:

​决定佛蒙特州版图的关键历史事件: 1. 早期探险与命名&#xff1a; - 1609年&#xff0c;法国探险家萨缪尔德尚普兰&#xff08;Samuel de Champlain&#xff09;到达了现在的佛蒙特州区域&#xff0c;并探索了尚普兰湖&#xff08;Lake Champlain&#xff09;。他将周围的山…

mwwz库添加对多模板匹配的支持:find_shape_models

多模板匹配的实现只需要对单模板匹配做一些扩展&#xff0c;传入的模板由不同的id表示&#xff0c;在金字塔顶层完成模板的分类&#xff0c;在剩下的金字塔完成对每一类模板的匹配&#xff0c;匹配结果由id标识。测试程序已集成该方法&#xff0c;清除模板后所创建的模板被看作…

Python基于决策树分类模型、支持向量机分类模型、随机森林分类模型和XGBoost分类模型实现月亮数据标签预测项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 在探索机器学习算法的性能与适用性时&#xff0c;我们往往需要依赖于精心设计的人工数据集来测试和验证…

全面对标GPT-4 Turbo,讯飞星火V4.0凭什么?

大数据产业创新服务媒体 ——聚焦数据 改变商业 自从ChatGPT爆火出圈之后&#xff0c;大模型就走上了发展的快车道。 一方面&#xff0c;大模型技术快速演进&#xff0c;Sora为我们打开了视频生成的想象空间&#xff0c;各大厂商争相打破大模型的“模态墙”&#xff0c;长文本…

pytest中的极其重要固件(request)的理解

pytest 是一个非常流行的Python测试框架&#xff0c;它为开发人员提供了丰寴的测试工具和功能。 在pytest中&#xff0c;固件&#xff08;fixture&#xff09;是一种非常核心的概念&#xff0c;用于设置测试前的预条件&#xff0c;清理测试后的环境&#xff0c;或者提供测试过…

什么是DEQ?

DEQ (Delivered Ex Quay, Duty Paid) 是指目的港码头交货 (……指定目的港)。 这种术语规定卖方在指定目的港码头将货物交给买方处置&#xff0c;并且不办理进口清关手续。 DEQ适用范围 DEQ术语仅适用于海运、内河运输或多式联运&#xff0c;并且在目的港码头卸货时使用。如…

RAG 基本流程及处理技巧 with LangChain

LLM 主要存在两个问题&#xff1a;幻想和缺乏领域知识。领域知识缺乏的原因是因为训练 LLM 本身的知识更新慢&#xff0c;对特定领域的知识也没有太细致的输入。 RAG 主要是解决 LLM 缺乏领域知识的问题。底层的逻辑是&#xff1a;把 LLM 作为逻辑推理引擎&#xff0c;而不是信…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验10 IPv4地址 — 构造超网(无分类编址)

一、实验目的 1.加深对构造超网的理解&#xff1b; 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.根据各网络所指定的地址块完成以下工作&#…

Python 面试【中级】

欢迎莅临我的博客 &#x1f49d;&#x1f49d;&#x1f49d;&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…