最最重要的是理解项目的逻辑,只有深刻理解了这个项目是干啥的,各个数据库之间有啥联系等逻辑才能更好的写代码。
我个人喜好按层分。Pojo,controller,service,Dao层。
层名 | 注释名 | 注释作用 |
Pojo | @Component | |
@Data | 产生get和set方法 | |
Controller | @RestController | @Controller+@RespondBody |
@RequestMapping | 设置默认访问路径 | |
@Api | Swgger注解 | |
@RequestBody | 把传进来的请求体转为对应的实体类 | |
@RequestParam | url里获取查询参数,例如?name=liming | |
@PathVariable | 获取路径参数,/depts/{id} | |
AOP,面向切面编程,就是把某个方法无痛前面加上一些命令,后面加上一些命令。
一般用*来省略包名和类名而不用..
方法参数一般用..
1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。5. 目标对象:Target,通知所应用的对象
(这样看来,连接点不一定是切入点,但切入点一定是连接点。)
定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解
@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常
一般在mapper里加AOP的annotation注解
再学一下javase的反射
获取joinPoint的方法签名(因为传进来的示例通常只有一个方法,所以直接获取到了需要的方法签名)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
获取该方法上的切面注解实例
切面注解名 小写切面注解名 = signature.getMethod().getAnnotation(自定义的切面注解名.class)
获取该切面注解实例的值,以便后续对连接点进行操作
OperationType operationType = autoFill.value();
获取到当前被拦截的方法(连接点)的参数--实体对象
Object[] args = joinPoint.getArgs();
因为被拦截的方法是我们自己定义的,所以参数有啥咱们门清,直接需要哪个参数就提取哪个参数
Object entity = args[0];
获取该实体对象的需要的方法,第一个参数是方法名,第二个是方法的参数的类型
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
利用反射将该entity属性赋值
setCreateTime.invoke(entity,now);
以上一系列操作是把mapper层传入的DTO内的属性进行修改,真正将DTO数据写入数据库仍需在sql语句中实现。
String objectName = UUID.randomUUID().toString() + extension; String filePath = aliOssUtil.upload(file.getBytes(), objectName); return Result.success(filePath);
文件上传功能直接调用接口得了,记得是返回个文件地址字符串就行。
分页查询时,PageHelper.starPage后跟mapper.pageQuery(DTO),查询结果返回的是许多VO,把这许多VO装到Page这个容器里,所以变成Page<VO> page = mapper.pageQuery(DTO)。把Page看成list就行。
page有getTotal和getResult方法,得到总数据条数和多条VO数据。
mapper层返回Page<数据库数据类型>类
service层返回的是PageResult类。return new PageResult(page.gettotal(),page.getResult);
controller层里,如果当前端传入的字段很多时,我们常常用DTO来作为controller的方法参数,此时不能给DTO加@RequestParam,因为spring会将传入的字段名和DTO的属性名一一对应,能对应上就给DTO的属性赋值。
如果我们不使用DTO,就想一个一个来创建方法参数,此时就要使用@RequestParam了。
总结,DTO不加@RequestParam。多个方法参数要使用@RequestParam
@RequestBody是接受json参数(请求体)的。例如客户端采用put方式,就会给服务端传送一个json数据{ "id": 12345 }。如果我们使用@RequestBody Long id是不对的,因为这个意思是Spring Boot 期望接收到的请求体是一个纯数字。但是收到的请求体一定是个json,所以我们应该创建个DTO来接收json数据。总结,客户端传来的json数据一定要用DTO接收。
@PathVariable接受路径参数。(URL里有{}占位符)
@RequestParam接受查询参数(URL后接?查询字符串)@RequestParam(“begin”)如果不加注释,也可以,但是要求客户端传来的查询参数名和controller的方法形参名一模一样。
Jwt令牌创建在controller层的登录方法里。
jwt令牌的claims可以填入啥内容由JwtClaimsConstant规定。
jwt的秘匙作用是和header和claims结合,生成signature。
controller层登录逻辑:
用传入得DTO调用service层,当service遇到用户名不正确或密码不正确等问题时,自会抛出异常。当都对得适合,service就会返回一个entity。之后再根据entity得id生成个hashmap的claims。然后再用jwtProperties的keysecret和ttl(过期时间)来制作jwt字符串。再把这个字符串传入VO,最后返回Result(VO)。
当前端传入请求时,会携带jwt令牌(除登录外),所以在任何请求前都要校验jwt令牌是否存在,jwt令牌是否正确。此时可以用interceptor拦截器。
AOP和拦截器
拦截器拦截的是URL,request,response。可以获取执行的方法的名称,请求(HttpServletRequest),可以控制请求的控制器和方法,但控制不了请求方法里的参数(只能获取参数的名称,不能获取到参数的值)
AOP拦截的是方法,可以获取执行方法的参数( ProceedingJoinPoint.getArgs() ),然后对方法的参数进行修改(比如给参数的属性赋值)
redis的opsForValue.get(key)在java中不仅可以取字符串,你放进去是啥就可以取出啥,但是要加强转 List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
全局工具类BaseContext,现在个人理解就是,因为它都用得static,所以是类变量和类方法,整个JVM里只有一个,所以可以全局调用。
因为用户端的显示是按照分类显示的,所以按照分类ID来把数据库内的菜品和套餐缓存到Redis内存。
用户端查询时,如果redis没有缓存,那么从数据库查,再插入到redis。如果redis有缓存,则直接返回缓存的数据。
管理端插入新数据,修改数据,删除数据时,防止redis和数据库数据不一致,进行删除redis内缓存操作。
chacheable一般用在用户端的查询上。但是当controller方法的返回值是Result时,redis里缓存的value就是Result。所以
cacheevict一般用在管理端的增删改上。
购物车用注释的时候的逻辑:
首先原来代码的list返回的是个Result,里面带着从数据库查询的数据。当给这个方法加上Cacheable后,每次调用list方法时都先从Redis里通过注释里设置的key查找有没有这个Result的value;如果有则直接让list方法返回这个Result;没有的话调用service的语句,正常走list的逻辑最后返回Result,然后把这个Result添加到redis里。
原来代码的save和delete都是修改了数据库内容,此时数据库内容和redis的内容就不一致了,需要将redis的内容和数据库内容同步。因为运行save和delete之后必定会运行list,所以只需要让save和delete之后把redis内容清空,再运行list时,就会因为redis内容为空,正常走list语句从数据库查询后,再将Result存入redis,此时redis就和数据库内容一致了。(利用了Cacheable的如果内存没有则从数据库查询的特性,巧妙使用cacheevict)
如果不用注释也可以直接调用redistemplate的语句对redis进行操作。
application.properties是springboot中属性注入的默认文件。
application.properties与 application.yaml都可以作为Spring Boot的配置文件,只是书写格式不同而已。但同级目录下读取的顺序是先读取application.properties,读取application.yaml。
开发SpringBoot应用的时候,通常程序需要在测试环境测试成功后才会上线到生产环境。而测试环境和生产环境的数据库地址、服务器端口等配置都不同。在为不同环境打jar包时,需要频繁的修改application.yml配置文件,十分麻烦。
可以采用创建多个配置文件的方法解决这一问题。
创建以下三个文件,配置不同环境的地址信息,存放在application.yml同一目录下:
application-dev.yml:本地开发环境
application-test.yml:测试环境
application-prod.yml:生产环境
默认情况下,一开始创建spring boot 项目只有application.properties文件,没有xxxx.yml文件。
其中application.yml存放公共配置,可通过修改active切换读取的配置文件,比如active: dev
改成active: test
就是将读取application-dev.yml改为application-test.yml,环境也从本地开发变成了测试环境
也就是先寻找active指定的配置文件里面的内容,如果没有找到则去找application.yml中的内容。
一般会在XxProperties类里使用@ConfigurationProperties(prefix = "sky.jwt")来进行属性注入。