2 Star 13 Fork 5

sanri1993 / web-ui

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

web-ui

这是一个 web 通用配置的组件,即插即用,可用于新项目或私活或者改造旧项目。是对 SpringBoot 快速开发的一种补充,它内置了大量的配置来简化开发,遵循约定高于配置原则。

组件测试代码:https://gitee.com/sanri/example/tree/master/test-web-ui

组件博客位置:https://blog.csdn.net/sanri1993/article/details/52200641

优势说明

  • 固定了输入输出格式
  • 2019/10/28 开始支持自定义输出,支持旧项目了
  • 不用像公司的返回结构一样,需要自己包装类型,直接返回原始类型,如果需要可以返回 void
  • 支持树结构数据返回,可以一个注解就转换结构为树形结构,支持森林结构
  • 如果项目中出现业务操作不符合或调用第三方出错,可使用异常或断言抛出,我们将拦截成统一格式返回
  • 自带参数空格过滤功能,还可以定义特殊字符和谐
  • 支持校验器,添加了大量常用验证器
  • 支持使用校验器来验证文件类型,文件大小,文本文件字符集,可以使用魔术数字来验证文件类型
  • 支持大文件分片上传,已内置 Controller
  • 支持方法日志参数记录

项目功能

我新开的一个项目,总结了多年的开发经验所得,它具有的功能有

  • 固定了输入输出格式

    // 普通输出格式
    @Data
    public class ResponseDto<T> implements Serializable {
        // 0 字符串表示成功,否则失败
        private String code = "0";
        private String message;
        private T data;
    }
    // 分页输出格式,是包裹在普通输出格式中的,PageResponseDto 做为 data 属性
    @Data
    public class PageResponseDto<T> {
        private List<T> rows;
        private Integer total;
    }
    
    // 分页输入格式 
    @Setter
    public class PageParam {
    	private String pageNo;
    	private String pageSize;
    }
  • 不用像公司的返回结构一样,需要自己包装类型,直接返回原始类型,如果需要可以返回 void

    示例一:

    @PostMapping("/insertUser")
    public void insertUser(User user){
        xxxService.insert(user);
    }

    它将会返回这样的数据结构

    {
        "code":"0",
        "message":"ok",
        "data":null
    }

    示例二:

    @GetMapping("/queryUserById")
    public User queryUserById(Integer userId){
        xxxService.queryUserById(userId);
    }

    它将会返回这样的数据结构

    {
        "code":"0",
        "message":"ok",
        "data":{
            "userId":1,
            "username":"9420"
        }
    }

    示例三:

    对于分页数据的处理

    @GetMapping("/queryUserPage")
    public PageResponseDto<User> pageQuery(PageParam pageParam,Map<String,String> queryParams){
        PageHelper.startPage(pageParam.getPageNo(),pageParam.getPageSize());
        Page page = (Page) xxxService.pageQuery(queryParams);
        List result = page.getResult();
        long total = page.getTotal();
        return new PageResponseDto(result,total);
    }

    它将会返回这样的数据结构

    {
        "code":"0",
        "message":"ok",
        "data":{
            "total":100,
            "rows":[{...},{...}]
        }
    }

    示例四: 树结构返回

    对于树型结构数据,你可以用简单数据返回,即原来的 List<Dto> 也可以添加一个注解,使其成为树状结构

    //rootId 指定为根结点 id 
    @GetMapping("/treeShowMenu")
    @TreeResponse(type = MenuDto.class,rootId = "1")
    public List<Menu> treeShowMenu(){
        List<Menu> menus = new ArrayList<>();
        menus.add(new Menu(1,"全国",-1));
        menus.add(new Menu(2,"湖南",1));
        menus.add(new Menu(3,"长沙",2));
        menus.add(new Menu(4,"深圳",1));
        return menus;
    }
    // 树状结构消息类
    public class MenuDto extends RootTreeResponseDto<Menu> {
    	public MenuDto(Menu origin) {
    		super(origin);
    	}
        
    	@Override
    	public String getId() {return origin.getId()+"";}
    
    	@Override
    	public String getParentId() {return origin.getPid()+"";}
    
    	@Override
    	public String getLabel() {return origin.getText();}
    
    	@Override
    	public Menu getOrigin() {return origin;}
    }

    它将返回如下数据结构

    {
    	"code": "0",
    	"message": "ok",
    	"data": [{
    		"origin": {
    			"id": 1,
    			"text": "全国",
    			"pid": -1
    		},
    		"childrens": [{
    			"origin": {
    				"id": 2,
    				"text": "湖南",
    				"pid": 1
    			},
    			"childrens": [{
    				"origin": {
    					"id": 3,
    					"text": "长沙",
    					"pid": 2
    				},
    				"childrens": [],
    				"id": "3",
    				"label": "长沙",
    				"parentId": "2"
    			}],
    			"id": "2",
    			"label": "湖南",
    			"parentId": "1"
    		}, {
    			"origin": {
    				"id": 4,
    				"text": "深圳",
    				"pid": 1
    			},
    			"childrens": [],
    			"id": "4",
    			"label": "深圳",
    			"parentId": "1"
    		}],
    		"id": "1",
    		"label": "全国",
    		"parentId": "-1"
    	}]
    }
  • 如果项目中出现业务操作不符合或调用第三方出错,可使用异常抛出,我们将拦截成统一格式返回

    示例一:

    if(业务条件不满足){
        throw BusinessException.create("业务提示信息");
    }

    它将会返回这样的数据结构,code 是随机生成的

    {
        "code":"234234",
        "message":"业务提示信息",
        "data":null
    }

    示例二:

    自定义 code 示例方法一

    if(业务条件不满足){
        throw BusinessException.create("E007","业务提示信息");
    }

    它将会返回这样的数据结构

    {
        "code":"E007",
        "message":"业务提示信息",
        "data":null
    }

    示例三:

    自定义 code 示例方法二

    // 配置异常代码 
    public enum  SystemMessage implements ExceptionCause<BusinessException> {
        SIGN_ERROR(4005,"签名错误,你的签名串为 [%s]"),;
        ResponseDto responseDto = new ResponseDto();
    
        private SystemMessage(int returnCode,String message){
            responseDto.setCode(returnCode+"");
            responseDto.setMessage(message);
        }
    
        public BusinessException exception(Object...args) {
            return BusinessException.create(this,args);
        }
    }

    使用异常

    if(业务条件不满足){
        throw SystemMessage.SIGN_ERROR.exception("签名串");
    }

    它将会返回这样的数据结构

    {
        "code":"4005",
        "message":"签名错误,你的签名串为 [签名串]",
        "data":null
    }

或许是 BUG 或许你已经注意到了,我可以使用异常来直接返回成功信息,throw SystemMessage.OK.exception(); 哈哈,如果你喜欢这么干的话,我也是支持的

目前系统消息中存在的错误码,可以直接拿来用哦;这个是对照于 http 状态码

  • 4 开头的是请求还未到后端,就直接出错了
  • 5 开头的是后端数据处理出错即 500 错误
  • 59 开头是专门用于文件的错误,这里准备 10 个错误用于文件相关的异常
OK(0,"成功"),
ARGS_NULL(4000,"参数错误,必填参数 [{0}]"),
ARGS_ERROR(4001,"参数错误,错误参数名[{0}],值[{1}],原因:{2}"),
NOT_LOGIN(4002,"未登录或 session 失效"),
PERMISSION_DENIED(4003,"没有权限"),
DATA_PERMISSION_DENIED(4004,"无数据权限"),
SIGN_ERROR(4005,"签名错误,你的签名串为 [{0}]"),
CHANNEL_NOT_SUPPORT(4006,"非法访问"),
ACCESS_DENIED(4007,"禁止访问 {0}"),

SERVICE_CALL_FAIL(5000,"后台服务异常"),
NETWORK_ERROR(5001,"网络连接错误或磁盘不可读"),
DATA_TO_LARGE(5002,"数据过大"),
REPEAT_DATA(5003,"数据重复 {0}"),
NOT_SUPPORT_OPERATOR(5004,"不支持的操作"),
NOT_SUPPORT_MIME(5005,"不支持的 MIME类型,当前类型为:{0}"),
POOL_OBJECT_NOT_ENOUGH(5006,"对象池[{0}]对象不足"),
CALL_MODUL_FAIL(5007,"{0} 模块调用错误"),

FILE_INCOMPLETE(5901,"文件不完整,上传失败"),
FILE_CORRUPTED(5902,"文件损坏,上传失败"),
FILE_TOO_LARGE(5903,"文件过大,当前文件大小:{0},超过最大上传大小:{1}"),
FILE_TYPE_NOT_ALLOW(5904,"文件类型不被支持,当前文件类型:{0},支持的文件类型为:{1}"),
  • 你以为它就这么点能耐吗,它还自带参数空格过滤功能,还可以定义特殊字符和谐,你只需要注入一个处理器,它就能工作,注入方式如下

    
    

@Bean("paramHandler") public ParamHandler paramHandler(){ return param -> param.replace("<","《"); }


* 自带了日期转化(输入)功能,可以支持的日期格式有 

```java
final String[] parsePatterns = new String[]{"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.S"};

现在是固定这三种格式 ,后面会放开让使用者自己配置

  • 支持校验器,已经帮你设置好了两个 group ,直接使用即可

    public interface Insert {
    }
    public interface Update {
    }
  • 支持大文件上传,文件秒传,文件验证;你只需要配置几个选项即可使用

     # 文件上传的位置
     sanri.webui.upload.basePath=d:/test/
     # 临时文件路径 
     spring.servlet.multipart.location=d:/tmp

    或者你想上传到别的地方,那就需要自己实现 com.sanri.web.bigfile.BigFileStorage然后注入 IOC 到容器

    @Bean
    public BigFileStorage bigFileStorage(){
        return new LocalBigFileStorage();
    }

大文件上传的几个接口说明

已经帮你添加了一个 controller ,用于大文件上传,下面是接口说明

  • GET /upload/file/fileMetaData?originFileName=原始文件名&fileSize=文件大小&md5=文件 md5 返回 FileMetaData 数据,重要的数据是那个相对路径 relativePath 因为它是接下来所有接口的入参

  • GET /upload/file/filePosition?relativePath=相对路径 返回 文件当前上传的大小,用于断点续传

  • POST /upload/file/uploadPart?relativePath=相对路径 body 中添加 form-data 参数,file=文件 返回上传文件位置

  • GET /upload/file/validateFile?relativePath=相对路径&fileSize=文件大小&md5=文件 md5 值 返回文件是否正常上传,无损坏

使用说明

引入包或下载 jar 包文件

<dependency>
	<groupId>com.sanri.web</groupId>
    <artifactId>web-ui</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

开启快速开发

@EnableWebUI 

如果想开启大文件上传

@EnableBigFileUpload

一些配置说明

# 当使用默认大文件上传 LocalBigFileStorage 时,需要指定一个本地路径 
sanri.webui.upload.basePath=d:/test/
# 项目包前缀,用于打印业务异常时,过滤无用信息
sanri.webui.package.prefix=com.sanri.test.testwebui

更新列表

update 2019/10/24 增加日志组件

可以为方法标记记录日志功能,这是很常见的一个功能,感谢网友 东莞-队长(qq: 1178130627) 的朋友提出

因为日志每个系统有各自的做法,有的可能还需要把日志存储到 mongodb 中去,所以不可能全部统一起来,注解也是不支持继承的;所以我的解决办法是,我可能帮你尽可能的解析出一些参数来,但具体的实现逻辑还需要你自己来弄,框架默认会给你注入一个把日志打印到控制台的功能。

使用方法为使用注解标记当前方法,它将默认使用 com.sanri.web.logmark.Slf4jLogInfoHandler 来记录日志

@GetMapping("/testParamTrim")
@SysLogMark
public void testParamTrim(TestParam testParam){}

兼容性说明 :

  1. 兼容 application/x-www-form-urlencoded 类型参数
  2. 兼容 application/form-data 类型参数
  3. 兼容 application/json 类型参数

当然,我会排除文件类型的参数,它太庞大了,不可能打印在日志里面

实现自己的日志记录方法:注入一个 LogInfoHandler 的 Bean 到 IOC 容器中

新增配置

# 日志参数打印,可支持的项有 base,param,header,body
sanri.webui.logmark.showInfos=base,param,header,body

update 2019/10/25 增加常用验证器和部分工具

一些常用校验器和通用预览和下载功能,比较常用,这里给全部集成了

参数校验器使用方法

// 必须为强密码
@NotNull
@Password(strength = Password.Strength.STRONG)
private String password;

其它常用验证器

  • @UserName 验证参数是否为用户名

  • @Password 验证参数是否为密码

  • @IdCard18 验证参数是否为 18 位身份证

    可以在 resources 目录下放置一个区域代码,做更强的验证,文件名为 areaCodes,文件内容以逗号分隔所有的区域代码

  • @EnumIntValue@EnumStringValue 验证参数是否为枚举值

增加一些文件下载,预览方法,和 request 请求信息的获取

@Autowired
RequestInfoHelper requestInfoHelper;
@Autowired
StreamHelper streamHelper;

修改处理器注入方式 ,使用自己的接口 ParamHandler,不使用 Function

@Bean("paramHandler")
public ParamHandler paramHandler(){
    return param -> param.replace("<","《");
}

update 2019/10/27 增加树形数据返回

为解决前端 mm 需要后端人员返回树形结构数据问题,其实大部分框架已经支持简单树形数据,像 ztree ,但也有的前端框架是需要后端帮忙转化一下数据结构的,所以特加此功能

这个树形结构的转换使用了一个快速转换的机制,充分利用了对象在内存中地址的原理,实测在万条数据转换为 10ms 左右,使用方法是先实现一个 TreeResponseDto 的类,然后在 Controller 中添加一个注解

@TreeResponse(type = MenuDto.class,rootId = "1")

update 2019/10/28 支持旧项目

  • 实现了对旧项目的支持,增加输出处理类 ResponseHandler 实现它,并注入 IOC 容器,即可实现对输出的自定义实现

     @Data
     @NoArgsConstructor
     @AllArgsConstructor
     public class ResultDto<T> {
       private String errorCode;
       private String prompt;
       private T data;
     }
     
     @Component
     public class CustomResponseHandler implements ResponseHandler {
         @Override
         public Object handlerOut(ResponseDto responseDto) {
             return new ResultDto(responseDto.getCode(),responseDto.getMessage(),responseDto.getData());
         }
     
         @Override
         public Object handlerError(ResponseDto responseDto) {
             return responseDto.getCode()+":"+responseDto.getMessage();
         }
     }
  • 扩充了树结构,可实现森林输出,即多根节点,使用 rootParentId 代替之前的 rootId 实现森林输出

  • 扩展了文件校验相关的校验器,用于文件类型,文件大小,文本文件字符集的校验,其中文件类型可以使用魔术数字来校验,只是还不太稳定,默认选项是不支持的,你需要配置为 true 来支持魔术数字检查。

    • @FileCharset 用来检查文本文件的字符集,可配置 supports 来说明支持哪些字符集的文件上传
    • @ApprovedFile 用来配置文件大小和文件类型
    /**
     *  文件类型的验证有点像 nginx 的主机过滤规则 allow 和 deny
     *  但这里是先顺序执行 allow ,然后顺序执行 deny 只要有匹配就跳出
     *  支持通配符 * ,例如 * 或者 text/* 或 image/*
     */
    @Documented
    @Constraint(validatedBy = {ApprovedFileValidator.class})
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ApprovedFile {
        String message() default "{sanri.webui.validator.constraints.approvedFile}";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    
        /**
         * 允许的文件类型列表,验证规则见文档注释
         * 这里写的是 mime 类型列表
         * @return
         */
        String [] allowFileTypes() default {"*"};
    
        /**
         * 拒绝的文件类型列表,验证规则见文档注释
         * 这里写的是 mime 类型列表
         * @return
         */
        String [] denyFileTypes() default {"*"};
    
        /**
         * 检查魔术数字是否满足
         * @return
         */
        boolean checkMagic() default false;
    
        /**
         * 允许文件最大大小,-1 表示无限制
         * @return
         */
        long maxFileSize() default -1;
    }

    使用魔术数字做校验时,目前只支持部分图片的魔术数字校验,如果需要扩展,请往 map 中添加数据,像这样

    ApprovedFileValidator.mimeTypeMagic.put(MimeTypeUtils.parseMimeType("image/png"),"89504e47");

    update 2019/11/15 支持自定义异常处理

    在此之前,都是框架帮忙把所有异常进行了拦截,但如果用户需要自己定义异常拦截时将没有任何办法,此次更新兼容了用户的异常处理,并将我的全局异常处理定义为最低优先级,如果你发现你的异常处理没有生效(大部分情况下会优先加载应用中的,虽然是同一优先级),请将你的异常处理优先级提高,设置如下

    @Order(Ordered.LOWEST_PRECEDENCE - 20)	// 数字越小,优先级越高
    public class CustomExceptionHandler 

    并且将返回类型定义为你自定义的返回类型 ,像这样

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseDto exception(IllegalArgumentException e){
        return ResponseDto.err("222").message(e.getMessage());
    }

    如果你的异常处理没有拦截到异常,将继续进入我的全局异常处理 ,把异常类名做为 code,把消息放入 message

    解决 bug

    支持将一些其它框架的返回忽略全局输出,像 swagger-ui 使用了自己的数据结构,在此之前,框架是照样拦截导致 swagger-ui 无法打开,目前已经支持 swaggerui 无需配置,如果有其它的框架类,你可以这样进行配置

    webui.advice.ignore.package=正则包路径过滤,忽略框架包

空文件

简介

web 项目通用输入输出常用配置组件 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/sanri/web-ui.git
git@gitee.com:sanri/web-ui.git
sanri
web-ui
web-ui
master

搜索帮助