Spring项目开发简单总结

本文最后更新于: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) {}

接受form参数

  • 使用@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=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com/xxx/xxx/xx/entity

使用也比较简单:

1
2
3
PageHelper.startPage(pageNumber, pageSize);	// 1. startPage
List<IndexDocVO> docList = documentDao.selectLatestUpdate(); // 2.Dao查询
return ResultUtil.getSuccessfulResult(ResultCodeEnum.SUCCESS, new PageInfo<>(docList)); // 3.使用PageInfo<>包装

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标签引入之后再使用

1
?callback=xxxfun

还可以使用Nginx做反向代理来解决跨域的问题。

11.正则表达式

表达式1:x.x.x版本匹配

1
^(\d+\.){2}(\d+)$

^$这个表示全字符匹配

表达式2:0.0.0版本的不匹配

为什么是不匹配呢?因为采用的是javax.validation.constraints.Pattern注解,这个注解的作用是,当字段不匹配regexp时就会出发异常,所以这里想要的效果是不匹配。

最初的版本是:

1
^((?!0+\.0+\.0+).)*$
  • ^$这个表示全字符匹配
  • (?!exp)是一种零宽断言,它表示某个位置之后不能匹配表达式exp。
  • 上述的正则可以匹配:
1
2
3
4
5
1
7
17
1111hello1234556
11.0.0

但是无法匹配:

1
2
10.0.0
100.0.0

变更:

1
^((?!^0+\.0+\.0+).)*$
  • 增加了一个符号^,这个符号限定开头。

https://www.cnblogs.com/macq/p/6597366.html

正则表达式中^的用法


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!