大大的RY专题

52 阅读10分钟

修改器下载

gitee.com/lpf_project…

选择压缩包,配置修改内容,开始执行。会输出一个文件

image.png

Swagger

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务

它的主要作用有:

  • 使得前后端分离开发更加方便,有利于团队协作
  • 在线自动生成接口文档,降低后端开发人员编写接口文档的负担
  • 功能测试

Spring已经将Swagger纳入自身的标准,建立了Spring-Swagger项目,现在叫Springfox。通过在项目中引入Springfox ,就可以非常简单快捷的使用Swagger啦。

<!-- swagger3-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
</dependency>

<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.6.2</version>
</dependency>

核心配置类

/**
 * Swagger2的接口配置
 */
@Configuration
public class SwaggerConfig {
    /**
     * 系统基础配置
     */
    @Autowired
    private RuoYiConfig ruoyiConfig;

    /**
     * 是否开启swagger
     */
    @Value("${swagger.enabled}")
    private boolean enabled;

    /**
     * 设置请求的统一前缀
     */
    @Value("${swagger.pathMapping}")
    private String pathMapping;

    /**
     * 创建API
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.OAS_30)
                // 是否启用Swagger
                .enable(enabled)
                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
                .apiInfo(apiInfo())
                // 设置哪些接口暴露给Swagger展示
                .select()
                // 扫描所有有注解的api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 扫描指定包中的swagger注解
                // .apis(RequestHandlerSelectors.basePackage("com.kzzyl.project.tool.swagger"))
                // 扫描所有 .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                /* 设置安全模式,swagger可以设置访问token */
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts())
                .pathMapping(pathMapping);
    }

    /**
     * 安全模式,这里指定token通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeyList = new ArrayList<>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
        return apiKeyList;
    }

    /**
     * 安全上下文
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .operationSelector(o ->
                                o.requestMappingPattern().matches("/.*"))
                        .build());
        return securityContexts;
    }

    /**
     * 默认的安全上引用
     */
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global",
                "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo() {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("标题:若依管理系统_接口文档")
                // 描述
                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
                // 作者信息
                .contact(new Contact(ruoyiConfig.getName(), null, null))
                // 版本
                .version("版本号:" + ruoyiConfig.getVersion())
                .build();
    }
}

集成knife4j

如果不习惯使用swagger可以使用前端UI的增强解决方案knife4j,对比swagger相比有以下优势,友好界面,离线文档,接口排序,安全控制,在线调试,文档清晰,注解增强,容易上手。 目前,企业项目中一般都使用knife4j框架。

需要移除依赖,springfox-boot-starter 和 swagger-models

<!-- SpringBoot2 使用依赖 -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

前端修改 ry-ui\views\tool\swagger\index.vue 跳转地址,如下

const url = ref(import.meta.env.VITE_APP_BASE_API + "/doc.html")

注解参数

// 作用到类上描述,标题
@Api(value = "护理项目管理", tags = "护理项目管理")
// 作用到方法上,描述具体的接口
@ApiOperation("查询护理项目列表")
// 作用到方法的参数上,描述参数信息
@ApiParam("查询条件对象")
// 作用到实体类上
@ApiModel("Entity基类")
// 作用到实体类的成员变量上
@ApiModelProperty("搜索值")

Velocity模块引擎

Velocity是一个基于Java的模板引擎,它可以通过特定的语法获取java对象的数据 , 填充到模板中,从而实现界面和java代码的分离。

image.png

常见的应用场景:

  • Web应用程序 : 作为应用程序的视图, 展示数据。
  • 源代码生成 : Velocity可以基于模板生成Java源代码。
  • 自动电子邮件 : 网站注册, 认证等的电子邮件模板。
  • 网页静态化 : 基于velocity模板生成静态网页。

案例

//TODO: 1.初始化velocity
Properties p = new Properties();
// 加载classpath目录下的vm文件
p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
// 定义字符集
p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8);
// 初始化Velocity引擎,指定配置Properties
Velocity.init(p);

// TODO: 2.创建Velocity上下文对象
VelocityContext context = new VelocityContext();
context.put("message", "加油朋友");

// TODO: 3.获取模板
Template template = Velocity.getTemplate("vms/index.html.vm", Constants.UTF8);
// 准备输出流,将文件写出
FileWriter fileWriter = new FileWriter("E:\\黑马ai\\kzzyl\\kzzyl-generator\\src\\main\\resources\\vms\\index.html");
// TODO: 4.合并模板和数据模型
template.merge(context, fileWriter);

fileWriter.close();

模板如下:使用 ${message} 传递 message 变量

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>随便写点数据吧--${message}</h3>
</body>
</html>

Velocity语法解释

  • ${variable}: 表示插入变量值。
  • #foreach 和 #end: 循环结构,用于遍历列表。
  • #if 和 #end: 条件判断结构。
  • #set: 设置变量。
  • #elseif: 条件分支。

集成 MybatisPlus

在 common 下引入

<!-- MyBatis-Plus 增强CRUD -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

删除 mybatis-config.xml 文件

# MyBatisPlus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.kzzyl.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  global-config:
    db-config:
      id-type: auto
  configuration:
    # 开启驼峰命名规则
    map-underscore-to-camel-case: true

删除 MyBatisConfig 文件,替换成 MyBatisPlusConfig 如下


/**
 * Mybatis Plus 配置
 */
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        // 乐观锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        // 阻断插件
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        return interceptor;
    }

    /**
     * 分页插件,自动识别数据库类型
     */
    public PaginationInnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        // 设置数据库类型为mysql
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        paginationInnerInterceptor.setMaxLimit(-1L);
        return paginationInnerInterceptor;
    }

    /**
     * 乐观锁插件
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }

    /**
     * 如果是对全表的删除或更新操作,就会终止该操作
     */
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
        return new BlockAttackInnerInterceptor();
    }

}

注意事项:

  1. 需要在 BaseEntity 里 params和searchValue 需要设置 @TableField(exist = false)

方便直接拷贝

package com.kzzyl.common.core.domain;

import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author Kun
 */
@Data
@ApiModel("Entity基类")
public class BaseEntity implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    @JsonIgnore
    @ApiModelProperty("搜索值")
    @TableField(exist = false)
    private String searchValue;

    @ApiModelProperty("创建者")
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty("创建时间")
    private Date createTime;

    @ApiModelProperty("更新者")
    @TableField(fill = FieldFill.UPDATE)
    private String updateBy;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.UPDATE)
    @ApiModelProperty("更新时间")
    private Date updateTime;

    /**
     * 备注
     */
    @ApiModelProperty("备注")
    private String remark;

    /**
     * 请求参数
     */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @TableField(exist = false)
    @ApiModelProperty("请求参数")
    private Map<String, Object> params;

    public Map<String, Object> getParams() {
        if (params == null) {
            params = new HashMap<>();
        }
        return params;
    }

}

MybatisPlus 自动填充

package com.kzzyl.framework.interceptor;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.kzzyl.common.utils.DateUtils;
import com.kzzyl.common.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class BaseEntityHandle implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createBy", String.class, SecurityUtils.getUserId().toString());
        this.strictInsertFill(metaObject, "createTime", Date.class, DateUtils.getNowDate());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateBy", SecurityUtils.getUserId().toString(), metaObject);
        this.setFieldValByName("updateTime", DateUtils.getNowDate(), metaObject);
    }

}

改造 controller 层

代码模板在下面,这里只说一下需要调整的内置代码

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serial;
import java.io.Serializable;
import java.util.List;

/**
 * 表格分页数据对象
 */
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
    
    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 总记录数
     */
    private long total;

    /**
     * 列表数据
     */
    private List<T> rows;

    /**
     * 消息状态码
     */
    private int code;

    /**
     * 消息内容
     */
    private String msg;

    /**
     * 分页
     *
     * @param list  列表数据
     * @param total 总记录数
     */
    public TableDataInfo(List<T> list, long total) {
        this.rows = list;
        this.total = total;
    }

}

还有 BaseController 中的下面方法

/**
 * 响应请求分页数据
 */
protected <T> TableDataInfo<T> getDataTable(List<T> list) {
    TableDataInfo<T> rspData = new TableDataInfo<>();
    rspData.setCode(HttpStatus.SUCCESS);
    rspData.setMsg("查询成功");
    rspData.setRows(list);
    rspData.setTotal(new PageInfo<>(list).getTotal());
    return rspData;
}

修改时间类型为 LocalDate

前端在 views/tool/editTable 文件中如下图位置。新增上 LocalDateTime

image.png

Java

// src/main/java/com/kzzyl/common/constant/GenConstants.java
/** 时间类型 */
public static final String TYPE_LOCAL_DATE_TIME = "LocalDateTime";

// src/main/java/com/kzzyl/generator/util/VelocityUtils.java
else if (!column.isSuperColumn() && GenConstants.TYPE_LOCAL_DATE_TIME.equals(column.getJavaType())) {
    importList.add("java.time.LocalDateTime");
    importList.add("com.fasterxml.jackson.annotation.JsonFormat");
}

image.png

初始化字段对应关系修改

// 在 src/main/java/com/kzzyl/generator/util/GenUtils.java
else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10
        || GenConstants.MYSQL_TINYINT.equals(column.getColumnType()) ||
        GenConstants.MYSQL_INT.equals(column.getColumnType())) {
    // 修改为Integer
    column.setJavaType(GenConstants.TYPE_INTEGER);
}
// ---------------
else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
    // 如果是时间类型 则生成LocalDateTime类型
    column.setJavaType(GenConstants.TYPE_LOCAL_DATE_TIME);
    column.setHtmlType(GenConstants.HTML_DATETIME);
}

// src/main/java/com/kzzyl/common/constant/GenConstants.java
/** MySql tinyint 类型 */
public static final String MYSQL_TINYINT = "tinyint";

/** MySql int 类型 */
public static final String MYSQL_INT = "int";

代码生成模板

controller.java.vm

package ${packageName}.controller;

import java.util.List;
import javax.servlet.http.HttpServletResponse;

import lombok.AllArgsConstructor;
import io.swagger.annotations.Api;
import com.kzzyl.common.core.domain.R;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kzzyl.common.annotation.Log;
import com.kzzyl.common.core.controller.BaseController;
import com.kzzyl.common.core.domain.AjaxResult;
import com.kzzyl.common.enums.BusinessType;
import ${packageName}.domain.${ClassName};
import ${packageName}.service.I${ClassName}Service;
import com.kzzyl.common.utils.poi.ExcelUtil;
#if($table.crud || $table.sub)
import com.kzzyl.common.core.page.TableDataInfo;
#elseif($table.tree)
#end

/**
 * ${functionName}Controller
 * 
 * @author ${author}
 * date ${datetime}
 */
@RestController
@AllArgsConstructor
@RequestMapping("/${moduleName}/${businessName}")
@Api(value = "${functionName}管理", tags = "${functionName}管理")
public class ${ClassName}Controller extends BaseController {

    private final I${ClassName}Service ${className}Service;

    @ApiOperation("查询${functionName}集合")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')")
    @GetMapping("/all")
    public R<List<${ClassName}>> ${className}All (${ClassName} ${className}) {
        return R.ok(${className}Service.list());
    }

    /**
     * 查询${functionName}列表
     */
    @ApiOperation("查询${functionName}列表")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')")
    @GetMapping("/list")
#if($table.crud || $table.sub)
    public TableDataInfo<${ClassName}> list(${ClassName} ${className}) {
        startPage();
        return getDataTable(${className}Service.select${ClassName}List(${className}));
    }
#elseif($table.tree)
    public AjaxResult list(${ClassName} ${className})
    {
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        return success(list);
    }
#end

    /**
     * 导出${functionName}列表
     */
    @ApiOperation("导出${functionName}列表")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')")
    @Log(title = "${functionName}", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ${ClassName} ${className}) {
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        ExcelUtil<${ClassName}> util = new ExcelUtil<>(${ClassName}.class);
        util.exportExcel(response, list, "${functionName}数据");
    }

    /**
     * 获取${functionName}详细信息
     */
    @ApiOperation("获取${functionName}列表")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')")
    @GetMapping("/{${pkColumn.javaField}}")
    public R<${ClassName}> getInfo(@PathVariable @ApiParam("唯一ID") ${pkColumn.javaType} ${pkColumn.javaField}) {
        return R.ok(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}));
    }

    /**
     * 新增${functionName}
     */
    @ApiOperation("新增${functionName}列表")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')")
    @Log(title = "${functionName}", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody ${ClassName} ${className}) {
        return toAjax(${className}Service.insert${ClassName}(${className}));
    }

    /**
     * 修改${functionName}
     */
    @ApiOperation("修改${functionName}列表")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')")
    @Log(title = "${functionName}", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody ${ClassName} ${className}) {
        return toAjax(${className}Service.update${ClassName}(${className}));
    }

    /**
     * 删除${functionName}
     */
    @ApiOperation("删除${functionName}列表")
    @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')")
    @Log(title = "${functionName}", businessType = BusinessType.DELETE)
    @DeleteMapping("/{${pkColumn.javaField}s}")
    public AjaxResult remove(@PathVariable @ApiParam("唯一ID集合") ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
        return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s));
    }
}

domain.java.vm

package ${packageName}.domain;

import lombok.*;

import java.io.Serial;
#foreach ($import in $importList)
import ${import};
#end

import io.swagger.annotations.ApiModel;
import com.kzzyl.common.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
#if($table.crud || $table.sub)
import com.kzzyl.common.core.domain.BaseEntity;
#elseif($table.tree)
import com.kzzyl.common.core.domain.TreeEntity;
#end

/**
 * @author ${author}
 * date ${datetime}
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
#if($table.crud || $table.sub)
#set($Entity="BaseEntity")
#elseif($table.tree)
#set($Entity="TreeEntity")
#end
@EqualsAndHashCode(callSuper = true)
@ApiModel(description = "${functionName}")
public class ${ClassName} extends ${Entity} {

    @Serial
    private static final long serialVersionUID = 1L;

#foreach ($column in $columns)
#if(!$table.isSuperColumn($column.javaField))
#if($column.list)
#set($parentheseIndex=$column.columnComment.indexOf("("))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($parentheseIndex != -1)
    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
#elseif($column.javaType == 'Date' || $column.javaType == 'LocalDateTime')
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
#else
    @Excel(name = "${comment}")
#end
#end
    @ApiModelProperty("${column.columnComment}")
    private $column.javaType $column.javaField;

#end
#end
#if($table.sub)
    /** $table.subTable.functionName信息 */
    private List<${subClassName}> ${subclassName}List;
#end
#if($table.sub)
    public List<${subClassName}> get${subClassName}List()
    {
        return ${subclassName}List;
    }

    public void set${subClassName}List(List<${subClassName}> ${subclassName}List)
    {
        this.${subclassName}List = ${subclassName}List;
    }
#end
}

mapper.java.vm

package ${packageName}.mapper;

import java.util.List;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import ${packageName}.domain.${ClassName};
#if($table.sub)
import ${packageName}.domain.${subClassName};
#end

/**
 * ${functionName}Mapper接口
 * 
 * @author ${author}
 * date ${datetime}
 */
public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> {

    /**
     * 查询${functionName}
     * 
     * @param ${pkColumn.javaField} ${functionName}主键
     */
    ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});

    /**
     * 查询${functionName}列表
     * 
     * @param ${className} ${functionName}
     */
    List<${ClassName}> select${ClassName}List(${ClassName} ${className});

#if($table.sub)

    /**
     * 批量删除${subTable.functionName}
     * 
     * @param ${pkColumn.javaField}s 需要删除的数据主键集合
     */
    int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);
    
    /**
     * 批量新增${subTable.functionName}
     * 
     * @param ${subclassName}List ${subTable.functionName}列表
     */
    int batch${subClassName}(List<${subClassName}> ${subclassName}List);
    

    /**
     * 通过${functionName}主键删除${subTable.functionName}信息
     * 
     * @param ${pkColumn.javaField} ${functionName}ID
     */
    int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField});
#end
}

service.java.vm

package ${packageName}.service;

import java.util.List;

import com.baomidou.mybatisplus.extension.service.IService;
import ${packageName}.domain.${ClassName};

/**
 * ${functionName}Service接口
 * 
 * @author ${author}
 * date ${datetime}
 */
public interface I${ClassName}Service extends IService<${ClassName}> {

    /**
     * 查询${functionName}
     * 
     * @param ${pkColumn.javaField} ${functionName}主键
     */
    ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField});

    /**
     * 查询${functionName}列表
     * 
     * @param ${className} ${functionName}
     */
    List<${ClassName}> select${ClassName}List(${ClassName} ${className});

    /**
     * 新增${functionName}
     * 
     * @param ${className} ${functionName}
     */
    boolean insert${ClassName}(${ClassName} ${className});

    /**
     * 修改${functionName}
     * 
     * @param ${className} ${functionName}
     */
    boolean update${ClassName}(${ClassName} ${className});

    /**
     * 批量删除${functionName}
     * 
     * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合
     */
    boolean delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s);

    /**
     * 删除${functionName}
     */
    void delete${ClassName}(List<Long> ids);

    /**
     * 删除护理员老人关联
     */
    void remove${ClassName}(${ClassName} ${className});
}

serviceImpl.java.vm

package ${packageName}.service.impl;

import java.util.Arrays;
import java.util.List;

import org.springframework.stereotype.Service;
#if($table.sub)
import java.util.ArrayList;
import com.kzzyl.common.utils.StringUtils;
import ${packageName}.domain.${subClassName};
#end
import ${packageName}.mapper.${ClassName}Mapper;
import ${packageName}.domain.${ClassName};
import ${packageName}.service.I${ClassName}Service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

/**
 * ${functionName}Service业务层处理
 * 
 * @author ${author}
 * date ${datetime}
 */
@Service
public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}>
        implements I${ClassName}Service {

    /**
     * 查询${functionName}
     *
     * @param ${pkColumn.javaField} ${functionName}主键
     */
    @Override
    public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) {
        return baseMapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField});
    }

    /**
     * 查询${functionName}列表
     *
     * @param ${className} ${functionName}
     */
    @Override
    public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) {
        return baseMapper.select${ClassName}List(${className});
    }

    /**
     * 新增${functionName}
     *
     * @param ${className} ${functionName}
     */
#if($table.sub)
    @Transactional
#end
    @Override
    public boolean insert${ClassName}(${ClassName} ${className}) {
#if($table.sub)
        int rows = ${className}Mapper.insert${ClassName}(${className});
        insert${subClassName}(${className});
        return rows;
#else
        return save(${className});
#end
    }

    /**
     * 修改${functionName}
     * 
     * @param ${className} ${functionName}
     */
#if($table.sub)
    @Transactional
#end
    @Override
    public boolean update${ClassName}(${ClassName} ${className}) {
#if($table.sub)
        ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}());
        insert${subClassName}(${className});
#end
        return updateById(${className});
    }

    /**
     * 批量删除${functionName}
     *
     * @param ${pkColumn.javaField}s 需要删除的${functionName}主键
     */
    @Transactional
    @Override
    public boolean delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) {
#if($table.sub)
        ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s);
#end
        return removeBatchByIds(Arrays.asList(${pkColumn.javaField}s));
    }

    /**
     * 删除${functionName}
     */
    @Override
    public void delete${ClassName}(List<Long> ids) {
        boolean remove = remove(Wrappers.<${ClassName}>lambdaQuery());
        if(!remove) {
            throw new RuntimeException("${ClassName} 删除失败");
        }
    }

    /**
     * 删除护理员老人关联
     */
    @Override
    public void remove${ClassName}(${ClassName} ${className}) {
        boolean remove = remove(Wrappers.<${ClassName}>lambdaQuery());
        if (!remove) {
            throw new RuntimeException("${ClassName} 删除失败");
        }
    }


#if($table.sub)

    /**
     * 新增${subTable.functionName}信息
     * 
     * @param ${className} ${functionName}对象
     */
    public void insert${subClassName}(${ClassName} ${className})
    {
        List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List();
        ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}();
        if (StringUtils.isNotNull(${subclassName}List))
        {
            List<${subClassName}> list = new ArrayList<${subClassName}>();
            for (${subClassName} ${subclassName} : ${subclassName}List)
            {
                ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField});
                list.add(${subclassName});
            }
            if (list.size() > 0)
            {
                ${className}Mapper.batch${subClassName}(list);
            }
        }
    }
#end
}

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="${packageName}.mapper.${ClassName}Mapper">
    
    <resultMap type="${ClassName}" id="${ClassName}Result">
#foreach ($column in $columns)
        <result property="${column.javaField}"    column="${column.columnName}"    />
#end
    </resultMap>
#if($table.sub)

    <resultMap id="${ClassName}${subClassName}Result" type="${ClassName}" extends="${ClassName}Result">
        <collection property="${subclassName}List" ofType="${subClassName}" column="${pkColumn.columnName}" select="select${subClassName}List" />
    </resultMap>

    <resultMap type="${subClassName}" id="${subClassName}Result">
#foreach ($column in $subTable.columns)
        <result property="${column.javaField}"    column="${column.columnName}"    />
#end
    </resultMap>
#end

    <sql id="select${ClassName}Vo">
        select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName}
    </sql>

    <select id="select${ClassName}List" parameterType="${ClassName}" resultMap="${ClassName}Result">
        <include refid="select${ClassName}Vo"/>
        <where>  
#foreach($column in $columns)
#set($queryType=$column.queryType)
#set($javaField=$column.javaField)
#set($javaType=$column.javaType)
#set($columnName=$column.columnName)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.query)
#if($column.queryType == "EQ")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName = #{$javaField}</if>
#elseif($queryType == "NE")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName != #{$javaField}</if>
#elseif($queryType == "GT")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &gt; #{$javaField}</if>
#elseif($queryType == "GTE")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &gt;= #{$javaField}</if>
#elseif($queryType == "LT")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &lt; #{$javaField}</if>
#elseif($queryType == "LTE")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName &lt;= #{$javaField}</if>
#elseif($queryType == "LIKE")
            <if test="$javaField != null #if($javaType == 'String' ) and $javaField.trim() != ''#end"> and $columnName like concat('%', #{$javaField}, '%')</if>
#elseif($queryType == "BETWEEN")
            <if test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''"> and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}</if>
#end
#end
#end
        </where>
    </select>
    
    <select id="select${ClassName}By${pkColumn.capJavaField}" parameterType="${pkColumn.javaType}" resultMap="#if($table.sub)${ClassName}${subClassName}Result#else${ClassName}Result#end">
#if($table.crud || $table.tree)
        <include refid="select${ClassName}Vo"/>
        where ${pkColumn.columnName} = #{${pkColumn.javaField}}
#elseif($table.sub)
        select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end
        from ${tableName}
        where ${pkColumn.columnName} = #{${pkColumn.javaField}}
#end
    </select>
#if($table.sub)

    <select id="select${subClassName}List" resultMap="${subClassName}Result">
        select#foreach ($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end
        from ${subTableName}
        where ${subTableFkName} = #{${subTableFkName}}
    </select>
#end

#if($table.sub)
    
    <delete id="delete${subClassName}By${subTableFkClassName}s" parameterType="String">
        delete from ${subTableName} where ${subTableFkName} in
        <foreach item="${subTableFkclassName}" collection="array" open="(" separator="," close=")">
            #{${subTableFkclassName}}
        </foreach>
    </delete>

    <delete id="delete${subClassName}By${subTableFkClassName}" parameterType="${pkColumn.javaType}">
        delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}}
    </delete>

    <insert id="batch${subClassName}">
        insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values
        <foreach item="item" index="index" collection="list" separator=",">
            (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end)
        </foreach>
    </insert>
#end
</mapper>

前端模板

在 src/main/java/com/kzzyl/generator/util/VelocityUtils.java 文件下修改如下图

image.png

继续修改获取文件名方法

else if (template.contains("hooks.js.vm")) {
    fileName = StringUtils.format("{}/hooks/{}/use{}.js", vuePath, moduleName, businessName);
}

image.png

hooks

import { ${businessName}All } from '@/api/${moduleName}/${ClassName}/index.js'

export function use${ClassName}(params) {
    const loading = ref(false)
    const ${className}List = ref([])
    const get${ClassName}List = async () => {
        loading.value = true
        const {data} = await ${businessName}All(params)
        ${className}List.value = data.map(item => ({
            label: item.name,
            value: item.id
        }))
        loading.value = false
    }
    return {${className}List, get${ClassName}List}
}

API

import request from '@/utils/request'

const baseUrl = '/${moduleName}/${businessName}'

// 查询${functionName}
export function ${businessName}All(params) {
    return request.get(`#[[$]]#{baseUrl}/all`, {params})
}

// 查询${functionName}列表
export function list${BusinessName}(params) {
    return request.get(`#[[$]]#{baseUrl}/list`, {params})
}

// 查询${functionName}详细
export function get${BusinessName}(${pkColumn.javaField}) {
    return request.get(`#[[$]]#{baseUrl}/#[[$]]#${${pkColumn.javaField}}`)
}

// 新增${functionName}
export function add${BusinessName}(data) {
    return request.post(baseUrl, data)
}

// 修改${functionName}
export function update${BusinessName}(data) {
    return request.put(baseUrl, data)
}

// 删除${functionName}
export function del${BusinessName}(${pkColumn.javaField}) {
    return request.delete(`#[[$]]#{baseUrl}/#[[$]]#{${pkColumn.javaField}}`)
}

OSS集成

问题分析

在若依框架目前的实现中,是把图片存储到了服务器本地的目录,通过服务进行访问,这样做存储的是比较省事,但是缺点也有很多:

  • 硬件与网络要求:服务器通常需要高性能的硬件和稳定的网络环境,以保证文件传输的效率和稳定性。这可能会增加硬件和网络资源的成本和维护难度。
  • 管理难度:服务器目录需要管理员进行配置和管理,包括权限设置、备份策略等。如果管理不善或配置不当,可能会引发一些安全问题和性能问题。
  • 性能瓶颈:如果服务器处理能力不足或网络带宽不够,可能会导致性能瓶颈,影响文件上传、下载和访问的速度。
  • 单点故障风险:服务器故障可能导致所有存储在其上的文件无法访问,尽管可以通过备份和冗余措施来降低这种风险,但单点故障的风险仍然存在。 基于以上原因,企业中很多的文件都会存储到OSS中,OSS可以解决以上所有的问题,并且成本也不高,下面咱们就把阿里的OSS集成到若依项目中。

集成OSS,修改若依改成OSS。

配置文件

实际上传调用

@Component
@AllArgsConstructor
public class OSSClient {

    private final OSS oss;
    private final OSSProperties ossProperties;

    public String upload(String bucketName, MultipartFile file) {
        try {
            String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
            String newFileName = UUID.randomUUID() + Objects.requireNonNull(file.getOriginalFilename())
                    .substring(file.getOriginalFilename().lastIndexOf("."));
            String objectName = dir + "/" + newFileName;
            oss.putObject(bucketName, objectName, file.getInputStream());
            return "https://" + bucketName + "." + ossProperties.getEndpoint() + "/"  + objectName;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

配置类

@Configuration
public class OSSConfig {

    @Bean
    public OSS endpoint(OSSProperties ossProperties) {
        DefaultCredentialProvider defaultCredentialProvider =
                new DefaultCredentialProvider(ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey());
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        return OSSClientBuilder.create()
                .endpoint("https://" + ossProperties.getEndpoint())
                .credentialsProvider(defaultCredentialProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(extractRegion(ossProperties.getEndpoint()))
                .build();
    }

    /**
     * 获取 region
     */
    private String extractRegion(String endpoint) {
        if (endpoint == null) return null;
        int start = endpoint.indexOf("oss-");
        int end = endpoint.indexOf(".aliyuncs.com");
        if (start == -1 || end == -1 || start + 4 >= end)
            throw new IllegalArgumentException("Invalid endpoint: " + endpoint);
        return endpoint.substring(start + 4, end);
    }

}

获取配置信息

@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class OSSProperties {

    /**
     * 存储桶
     */
    private String endpoint;
    /**
     * 访问密钥
     */
    private String accessKeyId;
    /**
     * 密钥
     */
    private String secretAccessKey;

}

定时器

cron表达式

组成部分含义取值范围
第一部分Seconds (秒)0-59
第二部分Minutes(分)0-59
第三部分Hours(时)0-23
第四部分Day-of-Month(日)1月31日
第五部分Month(月)0-11或JAN-DEC
第六部分Day-of-Week(星期)1-7(1表示星期日)或SUN-SAT
第七部分Year(年) 可选1970-2099

cron表达式还可以包含一些特殊符号来设置更加灵活的定时规则, 如下表所示:

符号含义
?表示不确定的值。当两个子表达式其中一个被指定了值以后,为了避免冲突,需要将另外一个的值设为 "?" 。例如:想在每月20日触发调度,不管20号是星期几,只能用如下写法:0 0 0 20 * ?,其中最后以为只能用“?”
*代表所有可能的值
,设置多个值,例如"26,29,33"表示在26分,29分和33分各自运行一次任务
-设置取值范围,例如"5-20",表示从5分到20分钟每分钟运行一次任务
/设置频率或间隔,如"1/15"表示从1分开始,每隔15分钟运行一次任务
L用于每月,或每周,表示每月的最后一天,或每个月的最后星期几,例如"6L"表示"每月的最后一个星期五"
W表示离给定日期最近的工作日,例如"15W"放在每月(day-of-month)上表示"离本月15日最近的工作日"
#表示该月第几个周X。例如”6#3”表示该月第3个周五

| 为了让大家更熟悉cron表达式的用法, 接下来我们给大家列举了一些例子, 如下表所示:

cron表达式含义
*/5 * * * * ?每隔5秒运行一次任务
0 0 23 * * ?每天23点运行一次任务
0 0 1 1 * ?每月1号凌晨1点运行一次任务
0 0 23 L * ?每月最后一天23点运行一次任务
0 26,29,33 * * * ?在26分、29分、33分运行一次任务
0 0/30 9-17 * * ?朝九晚五工作时间内每半小时运行一次任务
0 15 10 ? * 6#3每月的第三个星期五上午10:15运行一次任务

如何使用

  1. 创建执行方法
@Component
public class HelloTask {

    public void hello(){
        System.out.println("hello world");
    }

}
  1. 在界面使用该方法

image.png

  1. 把该目录加入到白名单中

首先,com.kzzyl.common.constant.JOB_WHITELIST_STR 加入定时类的方法。

image.png

最后开启了就会自动执行