记一次前后端对接加部署经历
后端使用spring boot + mybatis, 本文也主要记录完成学校课程作业前后端对接时遇到的小问题和一些新的尝试,因此可能和业界大佬们相比应该low一些。本文不会贴上全篇的代码,仅作注意事项记录。
使用jwt token
-
maven 依赖:
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.3.0</version> </dependency>
-
前端用户登陆成功后,后端生成jwt token,本次小项目我们在返回中设置Authorization字段,值就是生成token。遇到一些需要认证的rest api需要带上该token,使用的也是Authorization字段,后端抽取该字段进行验证。因此后端需要进行url的拦截,可以实现spring的HandlerInterceptor接口进行token的验证。
- 如果token解析正常,那么将得到的用户信息通过
request.setAttribute("userInfo", userInfo)
传给controller - 如果token解析出错(例如被篡改/过期…),那么我们需要捕捉异常,并向前端返回json。因此我们在该类中使用到Jackson(spring boot直接注入就可用,使用默认的配置)来进行json的序列化,然后调用
response.getWriter().write(json)
并设置状态码返回给前端,还需要记住设置Content-Type为application/json;charset=utf-8。
- 如果token解析正常,那么将得到的用户信息通过
-
如果只是作上述的配置,那么还是不能正常运作,这涉及到跨域问题。
跨域问题
-
需要了解同源策略和跨域相关的问题,推荐阮一峰老师的博文,百度可搜。
-
如果发送的是复杂请求,需要发送需检查报文即OPTIONS报文,但是OPTIONS报文不会带上我们的token,此时我们定义的Interceptor就是把我们的请求过滤掉。解决方法是我们定义的Interceptor需要放行OPTIONS请求
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { if (request.getMethod().equals("OPTIONS")){ return true; } ... }
-
还需要配置跨域问题,使用spring boot的方法十分简单,不必像自己定义一个Filter,然后设置头,当然这也是一种可行的方案。顺便配置自己定义的Interceptor:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer{ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE") .allowedHeaders("*") .exposedHeaders("Authorization") .allowCredentials(false).maxAge(3600); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthorizationInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/registration", "/authentication"); } }
mybatis打印出执行的sql语句
-
可以协助调试,配置很简单,下面是控制台输出,还支持多种输出
mybatis: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mysql小问题
-
乱码/时区问题:
jdbc:mysql://localhost:3306/YOUR_DB_NAME?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
- 网上的有的设置
serverTimezone=UTC
,时间会比我们的北京时间慢8小时。
- 网上的有的设置
-
mysql客户端连接不上远程的阿里云上的mysql-server,错误代码10061
-
确保mysql跑起来,
sudo systemctl status mysql
-
确保阿里云开放mysql的端口(例如默认的3306)
-
确保使用的用户有远程登陆的权限,先ssh到阿里云,登陆上mysql,查看mysql.user可以看
SELECT * FROM mysql.\`user\`;
观察到有Host一栏,如果使用的用户登陆权限为
localhost
,那么肯定是不可以远程登陆的;如果是%
,那么说明该用户在任意的主机都可以连接登陆上。 -
mysql权限赋予详见官方文档:https://dev.mysql.com/doc/refman/8.0/en/adding-users.html 。记住修改完权限需要刷新权限:
FLUSH PRIVILEGES
-
如果上面都没问题,那么问题很可能就是mysql的配置中bind-address设置为localhost的原因,修改配置方法:https://zhuanlan.zhihu.com/p/29226399
-
spring boot配置文件
-
spring boot加载配置文件特性
- 高优先级文件的配置会覆盖低优先级文件的对应配置
- 配置文件之间是互补的关系
-
配置文件的优先级详见官方文档:https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-external-config
-
虽然我们本次小项目没有使用Profile-specific的配置方法,但是它应该是比较好的配置习惯,简单总结这种Profile-specific的配置方法
- applicaton.yml/application.properties中配置通用的的内容,并且设置spring.profiles.active属性,例如设置为dev
- 在application-{profile}.yml/application-{profile}.properties中配置各个环境的配置
-
例如spring boot提供的配置文件的特性是十分方便的,假设你的小伙伴打好jar包给你部署,而此时发现一些配置需要更改,因此有以下几种更改情况和选择
-
如果只是单纯切换profile,例如从test切换到prod,那么可以在启动jar包时加上命令行参数
--spring.profiles.active=prod
-
如果只是单纯改少量的配置,可以使用直接命令行参数,例如更改端口号为8081,
--server.port=8081
-
如果发现需要改大量配置,那么可以在jar包目录下新建application.yml/application.properties,那么jar包启动会加载这些配置,而由官方文档可知,在jar包外的配置会比jar包内优先级高
…
14. Application properties outside of your packaged jar (application.properties
and YAML variants).
15. Application properties packaged inside your jar (application.properties
and YAML variants).
… -
最最最笨的方法,你的小伙伴在项目内修改配置,重新打包
-
Nginx作反向代理
- 直接摆链接,location的匹配等等
- 本人也不算很熟悉nginx这个东东,不过也知道它还可以做很多事,负载均衡什么的,当然一个课程作业就不做这么复杂了。
其它
放弃使用swagger2作对接文档
- 其实现在想起使用swagger2也是挺好的,虽然对代码侵入性较高,但毕竟不用写繁琐的对接文档
- 同时有大佬安利blueprint,以后试一下
放弃使用spring aop作简单的业务权限验证
- 我们项目中某一些功能需要一定的业务权限,例如增加项目,需要队长的权限,原本想着利用spring的aop特性少些代码,一些需要权限的方法我们需要校验参数,例如参数中有用户的id,我们需要检验该用户是否为操作队伍的队长。但是因为我们最初设计不合理,或者说没有设计好就开始急于打代码,导致方法参数不一,如果使用aop的话写的代码量也不少,最终放弃。
想过放弃使用jwt token
- 菜鸡的愚见:使用jwt token可以防csrf,服务器也不用存用户信息,也不用考虑多机session共享(当然我们本次也不存在多机)的问题
- 其实本次项目感觉不了什么好处,只是图个新鲜试玩一下,也产生以下想法和疑问
- jwt token的playload需要稳定不变的,也意味着存不了会变得数据,存个用户id还是可以的
- 因此一些用户常用数据或者热点数据需要使用额外的储存,不使用session就使用redis等等之类,那这样想也没有减轻服务器的负担?
菜鸡大学生的感想
- 需求文档很重要
- 概要设计和详细设计很重要
- 数据模型很重要
- 有了上述,代码是水到渠成的事