Skip to content

基础工具集

这里介绍一下一些基础工具集的使用。

主要包含在 wmeimob-fastboot-utils jar 中。

请求日志记录

通过启用注解 @EnableRestfulApiLog,可以实现对 controller 下所有 action 请求进行记录,如果想忽略某个 action,则可以使用 @IgnoreRestfulApiLog 注解。

xss 防御方案

框架支持两种模式:

  • 对非法脚本进行编码

例如 < 符号将被编码为 &lt

  • 剔除非法脚本(基于 jsoup 实现),默认实现。

支持的完整配置如下:

yml
wmeimob:   
    xss:
        exclude-path-patterns:
            - /logout #放行 url
        mode: jsoup #模式
        tags:       #自定义放行标签
            - hr
            - div:data-w-e-type
            - div:data-w-e-is-void

bean 属性复制

推荐使用BeanCopierUtil类,后续部署需要手动增加启动参数-noverify

其中 list copy 推荐使用 copyLists 方法,可保证集合不会出现 null 问题。更好的方式推荐使用 MapStruct。

枚举扩展

对于枚举需要扩展 message,code 的需求,可以实现EnumValue接口。这方便在swagger中集成显示枚举注释。

为了方便前端对接后端定义的枚举内容,对实现了EnumValue接口的枚举,后端通过 @EnableEnumRegister 注解来启用枚举扫描注册,通过 controller 暴露对应的枚举接口供前端使用。

java
/**
 * 通用枚举控制器
 *
 * @author mjyang
 * @date 2022/3/30 23:06
 */
@Api(tags = "枚举")
@RestController
@RequestMapping("/api/enum")
public class EnumController {

    @Resource
    private CommonEnumRegistry commonEnumRegistry;

    @GetMapping
    @ApiOperation(value = "获取所有枚举配置")
    public JsonResult<Map<String, List<PublicKeyValueDto>>> list() {
        Map<String, List<EnumValue>> dict = this.commonEnumRegistry.getNameDict();
        Map<String, List<PublicKeyValueDto>> dictVo = MapUtil.newHashMap(dict.size());
        for (Map.Entry<String, List<EnumValue>> entry : dict.entrySet()) {
            List<EnumValue> value = entry.getValue();
            dictVo.put(entry.getKey(), value
                    .stream()
                    .map(x -> {
                        return new PublicKeyValueDto(x.getKey(), x.getValue().toString());
                    }).collect(Collectors.toList()));
        }
        return JsonResult.ok(dictVo);
    }

    @GetMapping("types")
    @ApiOperation(value = "获取所有枚举配置类型名称")
    public JsonResult<List<String>> types() {
        Map<String, List<EnumValue>> dict = this.commonEnumRegistry.getNameDict();
        return JsonResult.ok(new ArrayList<>(dict.keySet()));
    }

    @GetMapping("{type}")
    @ApiOperation(value = "根据枚举配置类型名称获取枚举信息")
    public JsonResult<List<PublicKeyValueDto>> getByType(@PathVariable("type") String type) {
        Map<String, List<EnumValue>> dict = this.commonEnumRegistry.getNameDict();
        List<EnumValue> commonEnums = Optional.ofNullable(dict.get(type)).orElseGet(ListUtil::empty);
        return JsonResult.ok(
                commonEnums
                        .stream()
                        .map(x -> {
                            return new PublicKeyValueDto(x.getKey(), x.getValue().toString());
                        }).collect(Collectors.toList())
        );
    }

spring validate 验证注解

  • NotEmptyLongListValidator list 非默认值(某个给定值)验证

  • RangeExtend 枚举扩展验证

  • Mobile 手机号验证

  • IdCard 身份证验证

  • StringSpiltValidator 字符分隔符验证

  • 验证分组

    • insert
    • update

查看所有支持的内置注解

分页基础模型

有分页需求的可以统一继承BaseDTO类。里面包含一些常用的分页检索参数。标品里面分页分为两种:

java
/**
 * 普通查询输出参数
 *
 * @author mjyang
 * @date 2021/7/6
 */
@Getter
@RequiredArgsConstructor
public class PagedResult<T> extends BasePageResult{
    
    private Long total;
    private List<T> list;

    public static <T> PagedResult<T> of(Long total, List<T> list) {
        return new PagedResult<>(total, list);
    }

    public static <T> PagedResult<T> empty() {
        return of(null, ListUtil.empty());
    }

    public static <T> Page<T> startPage(int pageNum, int pageSize) {
        return PageHelper.startPage(pageNum, pageSize);
    }

    public static <T> Page<T> startPage(Object obj) {
        if (obj instanceof BaseDTO) {
            BaseDTO pageDto = (BaseDTO) obj;
            return startPage(pageDto.getPageNum(), pageDto.getPageSize());
        }
        throw SimpleException.getInstance("模型是否未继承自 BaseDTO ?");
    }
}
java
/**
 * 普通查询输出参数
 *
 * @author mjyang
 * @date 2021/7/6
 */
@Getter
@Setter
public class PagedResult<T> implements Serializable {

    private static final long serialVersionUID = 568969944565726191L;

    private Long total;

    private List<T> list;
}
java
/**
 * 滚动查询输出参数
 *
 * @author mjyang
 * @date 2021/7/6
 */
public class PagedScrollResult<T> extends BasePageResult{

    @Getter
    private final List<T> list;

    @Getter
    private Long nextToken;

    private Boolean isLastPage;

    private PagedScrollResult(List<T> list) {
        if (CollectionUtil.isNotEmpty(list)) {
            int size = list.size();
            T obj = list.get(size - 1);
            this.nextToken = obj.getId();
            this.isLastPage = ObjUtil.isNull(nextToken);
        }
        this.list = list;
    }

    public static <T extends NextToken> PagedScrollResult<T> of(List<T> list) {
        return new PagedScrollResult<>(list);
    }

    public static <T extends NextToken> PagedScrollResult<T> empty() {
        return new PagedScrollResult<>(ListUtil.empty());
    }

    public static <T> Page<T> scrollPage(int pageNum, int pageSize) {
        return PageHelper.startPage(pageNum, pageSize, false);
    }

    public static <T> Page<T> scrollPage(Object obj) {
        if (obj instanceof BaseDTO) {
            BaseDTO pageDto = (BaseDTO) obj;
            return scrollPage(pageDto.getPageNum(), pageDto.getPageSize());
        }
        throw SimpleException.getInstance("模型是否未继承自 BaseDTO ?");
    }

    public Boolean getIsLastPage() {
        return this.isLastPage;
    }
}
java
/**
 * 滚动查询输出参数
 *
 * @author mjyang
 * @date 2021/7/6
 */
@Getter
@Setter
public class PagedScrollResult<T extends NextToken> implements Serializable {

    private static final long serialVersionUID = 568969944565726191L;

    private List<T> list;

    private Long nextToken;

    private Boolean isLastPage;

    public Boolean getIsLastPage() {
        return this.isLastPage;
    }
}
java
/**
 * 基础分页
 *
 * @author mjyang
 * @date 2021/7/6
 */
@RequiredArgsConstructor
public abstract class BasePageResult {

    /**
     * 追加分页 limit 1 1,不统计 count
     */
    public static void startPageByFirstOnly() {
        startPageByFirstOnly(1);
    }

    /**
     * 追加分页 limit 1 pageSize,不统计 count
     */
    public static void startPageByFirstOnly(int pageSize) {
        PageHelper.startPage(1, pageSize, false);
    }
}
java
/**
 * page 工具类
 *
 * @author mjyang
 * @date 2024/6/3 21:30
 */
@UtilityClass
public class PageUtil {

    public static <T> PagedResult<T> of(Long total, List<T> list) {
        PagedResult<T> pagedResult = new PagedResult<>();
        pagedResult.setTotal(total);
        pagedResult.setList(list);
        return pagedResult;
    }

    public static <T> PagedResult<T> empty() {
        return of(null, ListUtil.empty());
    }

    public static <T extends NextToken> PagedScrollResult<T> ofScroll(Integer pageSize, List<T> list) {
        PagedScrollResult<T> pagedScrollResult = new PagedScrollResult<>();
        if (CollectionUtil.isNotEmpty(list)) {
            int size = list.size();
            Boolean isLastPage;
            if (size < pageSize) {
                isLastPage = Boolean.TRUE;
            } else {
                isLastPage = Boolean.FALSE;
            }
            T obj = list.get(size - 1);
            pagedScrollResult.setNextToken(obj.getId());
            pagedScrollResult.setIsLastPage(isLastPage);
        } else {
            pagedScrollResult.setIsLastPage(Boolean.TRUE);
        }
        pagedScrollResult.setList(list);
        return pagedScrollResult;
    }

    public static <T extends NextToken> PagedScrollResult<T> emptyScroll() {
        return new PagedScrollResult<>();
    }

    public static <T> Page<T> startPage(int pageNum, int pageSize) {
        return PageHelper.startPage(pageNum, pageSize);
    }

    public static <T> Page<T> startPage(Object obj) {
        if (obj instanceof BaseDTO) {
            BaseDTO pageDto = (BaseDTO) obj;
            return startPage(pageDto.getPageNum(), pageDto.getPageSize());
        }
        throw SimpleException.getInstance("模型是否未继承自 BaseDTO ?");
    }

    public static <T> Page<T> scrollPage(int pageNum, int pageSize) {
        return PageHelper.startPage(pageNum, pageSize, false);
    }

    public static <T> Page<T> scrollPage(Object obj) {
        if (obj instanceof BaseDTO) {
            BaseDTO pageDto = (BaseDTO) obj;
            return scrollPage(pageDto.getPageNum(), pageDto.getPageSize());
        }
        throw SimpleException.getInstance("模型是否未继承自 BaseDTO ?");
    }

    /**
     * 追加分页 limit 1 1,不统计 count
     */
    public static void startPageByFirstOnly() {
        startPageByFirstOnly(1);
    }

    /**
     * 追加分页 limit 1 pageSize,不统计 count
     */
    public static void startPageByFirstOnly(int pageSize) {
        PageHelper.startPage(1, pageSize, false);
    }
}

滚动分页的实现上是不计算 count,且再实现上做了取巧的实现,大数据量场景下会有深度分页问题,因此在模型里面有一个 nextToken 参数,联同分页结果一起给到前端,但同时需要前端在分页时带上该参数作为请求参数,后端查询时使用此参数做为查询条件 where id > nextToken。这样可以解决深度分页问题。默认情况下,nextToken 为主键 id 值,默认有序。BaseDTO 默认接收 nextToken 参数。

为什么 2.5.9.2 会出现两种不同的 pageResult 与 pageScrollResult 呢?这是因为当使用 dubbo 时,模型需要序列化,而 2.5.9.1 版本代码难以达到这个目的,因此再 2.5.9.2 里重新重构出 pageUtil 工具类来做分页需求。

数据脱敏

基于 jackson 序列化实现。目前支持的脱敏规则有:

  • 手机号
  • 姓名
  • 详细地址

为了简化使用提供 @DataMasking 注解,使用如下:

java
@ApiModelProperty("用户手机号")
@DataMasking(rule = DataMaskingRule.MOBILE)
private String mobile;

excel 工具类

基于 easypoi 实现。提供 EasyPoiUtil 工具类。

ip 工具类

IpUtils

web request 工具类

WebServletUtil

  • 常用正则工具类
    • cn.hutool.core.lang.Validator
    • cn.hutool.core.lang.PatternPool

其他工具类需求

原则上一律使用 hutool 类库。当前框架版本 5.8.20。

常见问题

  • BeanCopierUtilaccessor注解问题

当类上使用accessor注解时,它会帮助你实现链式调用。但当你使用BeanCopierUtil进行属性复制时,会出现属性一直为 null 的问题。这是因为它的底层进行属性 set 时,判断 set 方法必须为 void,而当使用accessor注解时,set 方法返回类型不是 void。如果要规避这个问题,只能是去掉accessor注解。