开发前期准备工作
文章目录
- 开发前期准备工作
- 0 代码规范
- 0.1 强制
- 0.2 推荐
- 0.3 参考
- `dao`:跟数据库打交道
- `service`:业务层,人类思维解决
- `controller`:抽象化
- 0.4 注释规范
- 0.5 日志规范
- 0.6 专有名词
- 0.7 控制层
- 统一异常
- 统一结构体
- 控制层
- 提示信息
- 0.8 控制语句
- 1 `idea`配置
- 2 `swagger`配置,`knife4j`
- 3 `jrebel`配置
- 4 常用账号汇总
- 5 集成单元测试
- 6 新建个人项目
- 7 断言
0 代码规范
0.1 强制
避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可理解性降低。
0.2 推荐
好的命名、代码结构是自解释的,注释力求精简准确、表达到位
0.3 参考
dao
:跟数据库打交道
[增加]
insert
:插入
batchInsert
:批量插入
insertSelective
:条件插入
insertUseGeneratedKeys
:
[删除]
delete
:删除
delete*ById
:根据主键删除
[修改]
update
:更新
update*ById
:根据主键更新
[查询]
service
:业务层,人类思维解决
add
:添加
find*ById
:根据主键查询
find*By**
:根据条件查询
find*ByList
:多条件查询
modify
:修改
modify*ById
:根据主键修改
remove
:删除
remove*ById
:根据主键删除
controller
:抽象化
insert
:插入
batchInsert
:批量插入
queryOne
:查询单个
queryById
:根据主键查询
queryByList
:多条件查询
count
:计数
update
:更新
update*ById
:根据主键更新
delete
:删除
delete*ById
:根据主键删除
0.4 注释规范
1 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐
2 所有的类都必须添加创建者和创建日期,而日期的设置统一为 yyyy/MM/dd
的格式
/**
* @author pingmingbo
* @date 2022/11/11
*/
3 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改
4 好的命名、代码结构是自解释的,注释力求精简准确、表达到位
5 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记
// TODO pingmingbo 20221112 20221211
0.5 日志规范
1【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String
字符串的拼接会使用 StringBuilder
的 append
()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
2 敏感信息不能记录在日志里面,比如用户名和密码
3 日志打印禁止直接使用JSON
工具将对象转换String
4 使用warn
级别日志记录用户输入参数错误情况,避免用户投诉时候,无所适从;注意日志输出级别,
error
级别只记录系统逻辑错误、异常等重要错误信息,如非必要,请不要在此场景打出error
级别日志
5 日志输出时候,字符串之间的拼接使用占位符方式
logger.info("traceId:[{}],and symbol:[{}]",id,symbol);
说明:因为String字符串的拼接会使用StringBuilder的append方式,有一定的性能损耗,使用占位符只是替换动作,可以有效提升性能
6 如果循环体次数过多,避免打印不必要的日志
7 打印日志的时机
- 当遇到问题,只能通过
debug
定位 - 碰到
if else
或者Switch
分支语句,需要在各自分支首行打印日志,确定进入哪个分支 - 经常以功能为核心进行开发,提交代码之前,可以确定通过日志看到整个流程
8 日志占位符基本格式,通过[]进行参数变量隔离
logger.info("traceId:[{}],and symbol:[{}]",id,symbol);
9 线上禁止使用System.out,System.err
10 谨慎使用e.printStackTrace(),
否则出现锁死,服务挂掉问题
短时间大量请求访问该接口,代码异常,进入catch,打印e.printStackTrace()异常信息到控制台,
这些错误堆栈字符串存储字符串缓存池内存空间,该内存空间一下子撑爆了,其他线程进行相互等待,
堆积一定程度,整个应用就挂掉了。
11 异常日志打印规范
logger.error("异常信息 ",e);
/**
* 处理异常几种方式
*/
@Test
public void testHandleCatch() {
int num = 0;
try {
num = 3 / 0;
logger.info("num : [{}]", num);
} catch (Exception e) {
logger.error("异常信息 ",e);
}
}
0.6 专有名词
DO
:数据库表对应的pojo
实体类,字段一一对应,通过DAO
向上传输
DTO
:数据传输对象,service
或者manager
向外传输
BO
:业务对象,service
层
Query
:数据查询对象,各层接收上层查询请求,注意超过两个参数进行封装,禁止使用map
传递
VO
:通常是web
层向模板引擎传输对象
0.7 控制层
统一异常
package com.geekmice.springbootselfexercise.exception;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise.exception
* @Author: pingmingbo
* @CreateTime: 2023-08-10 23:07
* @Description: 自定义异常
* @Version: 1.0
*/
public class BusinessException extends RuntimeException {
private String message;
public BusinessException(String message) {
super(message);
}
}
package com.geekmice.springbootselfexercise.exception;
import com.geekmice.springbootselfexercise.utils.AjaxResult;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise
* @Author: pingmingbo
* @CreateTime: 2023-08-10 22:34
* @Description: 统一异常处理
* @Version: 1.0
*/
@RestControllerAdvice(annotations = {Validated.class})
public class CommonExceptionHandler {
/**
* 用于捕获@RequestBody类型参数触发校验规则抛出的异常
*
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public AjaxResult handleValidException(MethodArgumentNotValidException e) {
StringBuilder sb = new StringBuilder();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
if (!CollectionUtils.isEmpty(allErrors)) {
for (ObjectError error : allErrors) {
sb.append(error.getDefaultMessage()).append(";");
}
}
return AjaxResult.error(sb.toString());
}
/**
* 用于捕获@RequestParam/@PathVariable参数触发校验规则抛出的异常
*
* @param e
* @return
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public AjaxResult handleConstraintViolationException(ConstraintViolationException e) {
StringBuilder sb = new StringBuilder();
Set<ConstraintViolation<?>> conSet = e.getConstraintViolations();
for (ConstraintViolation<?> con : conSet) {
String message = con.getMessage();
sb.append(message).append(";");
}
return AjaxResult.error(sb.toString());
}
@ExceptionHandler(value = BindException.class)
public AjaxResult handleConstraintViolationException(BindException e) {
StringBuilder sb = new StringBuilder();
List<ObjectError> allErrors = e.getAllErrors();
for (ObjectError allError : allErrors) {
String defaultMessage = allError.getDefaultMessage();
sb.append(defaultMessage).append(";");
}
return AjaxResult.error(sb.toString());
}
@ExceptionHandler(value = Exception.class)
public AjaxResult exception(Exception e) {
return AjaxResult.error(e.getMessage());
}
/**
* 自定义业务异常
*
* @param e
* @return
*/
@ExceptionHandler(value = BusinessException.class)
public AjaxResult exception(BusinessException e) {
return AjaxResult.error(e.getMessage());
}
}
统一结构体
package com.geekmice.springbootselfexercise.utils;
/**
* @BelongsProject: spring-boot-scaffold
* @BelongsPackage: com.geekmice.sbparamsvalidated.util
* @Author: pingmingbo
* @CreateTime: 2023-04-19 11:34
* @Description: 自定义统一返回结果
* @Version: 1.0
*/
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
/**
* 操作消息提醒
*
* @author ruoyi
*/
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
public static final String CODE_TAG = "code";
/**
* 返回内容
*/
public static final String MSG_TAG = "msg";
/**
* 数据对象
*/
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult() {
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (ObjectUtils.isNotEmpty(data)) {
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success() {
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data) {
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg) {
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data) {
return new AjaxResult(HttpStatus.OK.value(), msg, data);
}
/**
* 返回错误消息
*
* @return
*/
public static AjaxResult error() {
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data) {
return new AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(int code, String msg) {
return new AjaxResult(code, msg, null);
}
/**
* 方便链式调用
*
* @param key 键
* @param value 值
* @return 数据对象
*/
@Override
public AjaxResult put(String key, Object value) {
super.put(key, value);
return this;
}
}
控制层
package com.geekmice.springbootselfexercise.controller;
import com.geekmice.springbootselfexercise.annotation.MethodExporter;
import com.geekmice.springbootselfexercise.utils.AjaxResult;
import com.geekmice.springbootselfexercise.vo.ParamVO;
import com.geekmice.springbootselfexercise.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise.controller
* @Author: pingmingbo
* @CreateTime: 2023-08-10 22:29
* @Description: TODO
* @Version: 1.0
*/
@RestController
@RequestMapping(value = "param")
@Slf4j
@Validated
public class ParamController {
@GetMapping(value = "getMethod")
public AjaxResult getMethod(@Valid ParamVO paramVO) {
return AjaxResult.success();
}
@PostMapping(value = "postMethod")
public AjaxResult postMethod(@Valid @RequestBody ParamVO paramVO) {
return AjaxResult.success();
}
}
package com.geekmice.springbootselfexercise.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise.vo
* @Author: pingmingbo
* @CreateTime: 2023-08-10 22:28
* @Description: 入参VO
* @Version: 1.0
*/
@Data
public class ParamVO {
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
private String userName;
/**
* 生日
*/
@NotNull(message = "生日不为空")
private Date birthday;
/**
* 性别
*/
@NotBlank(message = "性别不为空")
private String sex;
/**
* 地址
*/
private String address;
/**
* 分数
*/
private Integer score;
}
提示信息
{
“msg”: “生日不为空;性别不为空;用户名不能为空;”,
“code”: 500
}
0.8 控制语句
1 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。
说明:注意 break 是退出 switch 语句块,而 return 是退出方法体。
switch(str){
case str1:
// 业务代码
break;
case str2:
// 业务代码
break
default:
break;
}
2 三目运算符 condition? 表达式 1 : 表达式 2
中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常
说明:以下两种场景会触发类型对齐的拆箱操作:
1) 表达式 1 或表达式 2 的值只要有一个是原始类型。
2) 表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型
3 当某个方法的代码总行数超过 10 行时,return / throw 等中断逻辑的右大括号后均需要加一个空行。
说明:这样做逻辑清晰,有利于代码阅读时重点关注。
4 除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
说明:很多 if 语句内的逻辑表达式相当复杂,与、或、取反混合运算,甚至各种方法纵深调用,理解成本非常高。如果赋值一个非常好理解的布尔变量名字,则是件令人爽心悦目的事情。
5 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)
6 避免采用取反逻辑运算符
1 idea
配置
博客地址
2 swagger
配置,knife4j
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
<!--接口平台-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
配置类
package com.geekmice.springbootselfexercise.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* @BelongsProject: spring-boot-scaffold
* @BelongsPackage: com.geekmice.sbhelloworld.com.geekmice.sbpagehelper.config
* @Author: pingmingbo
* @CreateTime: 2023-07-30 15:45
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class Knife4jConfig {
@Bean(value = "defaultApi2")
public Docket customDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.geekmice.springbootselfexercise.controller"))
.build();
}
/**
* 构建 api文档的详细信息函数
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("现货交易")
.version("1.0.0")
.description("现货交易详情")
.contact(new Contact("geekmice","http://geekmice.cn","2437690868@qq.com"))
.build();
}
}
控制层
package com.geekmice.springbootselfexercise.controller;
import com.geekmice.springbootselfexercise.injector.EasySqlInjector;
import com.geekmice.springbootselfexercise.utils.AjaxResult;
import com.geekmice.springbootselfexercise.utils.SpringUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise.controller
* @Author: pingmingbo
* @CreateTime: 2023-08-09 21:52
* @Description: bean操作
* @Version: 1.0
*/
@RestController
@RequestMapping(value = "bean")
@Api(tags = "3.获取bean操作")
@Slf4j
public class BeanController {
@GetMapping(value = "getBean")
@ApiOperation(value = "获取bean")
public AjaxResult getBean() {
// 根据class获取bean
EasySqlInjector bean = SpringUtil.getBean(EasySqlInjector.class);
// 根据name获取bean
EasySqlInjector easySqlInjector = (EasySqlInjector)SpringUtil.getBean("easySqlInjector");
// 根据name和class获取bean
EasySqlInjector easySqlInjectorSecond = SpringUtil.getBean("easySqlInjector", EasySqlInjector.class);
log.info("easySqlInjectorSecond : [{}]" , easySqlInjectorSecond);
log.info("easySqlInjector : [{}]", easySqlInjector);
log.info("bean : [{}]", bean);
return AjaxResult.success();
}
}
3 jrebel
配置
4 常用账号汇总
git账号
系统用户账号
开发环境 MySQL
5 集成单元测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
与事务相关的测试类
package com.geekmice.springbootselfexercise;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.geekmice.springbootselfexercise.config.DataSourceProperties;
import com.geekmice.springbootselfexercise.dao.UserDao;
import com.geekmice.springbootselfexercise.domain.UserDomain;
import com.geekmice.springbootselfexercise.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @description 测试类,需要调用dao
*/
@Slf4j
@SpringBootTest(classes = SpringBootSelfExerciseApplication.class)
@RunWith(SpringRunner.class)
class DaoTest {
private String port;
private static String newPort;
@Value("${server.port}")
public void setPort(String port){
newPort=port;
}
@Autowired
private DataSourceProperties dataSourceProperties;
@Autowired
private Environment environment;
@Resource
private UserService userService;
@Resource
private UserDao userDao;
@Test
void contextLoads() {
// log.info("端口号:【{}】",port);
String username = dataSourceProperties.getUsername();
String password = dataSourceProperties.getPassword();
String url = dataSourceProperties.getUrl();
String driverClassName = dataSourceProperties.getDriverClassName();
log.info("用户名:【{}】",username);
log.info("密码:【{}】",password);
log.info("地址URL:【{}】",url);
log.info("驱动类:【{}】",driverClassName);
}
}
与事务无关的测试类
package com.geekmice.springbootselfexercise;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.geekmice.springbootselfexercise.domain.TempData;
import com.geekmice.springbootselfexercise.domain.UserDomain;
import com.geekmice.springbootselfexercise.utils.BigDecimalUtil;
import com.geekmice.springbootselfexercise.utils.DateUtil;
import com.geekmice.springbootselfexercise.utils.FileUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* @BelongsProject: spring-boot-self-exercise
* @BelongsPackage: com.geekmice.springbootselfexercise
* @Author: pingmingbo
* @CreateTime: 2023-08-06 09:27
* @Description: 无事务
* @Version: 1.0
*/
@Slf4j
@SpringBootTest
public class NoDaoTest {
/**
* 解决map修改key问题
*/
@Test
public void testSetMapKey() {
log.info("需求:k1变为k2,value不变");
Map<String, Object> map = new HashMap<>();
map.put("k1", "v1");
log.info("修改前");
for (Map.Entry<String, Object> entry : map.entrySet()) {
log.info("key:【{}】,value:【{}】", entry.getKey(), entry.getValue());
}
String pendingItem = map.get("k1").toString();
map.remove("k1");
map.put("k2", pendingItem);
log.info("修改后");
for (Map.Entry<String, Object> entry : map.entrySet()) {
log.info("key:【{}】,value:【{}】", entry.getKey(), entry.getValue());
}
}
}
6 新建个人项目
工具类,配置类,切面,过滤器,监听器,常量,统一异常,统一结构体,文件解析类,自定义注解,反射
7 断言
处理抛异常情况,简单场景可以使用
逻辑复杂使用iflese,throw抛异常
// 对象
Assert.notNull(null, "对象不为空");
Assert.isNull(new Object(), "对象为空");
// 集合
Assert.notEmpty((Map)null, "集合不为空");
Assert.isTrue(CollectionUtils.isEmpty(testBuildData()), "集合必须空");
// 字符串包含
Assert.isTrue(StringUtils.contains("abc", "e"), "不能包含");
Assert.isTrue(!StringUtils.contains("abc", "a"), "必须包含");
// 表达式
Assert.isTrue(1>2, "条件表达式为false");