本文最后更新于:2023年9月23日 晚上
项目开发简单总结
1.统一结果集封装 1 2 3 4 5 public class Result <T > { private int code; private String msg; private T data; }
给前端返回的结果都是通过如上的对象进行封装,统一json格式。
成功结果:
1 2 3 4 5 { "code" : 200 , "msg" : "操作成功" , "data" : null }
失败结果:
1 2 3 4 5 { "code" : 403 , "msg" : "参数不合法" , "data" : "2不存在!" }
结果形如:
既然统一结果集封装,那么状态码也应当统一,通过构造枚举类来统一状态码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public enum ResultCodeEnum { SUCCESS(200 , "操作成功" ), DATABASE_ERROR(402 , "数据库错误,请稍后再试" ), DATABASE_OPERATION_ERROR(402 , "数据库操作失败,请稍后再试" ), PARAMETER_ERROR(403 , "参数不合法" ), USERNAME_EXIST_ERROR(404 , "用户名已存在" ), ID_WRONGFUL_ERROR(405 , "id不合法" ); private Integer code; private String msg; ResultCodeEnum(Integer code, String msg) { this .code = code; this .msg = msg; } }
返回结果集应当会被频繁调用,可以构造工厂方法提供工具类方便程序员使用。
2.lombok的使用 虽然导师不推荐使用lombok,因为“它会降低可读性、破坏封装性” 。
但是使用起来还是挺舒服的,在项目中主要用到的地方在于实体类和Dto之中,用于生成getter、setter方法、toString方法、无参构造等常见的模板方法,以及需要打日志的地方使用@Slf4j类引入日志对象。
在需要打日志的类上添加注解@Slf4j
,然后再需要的地方直接使用log对象来打日志即可。
3.参数校验–javax.validation.constraints 这是基于注解的参数校验方式,比以往的在controller层或者service层做参数校验简单方便。
使用这个注解之后,可以将参数检验的时机放在前端的传递的参数复制给后端的过程中。
controller层:
1 2 @PostMapping("/detail") public Result<Object> detailDocumentByVersion (@Validated @RequestBody DocCodeVersionDto docCodeVersionDto) {}
只有当传递JSON数据格式的时候才能确保可以校验,故应当加@RequestBody
注解来接受,同时要使用@Validated
注解来使用。
对应的Dto
1 2 3 4 5 6 7 8 9 10 11 public class DocCodeVersionDto { @NotBlank(message = "xxx不能为空") @Size(max = 100,message = "xxx过长,100字符以内") private String docCode; @NotBlank(message = "版本字段不能为空") @Pattern(regexp = "^((?!0+\\.0+\\.0+).)*$",message = "版本格式不合理,不能是0.0.0") @Pattern(regexp = "^(\\d+\\.){2}(\\d+)$",message = "版本不符合规则,应当x.x.x格式") private String docVersion; }
@NotBlank
:非空,也不能是空格,否则会报MethodArgumentNotValidException
异常,附带message中的信息。
@Size
:可以指定min、max来进行限定接受的长度。
@Pattern
:可以指定应当要匹配的正则表达式。
然后仅仅有参数校验是不够的,当参数校验发生问题的时候,会有异常然后返回bad request的结果。
观察返回就可以发现,这和上面的统一结果集封装想要的结果不一样,一种常见的作法就是捕获异常,然后进行异常处理。
4.全局异常处理 全局异常处理就是为了应对上面的这种情况而作的,全局捕获对应的异常,然后再统一封装结果集,返回给前端。
对于上面的异常MethodArgumentNotValidException
,创建出一个方法对应捕获即可。
1 2 3 4 5 6 7 8 9 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = MethodArgumentNotValidException.class) public Result<Object> inValidMethodArgsException (MethodArgumentNotValidException e) { String errMessage = e.getBindingResult().getAllErrors().get(0 ).getDefaultMessage(); return ResultUtil.getErrorResult(ResultCodeEnum.PARAMETER_ERROR, errMessage); } }
@RestControllerAdvice
注解用于开启全局的异常捕获,然后再使用注解@ExceptionHandler
定义要捕获的异常类型。
getDefaultMessage()
是获取自定义的异常提示信息,拿到这个异常信息可能需要摸索,可以通过调试的时候翻找来确认信息存储的字段。
除了因为参数校验而产生的异常,这里还可以捕获在程序中使用断言语句Assert
而产生的异常。
参考链接:https://www.jianshu.com/p/47aeeba6414c
5.Controller层 注解:@RestController
,相当于@RespomseBody
+@Controller
两者的结合,这样默认返回的都是json数据。
而仅仅使用@Controller
注解则返回的是对应的页面。
注解:@RequestMapping
用于地址映射,可以放在类名或者方法名之上。
@PostMapping
实际就是@RequestMapping(method=RequestMethod.POST)
.
接受json参数
使用@RequestBody
注解来接受JSON参数
1 2 @PostMapping("/detail") public Result<Object> detailDocumentByVersion (@Validated @RequestBody DocCodeVersionDto docCodeVersionDto) {}
使用@RequestParam
注解来标明待接受的参数的参数名称,是否必要以及默认值。
在对应的PostMan接口测试的时候,from的提交应当选择Body
中的x-www-form-urlencoded
,然后填写对应的数据。
1 2 3 4 @PostMapping("/updateOrderList") public Result<Object> listByLatestUpdate (@RequestParam(value = "pageNumber", required = false, defaultValue = "1") int pageNumber, @RequestParam(value = "pageSize", required = false, defaultValue = "15") int pageSize) { }
接受get参数
同样使用注解@RequestParam
指名参数名称即可,最终url会扩展成?xx1=1&xx2=2
的样子
1 2 @GetMapping("/detail") public Result<Object> detailDocumentByCode (@RequestParam(value = "docCode") String docCode) {}
6.Dao层 resultType和resultMap 因为数据库字段命名规范是小写下划线分割,而java实体类则是驼峰式,所以这就涉及到了映射的问题,直接使用resultType就会导致映射不上去,这时要么使用AS
语法起别名,要么使用resultMap方式。
根据时间倒序 1 2 3 SELECT * FROM `table_name` ORDER BY `change_time` DESC
模糊搜索 即使用LIKE
语句,再要搜索的内容前后加%
符号。
1 2 3 4 5 6 7 8 9 <select id="queryLike" parameterType="java.lang.String" resultMap="DocumentEntity"> SELECT * FROM `table_name` WHERE ( `doc_code` LIKE CONCAT('%',#{content},'%') OR `doc_name` LIKE CONCAT('%',#{content},'%') ) </select>
分页查询 本次使用PageHelper插件来进行分页查询。
application.properties配置:
1 2 3 4 5 6 7 pagehelper.helperDialect =mysqlpagehelper.reasonable =true pagehelper.supportMethodsArguments =true pagehelper.params =count=countSqlmybatis.configuration.map-underscore-to-camel-case =true mybatis.type-aliases-package =com/xxx/xxx/xx/entity
使用也比较简单:
1 2 3 PageHelper.startPage(pageNumber, pageSize); List<IndexDocVO> docList = documentDao.selectLatestUpdate(); return ResultUtil.getSuccessfulResult(ResultCodeEnum.SUCCESS, new PageInfo<>(docList));
startPage是在查询语句之前进行配置,同时要求查询的SQL语句中不能有分号;
的存在。
分页具体返回的结果(封装在结果集之中):
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 { "code" : 200 , "msg" : "操作成功" , "data" : { "total" : 17 , "list" : [ { "docName" : "xxx档" }, { "docName" : "xxxx0" }, { "docName" : "xxx6" }, { "docName" : "xxxxx5" }, { "docName" : "xxxxx" }, { "docName" : "xxxxx" }, { "docName" : "xxxx" }, { "docName" : "xxxx" }, { "docName" : "xxxxx" , }, { "docName" : "qq" } ], "pageNum" : 1 , "pageSize" : 10 , "size" : 10 , "startRow" : 1 , "endRow" : 10 , "pages" : 2 , "prePage" : 0 , "nextPage" : 2 , "isFirstPage" : true , "isLastPage" : false , "hasPreviousPage" : false , "hasNextPage" : true , "navigatePages" : 8 , "navigatepageNums" : [ 1 , 2 ], "navigateFirstPage" : 1 , "navigateLastPage" : 2 } }
7.Service层 dao层异常处理 service层主要是在调用dao层的方法,而dao层主要是数据库查询语句,难免会发生错误,所以关于dao的调用都需要使用try...catch{}
语句进行包裹。
1 2 3 4 5 6 7 8 9 10 public Result<Object> selectLatestUpdate () { try { PageHelper.startPage(pageNumber, pageSize); List<IndexDocVO> docList = documentDao.selectLatestUpdate(); return ResultUtil.getSuccessfulResult(ResultCodeEnum.SUCCESS, new PageInfo<>(docList)); } catch (Exception e) { log.error("[DocumentServiceImpl]: selectLatestUpdate(): An exception occurred while querying the database! pageNumber:{}, pageSize:{}." , pageNumber, pageSize, e); return ResultUtil.getErrorResult(ResultCodeEnum.DATABASE_ERROR); } }
如果不进行异常捕获,那么就会使得整个程序崩掉。
事务回滚 当要进行多个数据库操作时,其中一个出现了问题,那么就需要将所有的数据库操作进行回滚。
在本次项目中,关于两张表查询修改的模块是需要进行事务处理的,事务回滚触发的条件就是发生异常
事务注解,添加在方法之上:
1 @Transactional(rollbackFor = Exception.class)
但是经过了上面的try...catch{}
异常捕获之后,事务回滚就会失效,这是应当在`catch``语句中添加一行代码,使得事务可以捕获到异常。
1 2 3 4 5 6 7 8 9 @Transactional(rollbackFor = Exception.class) public Result<Object> updateDocument (EditDocDto editDocDto) { try { } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return ResultUtil.getErrorResult(ResultCodeEnum.DATABASE_ERROR); } }
8.Entity与Dto和Vo Entity是与数据库表相对应的实体,一个属性对应数据库表的一列。
Dto(Data Transfer Object):数据传输对象,个人理解就是用于传输必要的字段而非整个Entity对象,这样也能隐藏部分表结构。
Vo(View object):显示层对象,主要用于后端向前端展示数据用。
9.日志规范 本次中最主要的日志都写在了Service层。
1.日志需要有辨识度,建议日志原因+变量名+变量值,例如log.info(“日志原因是xx校验不通过,变量名:{}.”, 变量值);
2.日志需要打印出来的核心变量初始化必须在局部域以外完成,例如 if else,try catch
3.每个单独的分支或者所有分支之前必须有log,尤其是return的情况
主要的变量信息还是要有的。
如果要打印对象各个参数的信息,只要这和对象有toString()
方法,那就直接打印对象即可。
在catch
语句中的log要打印出对应的错误日志,这里捕获的异常e
是不需要使用占位符的,直接附加到日志的最后即可。
1 log.error("[DocumentServiceImpl]: updateDocument(): An exception occurred while querying the database! editDocDto:{}" , editDocDto, e);
10.跨域 CORS disable
在开发中往往会出现,前端页面在一台服务器上,而后端提供的接口又在其他服务器上,从前端获取接口数据的时候就会出现跨域问题。
同一服务器的不同端口之间访问也会出现跨域问题。
解决方式1:
设置响应头Access-Control-Allow-Origin
为*
,表示接受任何域名的访问。
解决方式2:josnp(JSON with Padding)
一个现象就是,通过href或者src请求的js脚本、css样式表、图片和视频文件是不存在跨域问题的,而通过Ajax请求的数据才存在跨域问题。
jsonp就是利用script
标签的src属性不受同源策略限制,向不同域发http请求,得到一段可以执行的js,这段js将响应数据作为参数调用回调函数。
jsonp就是在一端包装一个函数,另一端通过script
标签引入之后再使用
还可以使用Nginx做反向代理来解决跨域的问题。
11.正则表达式 表达式1:x.x.x版本匹配
^$
这个表示全字符匹配
表达式2:0.0.0版本的不匹配 为什么是不匹配呢?因为采用的是javax.validation.constraints.Pattern
注解,这个注解的作用是,当字段不匹配regexp
时就会出发异常,所以这里想要的效果是不匹配。
最初的版本是:
^$
这个表示全字符匹配
(?!exp)
是一种零宽断言 ,它表示某个位置之后不能匹配表达式exp。
上述的正则可以匹配:
1 2 3 4 5 1 7 17 1111hello1234556 11 .0 .0
但是无法匹配:
变更:
https://www.cnblogs.com/macq/p/6597366.html
正则表达式中^的用法