# web-ui **Repository Path**: wwwpythonpw/web-ui ## Basic Information - **Project Name**: web-ui - **Description**: web 项目通用输入输出常用配置组件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2021-10-08 - **Last Updated**: 2021-10-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # web-ui 这是一个 web 通用配置的组件,即插即用,可用于新项目或私活或者改造旧项目。是对 SpringBoot 快速开发的一种补充,它内置了大量的配置来简化开发,遵循约定高于配置原则。 组件测试代码:[https://gitee.com/sanri/example/tree/master/test-web-ui](https://gitee.com/sanri/example/tree/master/test-web-ui) 组件博客位置:[https://blog.csdn.net/sanri1993/article/details/52200641](https://blog.csdn.net/sanri1993/article/details/52200641) ## 优势说明 * 固定了输入输出格式 * 2019/10/28 开始支持自定义输出,支持旧项目了 * 不用像公司的返回结构一样,需要自己包装类型,直接返回原始类型,如果需要可以返回 void * 支持树结构数据返回,可以一个注解就转换结构为树形结构,支持森林结构 * 如果项目中出现业务操作不符合或调用第三方出错,可使用异常或断言抛出,我们将拦截成统一格式返回 * 自带参数空格过滤功能,还可以定义特殊字符和谐 * 支持校验器,添加了大量常用验证器 * 支持使用校验器来验证文件类型,文件大小,文本文件字符集,可以使用魔术数字来验证文件类型 * 支持大文件分片上传,已内置 Controller * 支持方法日志参数记录 ## 项目功能 我新开的一个项目,总结了多年的开发经验所得,它具有的功能有 * 固定了输入输出格式 ```java // 普通输出格式 @Data public class ResponseDto implements Serializable { // 0 字符串表示成功,否则失败 private String code = "0"; private String message; private T data; } // 分页输出格式,是包裹在普通输出格式中的,PageResponseDto 做为 data 属性 @Data public class PageResponseDto { private List rows; private Integer total; } // 分页输入格式 @Setter public class PageParam { private String pageNo; private String pageSize; } ``` * 不用像公司的返回结构一样,需要自己包装类型,直接返回原始类型,如果需要可以返回 void **示例一:** ```java @PostMapping("/insertUser") public void insertUser(User user){ xxxService.insert(user); } ``` 它将会返回这样的数据结构 ```json { "code":"0", "message":"ok", "data":null } ``` **示例二:** ```java @GetMapping("/queryUserById") public User queryUserById(Integer userId){ xxxService.queryUserById(userId); } ``` 它将会返回这样的数据结构 ```json { "code":"0", "message":"ok", "data":{ "userId":1, "username":"9420" } } ``` **示例三:** 对于分页数据的处理 ```java @GetMapping("/queryUserPage") public PageResponseDto pageQuery(PageParam pageParam,Map 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); } ``` 它将会返回这样的数据结构 ```json { "code":"0", "message":"ok", "data":{ "total":100, "rows":[{...},{...}] } } ``` **示例四: 树结构返回** 对于树型结构数据,你可以用简单数据返回,即原来的 `List` 也可以添加一个注解,使其成为树状结构 ```java //rootId 指定为根结点 id @GetMapping("/treeShowMenu") @TreeResponse(type = MenuDto.class,rootId = "1") public List treeShowMenu(){ List 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; } ``` ```java // 树状结构消息类 public class MenuDto extends RootTreeResponseDto { 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;} } ``` 它将返回如下数据结构 ```json { "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" }] } ``` * 如果项目中出现业务操作不符合或调用第三方出错,可使用异常抛出,我们将拦截成统一格式返回 **示例一:** ```java if(业务条件不满足){ throw BusinessException.create("业务提示信息"); } ``` 它将会返回这样的数据结构,code 是随机生成的 ```json { "code":"234234", "message":"业务提示信息", "data":null } ``` **示例二:** 自定义 code 示例方法一 ```java if(业务条件不满足){ throw BusinessException.create("E007","业务提示信息"); } ``` 它将会返回这样的数据结构 ```json { "code":"E007", "message":"业务提示信息", "data":null } ``` **示例三:** 自定义 code 示例方法二 ```java // 配置异常代码 public enum SystemMessage implements ExceptionCause { 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); } } ``` 使用异常 ```java if(业务条件不满足){ throw SystemMessage.SIGN_ERROR.exception("签名串"); } ``` 它将会返回这样的数据结构 ```json { "code":"4005", "message":"签名错误,你的签名串为 [签名串]", "data":null } ``` **或许是 BUG** 或许你已经注意到了,我可以使用异常来直接返回成功信息,`throw SystemMessage.OK.exception();` 哈哈,如果你喜欢这么干的话,我也是支持的 目前系统消息中存在的错误码,可以直接拿来用哦;这个是对照于 http 状态码 * 4 开头的是请求还未到后端,就直接出错了 * 5 开头的是后端数据处理出错即 500 错误 * 59 开头是专门用于文件的错误,这里准备 10 个错误用于文件相关的异常 ```java 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}"), ``` * 你以为它就这么点能耐吗,它还自带参数空格过滤功能,还可以定义特殊字符和谐,你只需要注入一个处理器,它就能工作,注入方式如下 ```java @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 ,直接使用即可 ```java public interface Insert { } public interface Update { } ``` * 支持大文件上传,文件秒传,文件验证;你只需要配置几个选项即可使用 ```properties # 文件上传的位置 sanri.webui.upload.basePath=d:/test/ # 临时文件路径 spring.servlet.multipart.location=d:/tmp ``` 或者你想上传到别的地方,那就需要自己实现 `com.sanri.web.bigfile.BigFileStorage`然后注入 IOC 到容器 ```java @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 包文件 ```xml com.sanri.web web-ui 1.0-SNAPSHOT ``` 开启快速开发 ```java @EnableWebUI ``` 如果想开启大文件上传 ```java @EnableBigFileUpload ``` 一些配置说明 ```properties # 当使用默认大文件上传 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` 来记录日志 ```java @GetMapping("/testParamTrim") @SysLogMark public void testParamTrim(TestParam testParam){} ``` 兼容性说明 : 1. 兼容 `application/x-www-form-urlencoded` 类型参数 2. 兼容 `application/form-data` 类型参数 3. 兼容 `application/json` 类型参数 当然,我会排除文件类型的参数,它太庞大了,不可能打印在日志里面 **实现自己的日志记录**方法:注入一个 `LogInfoHandler` 的 Bean 到 IOC 容器中 新增配置 ```properties # 日志参数打印,可支持的项有 base,param,header,body sanri.webui.logmark.showInfos=base,param,header,body ``` ### update 2019/10/25 增加常用验证器和部分工具 一些常用校验器和通用预览和下载功能,比较常用,这里给全部集成了 参数校验器使用方法 ```java // 必须为强密码 @NotNull @Password(strength = Password.Strength.STRONG) private String password; ``` 其它常用验证器 * `@UserName` 验证参数是否为用户名 * `@Password` 验证参数是否为密码 * `@IdCard18` 验证参数是否为 18 位身份证 可以在 resources 目录下放置一个区域代码,做更强的验证,文件名为 `areaCodes`,文件内容以逗号分隔所有的区域代码 * `@EnumIntValue` 和 `@EnumStringValue` 验证参数是否为枚举值 增加一些文件下载,预览方法,和 request 请求信息的获取 ```java @Autowired RequestInfoHelper requestInfoHelper; @Autowired StreamHelper streamHelper; ``` 修改处理器注入方式 ,使用自己的接口 `ParamHandler`,不使用 `Function` ```java @Bean("paramHandler") public ParamHandler paramHandler(){ return param -> param.replace("<","《"); } ``` ### update 2019/10/27 增加树形数据返回 为解决前端 mm 需要后端人员返回树形结构数据问题,其实大部分框架已经支持简单树形数据,像 ztree ,但也有的前端框架是需要后端帮忙转化一下数据结构的,所以特加此功能 这个树形结构的转换使用了一个快速转换的机制,充分利用了对象在内存中地址的原理,实测在万条数据转换为 10ms 左右,使用方法是先实现一个 `TreeResponseDto` 的类,然后在 Controller 中添加一个注解 ```java @TreeResponse(type = MenuDto.class,rootId = "1") ``` ### update 2019/10/28 支持旧项目 * 实现了对旧项目的支持,增加输出处理类 `ResponseHandler` 实现它,并注入 IOC 容器,即可实现对输出的自定义实现 ```java @Data @NoArgsConstructor @AllArgsConstructor public class ResultDto { 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` 用来配置文件大小和文件类型 ```java /** * 文件类型的验证有点像 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[] payload() default {}; /** * 允许的文件类型列表,验证规则见文档注释 * 这里写的是 mime 类型列表 * @return */ String [] allowFileTypes() default {"*"}; /** * 拒绝的文件类型列表,验证规则见文档注释 * 这里写的是 mime 类型列表 * @return */ String [] denyFileTypes() default {"*"}; /** * 检查魔术数字是否满足 * @return */ boolean checkMagic() default false; /** * 允许文件最大大小,-1 表示无限制 * @return */ long maxFileSize() default -1; } ``` 使用魔术数字做校验时,目前只支持部分图片的魔术数字校验,如果需要扩展,请往 `map` 中添加数据,像这样 ```java ApprovedFileValidator.mimeTypeMagic.put(MimeTypeUtils.parseMimeType("image/png"),"89504e47"); ``` ### update 2019/11/15 支持自定义异常处理 在此之前,都是框架帮忙把所有异常进行了拦截,但如果用户需要自己定义异常拦截时将没有任何办法,此次更新兼容了用户的异常处理,并将我的全局异常处理定义为最低优先级,如果你发现你的异常处理没有生效(大部分情况下会优先加载应用中的,虽然是同一优先级),请将你的异常处理优先级提高,设置如下 ```java @Order(Ordered.LOWEST_PRECEDENCE - 20) // 数字越小,优先级越高 public class CustomExceptionHandler ``` 并且将返回类型定义为你自定义的返回类型 ,像这样 ```java @ExceptionHandler(IllegalArgumentException.class) public ResponseDto exception(IllegalArgumentException e){ return ResponseDto.err("222").message(e.getMessage()); } ``` 如果你的异常处理没有拦截到异常,将继续进入我的全局异常处理 ,把异常类名做为 code,把消息放入 message **解决 bug** 支持将一些其它框架的返回忽略全局输出,像 swagger-ui 使用了自己的数据结构,在此之前,框架是照样拦截导致 swagger-ui 无法打开,目前已经支持 swaggerui 无需配置,如果有其它的框架类,你可以这样进行配置 ```properties webui.advice.ignore.package=正则包路径过滤,忽略框架包 ```