• Java后台开发中常用规范文档说明


    1.接口规范

    1.1 路径规范

    @RequestMapping("/api/file/tactics") //以api开头,controller具体功能
    public class FileTacticsController {
        @PostMapping("/page") //方法具体作用
        public ReturnT page(@RequestBody FileTacticsQueryVo vo) {
        }
        @GetMapping("/get/{id}") //restful风格
        public ReturnT getById(@PathVariable(name = "id") Integer id) {
            return fileTacticsService.getVoById(id);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.2 请求方式规范

    • 传递多个参数使用POST请求,并将多个参数封装到VO中(方便进行校验工作),使用@RequestBody接收json对象。
    • 根据id获取某条数据使用GET请求,id参数以resutful风格形式放在请求路径上。如上图。
    • 不使用PUT,DELETE等请求方式,因为nginx里开放此类请求方式存在安全问题。

    1.3 API文档描述规范

    • 使用swagger作为接口文档的管理工具
    • 使用注解标明接口所在的模块-子模块,接口所具有的作用
    @RestController
    @RequestMapping("/api/migrateLog")
    @Api(tags = "审计管理-迁移日志") //指明所处模块
    public class JobLogController extends BaseController {
    
        @Autowired
        private SysConfigService sysConfigService;
        @Resource
        private JobLogService jobLogService;
    
        //接口所具有的作用,以及请求方式
        @ApiOperation(value = "运行日志列表", notes = "运行日志列表", httpMethod = "POST") 
        @PostMapping("/logPageList")
        @OperateLog(module = "审计管理-迁移日志", type = "运行日志列表", des = "运行日志列表")
        public ReturnT<Map<String, Object>> pageList(@RequestBody MigrateLogVo migrateLogVo) {
            return jobLogService.logPageList(migrateLogVo);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.入参规范

    2.1 参数校验:

    • 执行条件查询时需要对可空的参数类型进行验证,避免可空的参数穿透到SQL,生成不可预知的SQL查询。
    • 尽量避免三个以上的参数(多参数函数),三个以上请封装成对象。

    3.异常处理规范

    3.1 使用SpringMVC的全局异常处理

    @RestControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(Exception.class)
        public ReturnT exceptionHandler(){
            //具体处理逻辑...    
        }
        
        @ExceptionHandler(BusinessException.class)
        public ReturnT businessExceptionHandler(){
            //具体处理逻辑...    
        }
        
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ReturnT methodArgumentNotValidExceptionHandler(){
            //具体处理逻辑...     
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.2 抛出方式

    • 所有异常都向上抛给controller,除特定可预见异常处理除外。
    • 创建具体的异常类型,如业务异常、文件异常、网络异常等。
    @Override
    public void add(JobUserVo vo) {
        //...
        JobUser existUser = jobUserMapper.selectOne(new QueryWrapper<>(whereUser));
        if (existUser != null) {
            throw new BusinessException(I18nUtil.getString("user_username_repeat"));
        }
        //...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.3 异常日志记录规范

    3.3.1 禁止使用e.printStackTrance()(错误的方式)

    • 此操作会打印堆栈异常,但是未输出到日志文件,上线问题无法排查。

    3.3.2 异常堆栈信息被吞(错误的方式)

    • 如下代码,只记录了异常信息的message,而异常本身并未记录,此操作会导致线上系统出现问题,无法准确定位问题代码和具体异常。
    logger.error(e.getMessage());
    logger.error("程序发生异常,异常原因{}",e.getMessage())
    
    • 1
    • 2

    3.3.2 异常信息直接返给前台(错误的方式)

    public ReturnT xxx(){
        try{
             xxxService.xxx();   
        }catch(Exception e){
            return ReturnT.failed(e.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3.3 正确的方式

    • 正确的方式应该如下所示,在记录异常message的同时将异常堆栈信息一起记录,即使用Logger接口的void error(String var1, Throwable var2)方法。
    try{
        
    }catch(xxxException e){
        logger.error(e.getMessage(),e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.3.4 特殊情况处理

    • 在系统中存在许多文件下载功能,前端既要处理文件内容,又要处理出现异常的情况。此时在下载过程中的异常统一抛出FileDownLoadException异常。
    @Override
    public void downloadFileJobTargetFile(int id, HttpServletResponse response) throws IOException {
        JobMigrateInfo jobMigrateInfo = jobMigrateInfoMapper.selectById(id);
        if (jobMigrateInfo.getMigrateType() != CommonConstant.FILE_TO_FILE_TACTICS && jobMigrateInfo.getMigrateType() != CommonConstant.DATA_TO_FILE_TACTICS)
            throw new FileDownLoadException("任务目的源不是文件,无法提供目标文件下载"); //以此种方式抛出异常,全局异常统一处理,修改Http状态码。
        if (jobMigrateInfo.getRunState() != JobMigrateInfo.FINISH_RUN) {
            throw new FileDownLoadException("当前任务未完成执行,无法下载迁移目标文件");
        }
        JobTactics jobTactics = jobTacticsMapper.selectById(jobMigrateInfo.getTacticsId());
        FileWriterConfig fileWriterConfig = fileWriterConfigMapper.selectById(jobTactics.getWriteConfigId());
        FileTypeEnum fileTypeEnum = FileTypeEnum.convertByTypeCode(fileWriterConfig.getFileType());
        File targetFile = new File(fileWriterConfig.getPath() + fileWriterConfig.getFileName() + "." + fileTypeEnum.getExt());
        if (!targetFile.exists()) {
            throw new FileDownLoadException("下载文件失败,未找到目标文件");
        }
    
        ServletUtils.setDownloadFileResponseHeader(response, fileWriterConfig.getFileName() + "." + fileTypeEnum.getExt());
        ServletOutputStream outputStream = response.getOutputStream();
        FileInputStream fileInputStream = new FileInputStream(targetFile);
        IoUtil.copy(fileInputStream, outputStream);
        IoUtil.close(fileInputStream);
        IoUtil.close(outputStream);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.查询分页规范

    • 使用myabtis提供的IPage接口封装分页对象
    
    @Override
    public IPage<JobUserLog> getPage(JobUserLogVo log) {
        IPage<JobUserLog> page = new Page<>(log.getCurrent(), log.getSize());
        QueryWrapper<JobUserLog> wrapper = new QueryWrapper<>();
        return this.baseMapper.selectPage(page, wrapper);
    }
    vo
    @ApiModel("操作日志接收对象")
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class JobUserLogVo extends JobUserLog {
        @ApiModelProperty(value = "当前页")
        private int current;
        @ApiModelProperty(value = "每页大小")
        private int size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5.配置规范

    • 将程序中可变的内容抽取到配置文件中进行统一配置管理。
    • 创建多个环境的application.yml文件,如application-dev.yml,application-test.yml,application-prod.yml。在application.yml里使用spring.profiles.active指定需要使用的配置环境。
    spring:
      profiles:
        active: dev
    
    • 1
    • 2
    • 3
    • 使用微服务部署时,将配置文件统一配置在配置中心中。

    6.参数校验

    6.1 使用实体或VO封装校验方法

    • 此方式写校验方法,由controller层进行调用,自定义校验逻辑简单。使用需要统一校验函数规范化方法名称。
    @ApiModel(value = "JobMigrateAddVo")
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class JobMigrateAddVo {
        @ApiModelProperty(value = "id")
        private int id;
        @ApiModelProperty(value = "任务名")
        private String name;
        @ApiModelProperty(value = "申请者id")
        private int applyId;
        @ApiModelProperty(value = "策略id")
        private int tacticsId;
        @ApiModelProperty(value = "任务执行周期,cron表达式")
        private String jobCron;
    
        public boolean formatAndVerify() {
            this.jobCron = "* * * * * ? *";
            return StringUtils.isNotBlank(name)
                    && applyId > 0
                    && tacticsId > 0
                    && CronExpression.isValidExpression(jobCron);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    6.2 使用Spring Validation在实体属性上进行校验

    • 此方式直接使用注解进行校验,快捷方便。针对自定义场景需要自定义校验注解。
    @ApiModel("业务应用更新")
    public class ReqUpdate {
        /**
         * 业务域ID
         */
        @ApiModelProperty(value = "业务域ID", required = false)
        @JsonProperty
        @Pattern(regexp = ValidateUtils.REGEX_NO_HTML_TAG,message = "业务域ID存在非法字符!")
        @Size(max = 32,message = "业务域ID长度超过最大32位")
        private String businessDomainId;
    
        /**
         * 业务应用名称
         */
        @ApiModelProperty(value = "业务应用名称", required = false)
        @JsonProperty
        @Pattern(regexp = ValidateUtils.REGEX_NO_HTML_TAG,message = "业务应用名称存在非法字符!")
        @Size(max = 100,message = "业务应用名称长度超过最大100位")
        private String name;
    
        /**
         * 部署级别:1.一级部署  2.二级部署
         */
        @ApiModelProperty(value = "部署级别:1.一级部署  2.二级部署", required = false)
        @JsonProperty
        @Pattern(regexp = "1|2|",message = "部署级别只能为1或者2")
        private String deployLevel;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    7.返回值

    • Controller层使用统一的返回体ReturnT,不能使用Map返回(开发一时爽,维护火葬场)
    public class ReturnT<T> implements Serializable {
        public static final long serialVersionUID = 42L;
    
        //错误码
        public static final int SUCCESS_CODE = 200;
        public static final int FAIL_CODE = 500;
        public static final int PARAM_ERR_CODE = 555;
        public static final int REQ_METHOD_ERR_CODE = 556;
        public static final int UNAUTHORIZED_CODE = 401;
        public static final int FILE_DOWNLOAD_FAIL_CODE = 586;
    
    
        public static final ReturnT<String> SUCCESS = new ReturnT<String>(SUCCESS_CODE, "执行成功");
        public static final ReturnT<String> FAIL = new ReturnT<>(FAIL_CODE, "执行失败");
    
        private int code;
        private String msg;
        private T content;
    
        public ReturnT() {
        }
    
        public ReturnT(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public ReturnT(int code, String msg, T content) {
            this.code = code;
            this.msg = msg;
            this.content = content;
        }
    
        public ReturnT(T content) {
            this.code = SUCCESS_CODE;
            this.content = content;
        }
    
        public static <T> ReturnT<T> fail(String msg) {
            return new ReturnT<T>(FAIL_CODE, msg);
        }
    
        public static ReturnT<String> failCode(Integer code, String msg) {
            return new ReturnT<String>(code, msg);
        }
    
        public static ReturnT<String> failException(Exception e) {
            if (e instanceof BusinessException) {
                BusinessException businessException = (BusinessException) e;
                return ReturnT.failCode(businessException.getCode(), businessException.getMessage());
            } else if (e instanceof AccessDeniedException) {
                return ReturnT.failCode(-1, e.getMessage());
            }
            return new ReturnT<String>(FAIL_CODE, e.getMessage());
        }
    
        public static ReturnT<String> fail(BusinessErrCode errCode) {
            return new ReturnT<String>(errCode.getCode(), errCode.getMsg());
        }
    
        public static <T> ReturnT<T> fail(String msg, T data) {
            return new ReturnT<T>(FAIL_CODE, msg, data);
        }
    
        public static <T> ReturnT<T> failData(T data) {
            return new ReturnT<T>(FAIL_CODE, SUCCESS.msg, data);
        }
    
        public static <T> ReturnT<T> succ(String msg) {
            return new ReturnT<T>(SUCCESS_CODE, msg);
        }
    
        public static <T> ReturnT<T> succData(T data) {
            return new ReturnT<T>(SUCCESS_CODE, SUCCESS.msg, data);
        }
    
        public static <T> ReturnT<T> succ(String msg, T data) {
            return new ReturnT<T>(SUCCESS_CODE, msg, data);
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public T getContent() {
            return content;
        }
    
        public void setContent(T content) {
            this.content = content;
        }
    
        @Override
        public String toString() {
            return "ReturnT [code=" + code + ", msg=" + msg + ", content=" + content + "]";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    8.工具类规范

    • 自定义工具类,尽量不要在业务代码里面直接调用第三方的工具类,能更好的与代码逻辑解耦。
    • 尽可能的抽象,编写使用范围更大的工具类

    举例,假设我们写了一个判断arraylist是否为空的函数,一开始是这样的:

    public static boolean isEmpty(ArrayList<?> list) {
      return list == null || list.size() == 0;
    }
    
    • 1
    • 2
    • 3

    当有其他类型的集合也需要该方法的时候,就显得很无力。可以改成最抽象的一层去实现,例如:

    public static boolean isEmpty(Collection<?> collection) {
      return collection == null || collection.size() == 0;
    }
    
    • 1
    • 2
    • 3

    改完后,所有实现了Collection都对象都可以用,适用范围变得更大。

    • 使用重载,提供各种类型的入参,调用起来方便,并尽量保证主体代码只有一份,方便维护。
  • 相关阅读:
    openfeign返回消息报错.UnknownContentTypeException
    Riccati 方程求解
    知识增强的大语言模型
    Autosar CAN硬件配置-Tc27x基于Davinci Cfg
    面试:插件化相关---资源
    MySQL进阶05_索引_SQL优化
    Mac 如何选择 选择Pro还是Air
    算法入门(二):简单排序算法
    10 路由协议:西出网关无故人,敢问路在何方
    【Java】什么是过滤器链(FilterChain )?哪些场景可以使用过滤器链?
  • 原文地址:https://blog.csdn.net/qq_41570752/article/details/126243069