写在前面
起因是这样的,当我准备了很长时间八股文准备找一段实习工作时,我接到了蔚来的面试,面试官的一个问题让我大脑瞬间就宕机了——你这个项目是怎么实现序列化的?我回想起八股文中序列化是什么,在什么地方需要使用序列化,为什么不推荐使用JDK自带的序列化......可我的项目中到底那里使用了,怎么使用的序列化哪?
于是便有了今天这篇文章,我相信很多人像我一样,跟着某颜色的马敲了一遍外卖项目,但是到头来对这个项目却是一窍不通,现在如果抛开这个,让你自己设计一个项目,你会怎么做哪?你可能知道个大概,比如什么使用MVC架构、redis做缓存、MyBatis做持久化,可具体的细节你又要怎么实现?比如那部分需要做持久化,怎么做;要不要搞个统一的异常处理,怎么搞。那么下面便跟着我来一起梳理一下这整个外卖项目是怎么实现的,都做了哪些操作以及做这些操作的原因吧!
概述
首先我们可以看到整个项目分为了3层的结构,分布是:common,pojo,server,我先来讲解一下这3层的大致构造:
common:存放公共类,也就是项目中server层共同使用的部分,常见的比如String常量来定义的各种字段,下面是一个简单的例子:
public class MessageConstant {
public static final String PASSWORD_ERROR = "密码错误";
public static final String ACCOUNT_NOT_FOUND = "账号不存在";
public static final String ACCOUNT_LOCKED = "账号被锁定";
public static final String ALREADY_EXISTS="已存在";
}
通过这种方法就可以让我们在项目中直接使用英文字符而不需要打字,虽然这么看着有点脱裤子放屁,但是这种做法确实有一定道理,比如将来需要对提示信息之类进行修改时就不用到项目中去挨个找了,实现了一个解耦。
pojo:存放实体类,也就是普通java对象,MVC架构的model便对应这部分,这里有
- dto(数据传输对象)数据在代码间互相传递而使用的对象,主要用于服务层和控制器层之间的数据传输,减少网络传输的数据量,提高性能。
- entity(实体对象)也就是对应这数据库的哪些表和字段的部分
- vo(视图对象)对应着在前端界面上显示的那些部分,封装了从后端传递到前端的数据。
为什么要分成这三个部分哪,直接使用一种对象怎么样?这也体现了一个解耦的好处,通过设置成3种不同的模块便让安全性和性能都有一定的优化,比如在使用VO来向前端传递数据时就可以把那些不必要的信息或者没有用的信息给过滤掉,传的少了响应速度变高了。至于在什么位置用了哪种对象我将在server层详细指出
serve:最核心的部分,整个后端的服务都在这里,包括什么员工,管理员登录;用AOP做的公共字段处理;JWT认证等等,大家如果是跟着视频来敲的话也多数是这部分
下面我便来详细讲解一下这3个部分都写了哪些内容:
common
先总览一下这部分都分为了哪些内容:
- constant:也就是前文所说的String常量,其中包括:公共字段填充相关常量,JWT令牌中使用到的常量,信息提示常量类,密码常量(其实只有一个默认密码123456),还有状态常量表示(启用禁用)
- context:这里面只定义了一个类用于线程空间ThreadLocal的管理,可以看到他把set,get方法都进行了封装,这便是一种规范化,如果直接调用不封装的话,谁知道你在里面存放的是什么东西,提一嘴这里面的ThreadLocal实际上存放的是解密后的jwt令牌,用来实现令牌校验优化的
- enumeration:使用了一个枚举类,使用枚举的地方还真不多这里应该是唯一处了,这个枚举只定义两个字段update,insert用来记录数据库操作类型
- exception:统一的异常处理部分,可以看这里面有非常多的类,但每个类只提供了2个方法,一个是无参构造,另一个有参构造方法(String类型的msg用来传递异常信息),每个类都继承了BaseException,而其又继承自RuntimeException,也就是对运行时异常的封装处理
- json:将java对象转为json和将json转为java对象的部分(也就是面试官问我你项目怎么实现序列化的部分,沟槽的原来在这里),此类继承 ObjectMapper,这是 Jackson 库中的一个核心类,用于 JSON 的序列化和反序列化。这里面定义了三个String静态常量,这种写法是对时间显示形式的规范定义方法;在构造方法中分为了3个部分:1. configure 方法, ObjectMapper 类提供的一个方法,用于配置 ObjectMapper 的行为,这里面的第一个参数实际上是父类提供的枚举值并非是我们定义的,第二个参便是告诉他不启用这个属性,既收到未知属性时不报异常;2. 同理设置为反序列化时的属性不存在兼容处理,解释一下这个this.xx是什么操作,这是指当前类的实例和对其使用的方法;3. 剩下的这一大串实际上是注册的一个自定义序列化模块,这个模块是对时间信息的序列化和反序列化,addSerializer 方法(序列化方法java对象转json)有两个参数:type:要序列化类型的class对象,serializer:自定义的序列化器实例,这里使用new来新建的。而addDeserializer 方法便是反序列化,最后将这个自定义的模块进行注册使其生效。通过这个部分便能实现前端的传过来的一个json数据要如何与java中的对象来做对应和如何把一个java对象转为json发给前端。在这里我们只是对时间和日期的格式进行了单独的处理,而其他部分Jackson所提供的功能已经足够满足我们使用了
- properties:这是用来做部分配置的,比如阿里的OSS,微信的小程序,还有jwt令牌相关,以jwt为例,这里用到了3个注解 @Component 和 @Data 这两个应该都懂吧,注册bean和生成set,get 那些 ,而@ConfigurationProperties指向了配置文件application.yml的jwt字段,这样的话便让String变量和文件中设置的属性相互对应也就是右边那个(多提一嘴,在go中通常是用viper来做这些的,结果现在才发现spring把这些都集成起来了真是挺强大的)
- result:分为了2个类,第一个类是对分页查询结果的封装,分别使用了@Date ,@AllArgsConstructor 和 @NoArgsConstructor 注解,用途为自动生成一个包含所有字段的构造方法和一个无参的构造方法。并且实现了 Serializable 接口,使该类的对象可以被序列化,便于在网络传输或持久化存储中使用。第二个类是对返回结果的统一处理,这里统一定义了Result<T>泛型类,其包含3个字段分别是Integer的code,String的msg和T的data,并提供了有参和无参的两个success方法和error方法,在server层的controller中,我们都是使用其作为返回值来表示是否执行成功并返回数据给前端。
utils:工具类
一共有4个类,其中阿里oss的请求文件上传等和微信小程序的服务部分这里不做展开,这里详细讲解一下jwt认证部分
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥 《这部分是为了注释此方法的传入参数是什么等》
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return build
标签:String,对象,jwt,一字一句,读懂,外卖,使用,序列化,public
From: https://blog.csdn.net/2401_83455984/article/details/143996932