创新实训2 初步了解项目架构以及项目技术细节

2021SC@SDUSC

SDUDOC架构

SDUDOC运用的技术包含:

  • Spring-boot
  • Spring-security
  • Mybatis
  • MonogoDB
  • Redis
  • Jwt
  • solr
  • POI
  • swagger
  • Hibernate

项目分为7个模块,分别是

  • search-engine

    搜索服务

  • sdudoc-security

    安全服务

  • sdudoc-mysql

  • sdudoc-mbg

    Mybatis自动生成代码集,具体原理还不了解

  • sdudoc-manager

  • sdudoc-doc

  • sdudoc-common

    提供基础API和基础工具包给其他模块

search-engine

因为本人还在学习Spring框架, 对于基于Spring的更高级的架构并不了解, 所以理解上可能存在偏差, 如果有什么错误我会及时改正

Config

首先先看config包, 包下有五个config.

InterceptorConfig
源码查看

项目中在search-engine模块配置了拦截器, 具体如下:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
            .addPathPatterns("/**");
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

拦截器的目的是要确定访问的接口是否有@LoginRequired注解, 如果有, 就说明这个服务需要先进行登录.

拦截器会先判断接口是否有@passwordToken注解,如果有则会跳过认证,否则就会检查是否有@UserLoginToken注解, 如果有则会取出请求中的token字段,并进行身份认证.

疑问
  1. 对于异常情况的处理, 这里使用的方法是抛出异常, 但是这些异常在哪里被catch呢?
进行的优化

在看源码时, 我发现了一处不合理的地方, 并做出了更改:

在认证用户时, 原来对于访问持久层得到的结果的处理是:

Optional<UmsUser> user = service.findById(userId);
if (user == null) {
 throw new RuntimeException("用户不存在,请重新登录");
}

但是在我观察了Optional的源码,当找不到用户时,并不是返回null, 而是isEmpty()方法为true, 文档如下:

Returns: the entity with the given id or Optional#empty() if none found.

故进行了以下更改:

Optional<UmsUser> user = service.findById(userId);
if (user.isEmpty()) {
 throw new RuntimeException("用户不存在,请重新登录");
}
CorsConfig

CORS(Cross-Origin Resource Sharing)跨域资源共享

源码查看

这里出现了一个之前没有见到过的注释 : @Bean

查看源码后得知, 该注释表示方法会返回一个Bean, 希望Spring Container去管理这个返回的对象.

Indicates that a method produces a bean to be managed by the Spring container.

通过源码得知, 跨域请求都会被允许, 允许请求方法为

  • GET
  • POST
  • PUT
  • DELETE
  • OPTIONS

并且设定有效时间为1小时.

DataSourceConfig
源码查看

DataSourceConfig定义了两个数据源, 以提供数据服务

Ds1Config
源码查看

这里用到的技术是JPA, 我之前并不了解这一技术, 经过查阅资料得知:

JPA :

JPA(Java Persistence API) 是 Java持久化API. 是Sun公司Java官方指定的一套ORM方案, 是一套标准, sun公司自己并没有实现

ORM :

ORM(object Relational Mapping) 对象关系映射

ORM希望做到直接通过对象来操作数据库, 而不用编写sql语句

项目中用到的JPA框架是 Hibernate

Hibernate

Hibernate是JPA的众多实现者中, 性能最好的.

我对于Hibernate进行了粗略地了解:

对于映射的实体集, 需要将JPA的映射注解写在实体类里面:

// 指定该实体类是一个基于JPA规范的实体类
@Entity
// 指定当前实体类关联的表
@Table(name = "...")
public class ... {
 // 声明属性为一个OID属性
 @Id
 // 指定主键生成策略
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 // 设置属性与数据库字段的关系, 如果属性名称和表的字段名称相同, 则可以不设置
 @Column(name="...")
 private Long id;

 ...
}

创建工具类JPAUtil, 获得操作对象 (EntityManager)

操作方法 :

// 已经获取到EntityManager em 和TransactionManager tm
tm.begin();
...
em.persist(...);
tm.commit();
// em.close();

// 删除操作
remove(...);
// 更新操作
merge(...);
// 查询操作
find(...);
Ds2Config
源码分析

(与Ds1Config类似, 用于声明ds2)

Controller

LoginController
源码分析

LoginController提供了11个接口, 分别是:

  • login 用户登录
  • register 用户注册
  • getMessage (直接返回"通过验证")
  • setPassword 设置密码
  • setNikename 设置昵称
  • setEmail 设置邮箱
  • setPhone 设置手机号
  • setSex 设置性别
  • setBirthday 设置生日
  • setAvatar 设置头像
  • getAvatar 获取头像

其中所有的接口都会调用service层的服务, 并通过简单的事务逻辑处理目标操作.

不过我在查看源码的时候发现了一些问题, 并作出了更改

重要发现

在观察源码的时候, 我发现serAvatar方法中有关于base64的处理, 经过查阅, 我了解到base64是一种编码技术, 能够将字符编码为64中基本字符, 网络上传输image就是基于base64编码.

看到这里, 我突然想起源码中有这么一段代码:

String img = data.getString("img").replaceAll(" ", "+");

String[] d = img.split("base64,");

if (d.length == 2) {
    String b = d[1];

    byte[] bs = Base64Util.base64Decode2Bytes(b);
    for(int i = 0 ; i < bs.length; ++i) {
        if(bs[i] < 0) {
            //调整异常数据
            bs[i] += 256;
        }
    }
    ...
}

我注意到了网络上传输的image格式为 “…;base64,[base64码]”

data:image/gif;base64,R0lGODlhkQAtAKIAAAAAAP///1a+5zfn9wAAAAAAAAAAACH5BAEAAAQALAAAAACRAC0AQAP/SLrc/jDKSau9uIrsxN5cAxJeSI5MmV6q4r1w3JKRDC/2W1Mz3/GoVO8UE2GGK+MEVQoKP04XKJqJBj+/DpUVGXgb3vAA/GWIz2EFekxIq8WLcnxNr8sddnd7bUn7v2V/e4BjhIaDg4WJinCBhWdvi5B/j5WIgolvelxCIU5bSRygV1kQOVoySyY0WkqfnKESr7GotDtStzi4OLNSSKgzvxZIpKC6rKKrysNWxstHzs+UjZWPmYbUlpOLbZds1YeU3mjj5OWObF1565Pg7nqQmuzz5PT2m6I3ykXPyzf8+fQd8XfMRItopqiQ0oBslUKEpgp6upJKRMVSEpccHOgi/9eDUVZqdRqGMSJGYR2JQDNWrJWtkR8hpnyZ0Ei0KTJ3meT1ZMqPnC6BlpzZ8VPFHEZt1Cgi7CIRgTGhPkVKVeFIJjUdgqojj52ye/H6iQ0hTh6iPWglzTFXj5u2b+rSxcUDTw6ftXceiBPkhtCcs2XNgutGTc1aTePOuQ3nd9pZCPjMbILzN2/YS265mZEQWfLkvJAjdx5LurTp0xpGoJSlNHXV1kthro4Jk8WpnheRAgN4wuCWhbxaC81I03bvfcSNf1SS+pnVfVAIJme4vDhtnRqnT5WqO3jS7z6Ys7a4lKn26g3FVwnZqVd460QzsnT6dLdH77+IURwqi796/P/ZnYcea/R1d9197wnoy08JwobfbMXsR5NUTyTkoC/0yZeeScONUgoTPag2HDIjJtdSgLO5NGB8x8WHxXj9bZhMSS++lGJsKqoEowoh9qhDQTdmpV6N2PGEHI4YXugfagEdxJtFr1FFwV1d2SENZZKYw+SWZIBFB15y/QWml1/OM6VdoGVywV7tvFWll2g14s2bZIZJJ5wTsJlYnOggpiY2jMiJyTt8vmVJN36iadafaXqWZaHWFMYHm435cQ1c9TBmaJlWHrJnaIYWGklf1iAWiGCZFZbWZImV9Y2mc4rWqKmwUsonYLVWQ5iqr1IGqK+5hjpNr2fWmWmWXA32jrEFzJL5QAIAOw==

所以这段内容是在提取base64码, 并且对其进行解码和处理异常数据.

进行的优化

在检测输入是否合法的步骤中, 原来的代码主要是通过对于字符串进行直接检验, 而我认为可以通过正则表达式来快速规定输入格式. 特别地, 这里更改了邮箱的部分.

  1. 在setEmail中, 我注意到了原来的代码对于email格式的检测仅仅是
if (email == null || email.equals("")) {
    return "邮箱不能为空";
}
if (email.indexOf('@') == -1) {
    ...
}

通过检测email中是否有@符号, 来判断email格式

这样的检测太过于粗糙, 于是我做了以下优化:

// 21/10/09 : 改为了使用正则表达式判断邮箱格式是否正确
final String pattern = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
final String patternWithChinese = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";

boolean isMatch = Pattern.matches(pattern, email) || Pattern.matches(patternWithChinese, email);

if (email == null || email.equals("")) {
    return "邮箱不能为空";
}
if (!isMatch) {
    return "邮箱地址格式错误";
}

通过正则表达式来判断邮箱格式, 更加合理且精确.

  1. 在登录逻辑处有一处冗余:
Optional<UmsUser> one = service.findOne(Example.of(user));
if (!one.isPresent()) {
    return "用户不存在";
}else ...

我进行了修改:

Optional<UmsUser> one = service.findOne(Example.of(user));
if (one.isEmpty()) {
    return "用户不存在";
}
  1. 在getAvatar中, 我发现了一段令人疑惑的代码:
byte[] data = new byte[in.available()];
while(in.read(data) == -1) break;

首先, 之前的我并不了解available() 方法是具体什么功能, 于是我查看了源码, 发现官方文档是这么描述的:

Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not.
It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.

结果却发现代码中使用available() 的返回值进行缓冲区的allocate, 于是我直接改掉了这块,

其次, 下面这个while循环确实令人很迷惑, 感觉这个while循环的目的就是为了执行一下in.read(data) ? 那能保证数据都被都出来吗?

于是我改为了标准的读法:

/*
   21/10/09
   原来写法:
   ```java
   	byte[] data = new byte[in.available()];
    while(in.read(data) == -1) break;
   ```
   源代码指出的缓冲区大小是in.available(), 但是现在注意到官方对于available()方法的一段注释:
   ···
   	Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not.
   	It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.
    ···
    指出不应该用available()的返回值来分配缓冲区大小
    故决定更改写法
*/
byte[] data = new byte[1024];
int len = -1;
while ((len = in.read(data)) != -1) {
    out.write(data, 0, len);
}

in.close();

out.write(data);
out.flush();
out.close();

总结

这次的阅读源码, 让我初步了解到了整个项目的架构, 以及巩固了之前学到的关于Spring框架的知识. 了解到了包括:

  • 自定义注解在项目中的灵活运用
  • Cors的概念和流程
  • JPA与ORM
  • Hibernate框架实现的基本使用方法
  • base64原理以及运用
  • 回顾IO流控制

这次分析了全部的config包下的内容, 以及Controller包下的一个Controller, 收获颇丰, 正所谓万事开头难, 在了解了模块的config包后, 我也更加熟悉了模块的整体架构.下次的目标就是整个search-engine.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值