项目背景
在开学时学生会进行选课操作,对于部分热点课程来说容量是有限制的,所
以对这些课程可以设置一个开始时间和结束时间,在这个时间内才可以抢课
,当到达开始时间的时候,会有大量请求涌入,这个系统的核心就是着重处
理大量请求,尽可能多的承载容量
前后端分离架构
本质就是将一个应用的前端代码和后端代码分开写,传统的Javaweb开发中,前端
使用JSP开发,JSP不是由后端开发者独立完成的。前端写HTML 静态页面,后端将
HTML整合到 JSP,这种方式效率极低。前后端开发者只需要提前约定好接口文档
(URL,参数,数据类型…),然后独立开发。前后端分离的核心思想是前端页
面通过ajax调用后端的restuful api传递JSON数据进行交互
restuful api
基于REST构建的API就是Restful风格。REST:是一组架构约束条件和原则,
通俗说法就是URL定位资源,用HTTP动词(GET,POST,DELETE,PUSH等)
描述操作。设计思想就是RestfulAPI就是由后台(SERVER端)来提供接口,
前端来调用。前端调用API向后台发起HTTP请求,后台响应请求将处理结果
反馈给前端。也就是说Restful 是典型的基于HTTP的协议
Web服务器和应用服务器的区别?
只有web服务器才能被外网访问,应用服务器只能内网访问
- Web服务器:一般指像nginx,apache这类的服务器,一般只能解析静态资源
- 应用服务器:一般指像tomcat jetty resin这类的服务器可以解析动态资源
也可以解析静态资源,但解析静态资源的能力没有web服务器好
前后端分离的好处?
- 可以实现真正的前后端解耦,前端页面异步调用后端的接口,后端/应用
服务器使用tomcat,动态资源和静态资源分开存放,减轻服务器压力 - 发现bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象。页面
逻辑,跳转错误,浏览器兼容性问题,脚本错误,页面样式等问题,全部由
前端工程师来负责。接口数据出错,数据没有提交成功,应答超时等问题,
全部由后端工程师来解决。双方互不干扰 - 在高并发情况下,我可以同时水平扩展前后端服务器
- 减少后端服务器的并发/负载压力。除了接口以外的其他所有http请求全
部转移到前端nginx上,接口的请求调用tomcat,且除了第一次页面请求外
,浏览器会大量调用本地缓存 - 即使后端服务暂时超时或者宕机了,前端页面也会正常访问,只不过数
据刷不出来而已 - nginx支持页面热部署,不用重启服务器,前端升级更无缝
- 前端大量的组件代码得以复用,组件化,提升开发效率
- 如果遇到跨域问题,spring4的CORS可以完美解决,但一般使用nginx反
向代理都不会有跨域问题,除非你把前端服务和后端服务分成两个域名
使用框架
前端使用Vue+ElementUI+axios,后端使用SpringBoot+Mybatis。数据库
使用MySQL,基于IDEA编辑器使用Maven快速构建项目框架
功能
使用分层模型设计方式完成如下功能
- 学生输入学号获取注册码,然后根据注册码完成注册,然后跳转登录页
- 学生输入学号和密码进行登录,进入个人信息页,可以查看已经选择的课程
- 学生可以进入课程列表显示所有课程信息,点击某个课程就进入详细课程信息
- 课程有三种情况
- 第一种就是可以正常选课,比如必修课容量充足可以随时选择,只能选择1个
- 第二种就是选修课,根据课程信息有三种情况。第一种就是还未到选课时
间此时不能选课。第二种情况就是倒计时到达选课时间此时进行抢课可能
成功也可能失败。第三种就是选课时间结束无法选课
- 学生选完课程后可以返回到个人信息页面然后完成退出功能
分层结构
后端开发采用使用分层模型设计
- 用户界面层 用户看到的页面,由前端Vue框架单页面进行显示
- 服务层 向服务器发出请求后台接收请求,使用axios向后端发送请求
- 业务逻辑层 针对具体问题的操作
- 数据访问层 对数据库的操作
- 数据存储 数据库
Vue单页面应用
整个网页只有一个html页面,静态资源服务器中可能只有一个html文件
一个css文件,一个javascript文件,url和页面组件的映射关系是由
前端路由管理url发生改变时会抽取相应的组件,整个页面并没有刷新
在用户与应用程序交互时动态更新该页面的Web应用程序,不过首次加
载页面时需要加载大量的静态资源
后端项目结构
项目分解为多个Java包,每个包实现不同的功能
- config 这个包里面创建CrosConfig类,实现WebMvcConfigurer解
决跨域问题 - controller 对响应进行处理的部分,处理前端发送来的请求,然后
将请求的结果发送回前端 - dao Data Access Object(数据访问对象,DAO)即用来操作数据库
的对象。对应mapping的映射文件生成相应的接口类,这些接口中有基本的
增删查改方法,可以认为都是具有原子性的,具体实现功能由service完成 - dataobject mybatis-generator为数据库表自动生成的相关数据对
象会存放在这个包下,在mybatis-generator.xml文件中可以指定相应
数据对象的名字 - service 负责业务逻辑与功能相关的操作,接口中有相应的逻辑方法
,为了进一步解耦合在该包下创建实现子包impl具体实现相关逻辑 - response 定义通用的返回对象
- error 定义通用的返回对象,如果程序中出现问题就应该返回相应的错误信息
资源配置
resources目录下有三个小目录
- mapping 自动生成数据库表的映射文件放在这个包下
- application.properties 配置端口号和数据库连接池
- mybatis-generator.xml mybatis自动生成器自动生成工具,生成数据库
文件的映射,分别映射到dao dataobject mapping - pom.xml 导入所有的依赖
数据库文件的设计
学生表 密码表 课程信息表 课程容量表 学生密码盐加密表 交易表 选修课信息表
- 学生信息表 id name age gender telphone register_mode third_party_id
- 密码表 密码与用户主表信息需要分开存储,为了安全这里存储的密码是加密
后的密码而不是真实的密码 id encrpt_password user_id - 课程信息表 包括必修和选修课,课程信息和课程容量分开存储,包含已选课
人数的信息,如果有学生选课操作那么人数会加1 - 课程容量表 包括相应课程的剩余容量,如果有学生选课情况那么课程剩余容
量减1 - 选课交易表 包括交易流水号,用户id,课程id,选课数量这里设定为1,课程
类型 - 选修课系信息表 记录该课程开始时间、结束时间,课程id等
- 加盐表 保存每个学生的盐用于登录判断
数据模型
- UserDO 与数据库表字段完全对应
- UserModel 多个数据对象的融合
- UserVO 可以返回给前端的数据对象
返回正确的信息
定义通用的返回对象——返回正确信息。也就是规范responsebody的返回参数
程序不出错返回的就是正确的信息。如果后端产生了错误也要给前端返回有
意义的信息。
- 首先要归一化responsebody的返回参数,归化为一个统一的status+data
的格式。status就是返回的结果,success/fail。data就是要返回给前端的数
据,如果失败就是返回通用的错误码格式。response包创建CommonReturnType - 定义通用的返回对象返回错误信息。创建error包创建commonError接口
,定义一个错误格式,定义一个异常类并在方法后声明表示程序中可能出现
的异常,使用枚举类定义各种不同的错误信息,比如参数不合法、手机号不
存在、用户未登录等精确信息,如果程序出现错误就会返回错误信息 - 前端传到后端的信息都要经过 HtmlUtils.htmlEscape转化,对html
标签进行转义,防止XSS攻击
用户模块功能
- 根据id获取user 注意这里返回的是UserModel而不是UserDO,UserDO
是与数据库中表的字段完全对应的,但是在service不能直接将对应数据库
映射的userDO传给前端,这是需要一个model作为业务逻辑交互模型,这个
model中不止是userDO 中的所有字段,还包括用户的密码,密码也是属于
user的,所以这个model包含了完整的用户信息。控制层获取到usermodel
后也获取到用户的加密密码,但是不应该将model直接传给前端,这样就把
密码也传了过去,前端只需要拿到需要展示的数据即可,所以在控制层需要
再加一层模型对象传给前端相应的模型
request.setAttribute()和request.getSession().setAttribute()
- request.setAttribute(“num”,value) 有效范围是一个请求范围,不发送
请求的界面无法获取到value的值,只能在一个request内有效,如果重定向客
户端,将取不到值。
request在当次的请求的URL之间有效,比如,你在请求某个servlet,那么你
提交的信息,可以使用request.getAttribute()方式获得,而当你再次跳转
之后,这些信息将不存在 - request.getSession().setAttribute(“num”,value) 有效范围是一个
session周期,在session过期之前或者用户关闭页面之前是有效的,可以通
过sessionID得到自己的session,将参数存储在session中,即使重定向客
户端也没事,这个值可以在多个页面上使用。
比如访问一个网站,登录后用户信息被保存到session中,在session过期之前
或者用户关闭页面之前,用户信息可以通过request.getSession().
getAttribute()方式获得
注册流程
- 前端展示注册页面,发送学号获取otp验证码,然后跳转到注册页面
- 后端收到手机号,生成5位验证码,这里通过random.nextInt(90000)
+10000生成,random可以生成[0,n)的随机数。HTTP请求头中的所有信息
都封装在httpServletRequest,然后通过httpSession将手机号和验证
码以key-value的形式绑定,后端验证码返回给前端
1 | httpServletRequest.getSession().setAttribute(telphone, otpCode); |
- 后端将otpCode发送给前端,前端收到验证码之后跳转到注册页面
- 用户注册时候需要输入telphone otpCode name gender age password
- 前端将这些数据发送给后端,后端从httpSession中通过传入的学号
获取验证码,与传入的验证码进行比对,如果一致就调用service层的注
册功能,不一致就返回短信验证码不符合这条信息
用户信息加密
之前我们的用户信息都是明文存储在数据库中的,这样做有三大弊端
- 不安全 很多应用脱库后用户密码全网流传
- 用户也不希望我们知道他们的密码,我上不了你的号,但是我知道你的号
在干什么 - 如果用户在各个应用使用相同的密码,一个地方密码被盗则一连串被盗
所以我们要对用户信息进行加密,主要是用于验证的敏感信息,比如密码,
而且这种加密最好是不可逆的,明文密码只有用户知道。加盐是提高Hash算
法安全性的一个常用手段,本质是在密码后面加一段随机的字符串,然后再
hash
- 用户注册时,输入用户名密码,向后台发送请求
- 后台将密码加上随机生成的盐并hash,再将hash后的值存入数据库中,盐
也作为单独的字段存起来 - 用户登录时输入用户名密码,向后台发送请求,每个用户都有一个自己的盐
- 后台根据用户名查询出盐,和密码组合并hash,将得到的值和数据库中存
储的密码比对,若一致则通过验证
加盐为什么能提高安全性?
我们知道一个 hash 值(输出)可以对应无数输入,如果不加盐,找到一个和
明文密码 hash 结果相同的输入相对容易,但在有盐的情况下,如果不知道盐
,找到这种输入的难度就是炸裂性增长。
当然,如果别人有办法拿到数据库中以 hash 值存储的密码,拿到盐的信息也
是有可能的,但是由于不同用户盐不同,所以即使有很多用户使用了相同的密
码,存储在数据库里的 hash 值也不同,试图窃取信息的黑客只能一个一个
的去算,这才是加盐最大的意义所在。
insert和insertSelective
- insert就是正常的插入操作
- insertselective首先判断这个字段是否为null,如果为null就插入的是
默认值,在设计数据库表的时候最好不要设置null字段
注册功能实现
- 注册功能要么成功或者失败,不能出现添加学生信息成功但是添加密码
失败的情况,要加上@Transaction注解,抛出异常时会进行回滚1
2
public void register(UserModel userModel) - 然后对数据进行校验,使用validator类库自动对数据进行校验,在
相应字段上加上注解,比如@NotBlank @Min自动判断信息不能为空,还
有年龄最大最小值这些信息 - 密码采用加盐加密的方式,本质就是在密码后加一段字符串然后再hash,
hash后的值存入密码表中,盐也存入密码表中。盐通过RandomStringUtils
生成16位字符串,使用DigestUtils.md5Hex将字符串进行hash加密,然后
将数据插入到数据库学生表和密码表中,使用的是insertselective
登录流程
- 在前端登录界面输入学生号和密码,前端将数据发送给后端
- 在service层调用登录方法,首先根据学号获取用户信息,如果不存
在该用户那么说明没有注册这个学号,密码表中的外键userId和加盐表
中的外键userId就对应用户表中的主键id,将传来的密码和盐拼接再hash
与密码表中的密码进行比较,判断是否一致 - 用户登录成功后就将登录状态和用户信息保存在session中,进行抢课
操作时就会从session中取出登录状态和用户信息进行操作
创建课程
- 先思考如何创建模型再考虑创建相应的数据库表。一个课程肯定有一个
id title price stock description sales imgUrl 。考虑完以上这
些属性后就可以创键相应数据库表,stock与交易流水有关,每次对商品
表的操作就是对库存表的操作,所以新建一个库存表。一个课程有剩余容
量和已选人数两个重要信息,将剩余容量和课程分开存储,剩余容量与
交易流水有关,每次对课程表的操作就是对剩余容量表的操作,所以新
建一个容量表 - 前端将课程相关信息和管理员学号密码发送给后端,后端首先判断这个
学生是不是管理员,如果是那么可以调用service层的createItem方法
创建课程功能
- 创建课程也是一个事务级别的操作,要么成功要么失败,不能添加课程
信息成功但是添加课程容量信息失败。将课程信息和课程容量插入相应的数
据库表中即可
展示课程列表
- 在itemService中添加展示所有课程的功能,通过选课人数有高到低进行排
序,将课程信息存放在一个List集合中,使用 Stream 流将 ItemDO 转化为
temModel,也就是将课程信息和选课人数组合在一起存入List集合中返回1
2
3
4
5
6
7
8List<ItemModel> itemModelList = itemDOList.stream().map(itemDO ->
{
ItemStockDO itemStockDO = itemStockDOMapper.selectByItemId(
itemDO.getId());
ItemModel itemModel = this.convertModelFromDataObject(
itemDO, itemStockDO);
return itemModel;
}).collect(Collectors.toList());
创建限时选课模型
有些限选课存在限时选课的情况,也就是说学生不能任意时刻都能选择这门
课,只能保证在限定时间进行选课,在倒计时时间可以进行选课
- 先思考如何创建模型再考虑创建相应的数据库表。在课程表中没有区分必
修课和限选课,这个模型应该包括限选课程id,可以开始选课时间和结束时
间,以及课程学分,这个表就是promo表,这个表中的数据是由后端程序员
进行添加的,如果要把某一门选修课设置为限时选课,那么就将这么课的id
开始时间、结束时间和学分加入到promo表中 - 前端在进入一个课程详细页的时候就可以展示该课程目前是否能够进行
选课,这里有三种状态,用promoId表示
- 第一种就是该课程没未到选课时间 promoId表示1
- 第二种表示该课程正处于可以选课时间 promoId表示2
- 第三种表示该课程选课时间已经结束目前不能选课 promoId表示3
- 前端页面首先通过课程id获取课程信息,再从promo表中获取该课程的
活动信息,如果不存在就说明这是一门必修课在任意时间都可以选。如果存
在说明是限时限量的选修课,需要根据当前时间与这门课的开始时间来判断
可选状态,有1 2 3三种状态,这里是通过DateTime来判断
个人信息
- 登录成功后前端就会展示到学生的个人信息页面,显示已经选择的课程
- 前端将学生学号传入后端,后端接收到学号后就调用service层的获取
个人课程的方法getUserInfo
获取个人信息
首先会通过学号获取学生信息,数据库中有一个交易表,学生完成抢课后
会在交易表中生成相关的信息,保存用户id,课程id,课程学分等信息。
在这个表中通过用户id找到所有课程id,将课程的id等信息放入一个map
返回给前端,然后前端就可以根据一个列表展示学生所有的选课信息
登出功能
- 在前端个人页面有一个登出功能选项,前端会向后端发出登出请求
- 后端接收到登出请求后会从从Session中删除用户登录的信息,
session.removeAttribute
创建选课模型
- 首先考虑学生选课模型,一个选课模型应该具有 id userId itemId
orderPrice amount orderAmount。 orderPrice是课程学分,事实上
这个学分可能变化,所以orderPrice就表示当前选课时的学分,amount
表示选课数,设定为1。userId是学生Id,itemId是课程Id - 在课程列表中点击课程前端会向后端传送课程id数据,后端根据课程
id获取课程详细数据,包括课程名、课程学分和课程剩余容量和课程限时
活动状态,后端已经把promoId传给了前端,如果是1说明这么课是选修
课并且还不能进行选择,如果是2说明可以进行选择,这里通过每1秒中
调用一次setInterval函数判断当前时间与开始时间的关系来在前端更
新promoId - 选择选课功能就会进入选课下单流程,前端向后端发送课程Id号、数量
默认是1,还有最新的promoId值,不需要传送学号和密码 - 后端接收到前端传过来的请求,从httpServletRequest中获取session
对象,从关键字IS_LOGIN中判断学生是否已经登录,如果已经登录就再获取
用户的学号信息,在登录时就在session中保存了登录状态和当前学生信息。
如果学生还没有登录过向前端发送用户还未登录,不能下单的返回信息,前
端就会跳转到登录页面 - 后端调用service层的创建课程订单功能
创建课程订单功能
创建课程订单功能也必须保证是一个事务级别的功能,要么完全成功或失败。
一个订单的创建分为4大步
- 校验下单状态 也就是判断需要下单的课程是否存在,学生信息是否存在,
选课数量是否合法,这里设定数量只能为1。然后根据promoId的值判断是否
可以选课,如果为null表示可以选课,如果为1表示不能选课,如果为2表示
可以选课,如果为3表示选择时间结束 - 落单减容量 一旦落单成功那么相应的课程容量就会被这个用户占有扣减
1,这里操作的是课程容量表,判断是否还有相应的课程容量,如果有那么
减容量操作成功,这里调用service层的减库存方法,也是一个事务级别
的功能,使用默认的隔离级别,如果落单无法减去容量那么整个创建课
程订单的过程都失败 - 订单入库 如果落单减容量操作完成那么就可以创建一条订单信息,除了
学生和课程相应信息外还要生成一个交易流水号,这个交易流水号有16位,
前8位表示时间信息,也就是创建课程订单的时间,中间6位是自增序列,最
后两位随机,创建订单是一个事务级别的功能,传播级别是REQUIRES_NEW
,这个事务方法和创建订单的其它部分没有关联,就算最后订单创建失败
依然会生成一个流水号,但是如果创建流水号失败那么会影响外部部分,
整个创建订单会失败 - 增加选课人数信息 相应的课程选课数量也要增加,与课程容量表减容量
操作类似,也是一个事务级别的操作,采用默认的事务隔离级别,与创建订
单的整个过程关联在一起
跨域请求
跨域请求分为两大类: 简单请求和非简单请求
- 简单请求 请求方式为 GET、POST、HEAD。请求头信息不能超过Accept、
Accept-Language、Content-Language、Content-Type
实现跨域操作
使用CORS跨域资源共享的方式
- 前端 Vue 框架中有一个Vue.config.js 配置文件,包含生成环境和开
发环境以及一些打包的配置,其中有一个devServer 的部分用来配置跨域
请求,配置主机端口和目标代理服务器的地址,允许这个地址跨域 - 后端 SpringBoot 框架中有两种解决方法
- @CrossOrigin 在controller 层的类或者方法上添加这个注解,指定属性
allowedCredentials 为true,表示允许跨域请求上传cookie或用户凭证等
信息,指定属性allowedHeaders 为true,允许跨域请求传入的header字段 - 全局配置 创建一个配置类CrosConfig 实现WebMvcConfigurer,使用注
解@Configuration 表示这是一个配置类,实现addCorsMappings方法,
允许所有类型的请求方法和头字段跨域
登录拦截
一个简单拦截器的逻辑如下
- 用户访问 URL,检测是否为登录页面,如果是登录页面则不拦截
- 如果学生访问的不是课程页面,并进行选课操作时,不需要输入学生号和密
码,在后端检测用户是否已登录,如果未登录则跳转到登录页面
为了保存登录状态,我们可以把用户信息存在 Session 对象中(当用户在应用
程序的Web页之间跳转时,存储在 Session 对象中的变量不会丢失),这样
在访问别的页面时,可以通过判断是否存在用户变量来判断用户是否登录
认证方案(session 与 token)
先说最简单的认证方法,即前端在每次请求时都加上用户名和密码,交由后端验
证。这种方法的弊端有两个:
- 需要频繁查询数据库,导致服务器压力较大
- 安全性,如果信息被截取,攻击者就可以 一直 利用用户名密码登录(注意
不是因为明文不安全,是由于无法控制时效性)
SESSION
许多语言在网络编程模块都会实现会话机制,即 session。利用 session,我们
可以管理用户状态,比如控制会话存在时间,在会话中保存属性等
- 服务器接收到第一个请求时,生成 session 对象,并通过响应头告诉客户端
在 cookie 中放入 sessionId - 客户端之后发送请求时,会带上包含 sessionId 的 cookie
- 服务器通过sessionId获取 session ,进而得到当前用户的状态(是否登录
)等信息
也就是说,客户端只需要在登录的时候发送一次用户名密码,此后只需要在发送
请求时带上 sessionId,服务器就可以验证用户是否登录了。
session 存储在内存中,在用户量较少时访问效率较高,但如果一个服务器保存
了几十几百万个 session 就十分难顶了。同时由于同一用户的多次请求需要访问
到同一服务器,不能简单做集群,需要通过一些策略(session sticky)来扩展
,比较麻烦。
Token
使用Token 可以防止表单重复提交。
- 在服务器端生成一个唯一的随机标识号,称为Token(令牌),同时在当前用户
的Session域中保存这个Token - 将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个
Token,表单提交的时候连同这个Token一起提交到服务器端 - 在服务器端判
断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就
是重复提交了,时服务器端就可以不处理重复提交的表单。如果相同则处理表单
提交,处理完后清除当前用户的Session域中存储的标识号
以下情况会拒绝表单的请求
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同
- 当前用户的Session中不存在Token(令牌)
- 用户提交的表单数据中没有Token(令牌)
Token可以不保存在session中
- session是空间换时间,token是时间换空间
- 浏览器第一次访问服务器时,会传过来一个唯一表示ID,服务端通过算法,
加密钥,生成一个token 发送给客户端 - Token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign
(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串 - 服务器不需要记录任何东西,每次都是一个无状态的请求,每次都是通过
解密来验证是否合法
单点登录
在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。
图书管理系统
项目背景
仿照豆瓣网站设计的图书信息展示系统,能够展示图书的信息,并且对图书实现增删查改
项目架构
前端基于 Vue+ElementUI+axios,后端基于 SpringBoot,采用Vue的组件化思想
实现页面导航栏和图书分类功能。根据Shiro相关文档的讲解实现用户信息加密与登
录认证功能
导航功能
- 需要把导航栏放在其他页面的父页面,当表面有多个页面功能,比如首页、
图书馆和笔记本等,为了实现在这些页面之间能够相互切换,可以使用导航栏 - 在一个组件中通过导入引入了其它组件,导航栏的各个部分就是一个子组件
搜索栏查询
只需要输入书名或作者名的部分信息即可查询
1 | <select id="findAllByTitleLikeOrAuthorLike" parameterType="HashMap" |
RBAC
- RBAC是基于角色的访问控制模型,权限与角色相关联,用户通过成为适当
角色的成员而得到这些角色的权限,这就极大地简化了权限的管理。这样管理
都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户 - 三部分:用户、角色和权限。通过定义角色的权限,并对用户授予某个角
色来控制用户的权限,实现了用户和权限的逻辑分离,极大方便权限的管
理。存在用户-角色映射和角色-权限映射。一个用户可以赋予多个角色,
一个角色可以赋予多个权限 - 角色就比如普通用户和管理员,权限就比如修改或查看个人信息,管理
员可以冻结用户实现任何操作
安全原则
RBAC支持三个安全原则:最小权限原则、责任分离原则和数据抽象原则
- 最小权限原则 RBAC可以将角色配置成其完成任务所需的最小权限集合
- 责任分离原则 可以通过调用相互独立互斥的角色共同完成敏感任务,例
如要求一个记账员和财务管理员共同参与同一过账操作 - 数据抽象原则 可以通过权限的抽象来体现,例如财务操作用借款、存款
等抽象权限,而不是使用典型的读、写、执行权限
一个用户要实现某个功能的时候首先检查该用户对应的角色,然后检查对应
的角色是否有相应的操作权限
数据库表
- user 包括姓名 密码 密码 盐
- admin_user_role 用户与角色的映射表
- admin_role 角色表
- admin_role_menu 角色与菜单映射表
- admin_menu 保存菜单栏的相关信息
- admin_permission 保存权限对应的接口
Shiro
- 这里我通过阅读Shiro的相关文档完成加密与认证功能
- 关于Shiro有三个基本的核心概念:Subject、SecurityManager和Realms
- Subject 现在在于软件交互的东西,这个东西可能是你我他,负责存储与修
改当前用户的信息和状态,使用Shiro实现我们设计的各种功能,实际就是在
调用Subject的API - SecurityManager 管理所有的Subject,只需要配置一次即可
- Realm 是Shiro和安全数据之间的桥梁,也就是说Realm负责从数据源中获
取数据并加工后传给SecurityManager
Shiro配置与登录认证
- 创建Realm并重写获取认证与授权信息的方法,获取认证信息,即根据token
中的用户名从数据库中获取密码、盐等并返回 - 创建配置类,包括创建并配置 SecurityManager 等
- 登录操作时首先获取当前用户subject,然后封装当前用户的登录数据,
执行subject.login方法,Shiro内部帮我们进行验证操作。通过用户的账号
和密码生成一个令牌,需要认证时能够取出令牌进行认证 - 实现登出功能时执行subject.logout方法即可,该方法会清除session、
principals,并把authenticated设置为false
RememberMe
cookie的生命周期如果未特别设置则与浏览器保持一致,关闭浏览器后sessionId
就会消失,再次发送请求shiro就会认为用户已经变更,有时需要保持登录状态,
不然每次都需要重新登录。所以shiro提供了rememberMe机制,rememberMe不
是单纯设置cookie存活时间,而是又单独保存一种新的状态,之所以这样设计
也是基于一种安全考虑,把记住我的状态和实际登录状态做出区分这样就可以
控制用户在访问不太敏感的页面时无需重新登录,而访问类似于购物车、订单
之类的页面时必须重新登录
负载均衡算法有哪些?
- 轮询 轮询算法把每个请求轮流发送到每个服务器上,该算法比较适合每个
服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的
服务器可能无法承担过大的负载 - 加权轮询 加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器
赋予一定的权值,性能高的服务器分配更高的权值 - 最少连接 将请求发送给当前最少连接数的服务器上
- 加权最少连接
负载均衡知道哪些?
- 将负载(工作任务)进行平衡、分摊到多个操作单元上进行执行。在负载均
衡中可以分为两种方式,硬件方式与软件方式
负载均衡的应用?
- DNS负载均衡 最早的负载均衡技术是通过DNS来实现的,在DNS中为多个
地址配置同一个名字,因而查询这个名字的客户机将得到其中一个地址,从
而使得不同的客户访问不同的服务器,达到负载均衡的目的 - 代理服务器负载均衡 使用代理服务器,可以将请求转发给内部的服务器
,使用这种加速模式显然可以提升静态网页的访问速度 - 反向代理负载均衡 反向代理负载均衡技术是把将来自internet上的连接
请求以反向代理的方式动态地转发给内部网络上的多台服务器进行处理,从
而达到负载均衡的目的
集群下的 Session 管理
- 一个用户的所有请求都路由到同一个服务器,当服务器宕机时,将丢失该
服务器上的所有 Session - 在服务器之间进行Session同步操作,每个服务器都有所有用户的Session
、信息,因此用户可以向任何一个服务器进行请求。占用过多内存,同步过程占
用网络带宽以及服务器处理器时间 - 使用一个单独的服务器存储 Session 数据,可以使用传统的 MySQL,也
使用 Redis。为了使得大型网站具有伸缩性,集群中的应用服务器通常需要保
持无状态,缺点是需要去实现存取 Session 的代码
项目中的sql优化问题?
- 索引优化
- 联合查询优化
设计秒杀系统的关键?
秒杀系统其实主要解决2个问题,一个是并发读,一个是并发写
- 高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常
关键 - 一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商
品在同一时刻被很多倍的请求同时来减库存 - 高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们
考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个
PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对
设计秒杀系统时应该注意什么?
总结来说就是“4 要 1 不要”
- 数据要尽量少 所谓“数据要尽量少”,请求的数据包括请求包体和返回包
体,请求数据还是返回数据都需要服务器做处理,而服务器在写网络时通常
都要做压缩和字符编码 - 请求数要尽量少
- 路径要尽量短
- 依赖要尽量少
- 不要有单点
MD5算法是什么过程呢?
用于信息摘要技术
- MD5算法就像一个函数,任意一个二进制串都可以作为自变量进入这个“函数”
,然后会出来一个固定为128位的二进制串 - 处理原文 我们计算出原文长度(bit)对 512 求余的结果,如果不等于448,
就需要填充原文使得原文对 512 求余的结果等于 448。填充的方法是第一位填
充1,其余位填充0,用剩余的位置(512-448=64 位)记录原文的真正长度,
把长度的二进制值补在最后 - 设置初始值 对这个512个位平均分成16组,每组32个位,然后使用四个常数
进行运算,一共进行64轮运算,进行完这些运算后,A,B,C,D的值都获得了更新
正向代理和反向代理
在开发的时候,前端用前端的服务器(Nginx),后端用后端的服务器(Tomcat)
,当我开发前端内容的时候,可以把前端的请求通过前端服务器转发给后端(称
为反向代理)
- 正向代理 正向代理是一个位于客户端和原始服务器之间的服务器,为了从原
始服务器获取数据,客户端向代理发送请求并指定目标(原始服务器),然后代
理向原始服务器转交请求并将获得的内容返回给客户端。正向代理的过程隐藏了
真实的请求客户端,服务端不知道真实的客户端是是谁, - 反向代理 隐藏了真实的服务端,对于客户端而言它就像是原始服务器,并且
客户端不需要进行任何特别的设置,对于客户端而言它就像是原始服务器,并且
客户端不需要进行任何特别的设置 - 正向代理代理的对象是客户端,反向代理代理的对象是服务端
Nginx 是什么?
Nginx 是一款轻量级的Web 服务器,反向代理服务器及电子邮件代理服务器。占
用内存少,并发能力强,Nginx专为性能优化而开发, 在高连接并发的情况下,
能够支持高达50000 个并发连接数的响应,Nginx支持热部署,可以在不间断
服务的情况下,对软件版本进行升级
Nginx 的应用场景有哪些?
- http服务器: Nginx是一个http服务可以独立提供http服务。可以做网页静态
服务器 - 虚拟主机: 可以实现在一台服务器虚拟出多个网站。例如个人网站使用的虚拟
主机 - 反向代理,负载均衡: 当网站的访问量达到一定程度后,单台服务器不能满
足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台
服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置
的情况
Nginx 常见命令?
- ./nginx 启动
- ./nginx -s stop 关闭
- ./nginx -s reload 重启
- ps aux | grep nginx 查看进程
负载均衡是什么?
- 当一个请求发送过来的时候,Nginx作为反向代理服务器,会根据请求找到
后面的目标服务器去处理请求,这就是反向代理。那么,如果目标服务器有多
台的话,找哪一个服务器去处理当前请求呢?这个合理分配请求到服务器的
过程就叫做负载均衡 - 负载均衡是因为当系统面临大量用户访问,负载过高的时候,通常会使用增
加服务器数量来进行横向扩展,负载均衡主要是为了分担访问量,将请求合理
分发给不同的服务器,避免临时的网络堵塞
nginx在项目中怎样部署负载均衡呢?
- 轮询 轮询算法把每个请求轮流发送到每个服务器上,该算法比较适合每个
服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的
服务器可能无法承担过大的负载 - 加权轮询 加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器
赋予一定的权值,性能高的服务器分配更高的权值。 - 最少连接 最少连接算法就是将请求发送给当前最少连接数的服务器上
- 加权最少连接 在最少连接的基础上,根据服务器的性能为每台服务器分配
权重,再根据权重计算出每台服务器能处理的连接数 - 随机算法 把请求随机发送到服务器上
- 源地址哈希法 源地址哈希通过对客户端IP 计算哈希值之后,再对服务器
数量取模得到目标服务器的序号,可以保证同一IP 的客户端的请求会转发到
同一台服务器上,用来实现会话粘滞
前端优化方式有哪些?
前端优化的核心是提高页面的加载速度与操作的响应速度,其实是加快页面
的 “导航” 、 “渲染” ,并提高脚本的执行速度
- 导航流程优化 所谓导航,也就是从输入 URL 到页面展示之前发生的事情
- 缓存
- 长连接
- 渲染流程优化 包括构建DOM 树、样式计算、布局、分层、绘制、分块、栅格
化、合成和显示等阶段 - JS 性能优化 由于JS 运行在主线程上,如果单个脚本执行时间太长,会
影响页面对其它交互的响应
查询服务器负载的命令?
负载(load)是linux机器的一个重要指标,直观了反应了机器当前的状态。如果
机器负载过高,那么对机器的操作将难以进行。Linux 的负载高,主要是由于
CPU使用、内存使用、IO消耗三部分构成
- uptime 直接展示负载
- top 能够实时显示系统中各个进程的资源占用状况,包括进程计数,CPU使
用率还有内存的使用信息 - iostat 输入iostat -x 1 10命令,表示开始监控输入输出状态,-x表示
显示所有参数信息,1表示每隔1秒监控一次,10表示共监控10次
Node.js 是什么?
- 一种javascript的运行环境,能够使得javascript脱离浏览器运行,基于
Chrome V8引擎 - NodeJS使用事件驱动,非阻塞型I/O,可以控制系统文件的读写,网络的输
入输出。所以NodeJS 又可以被单纯的认为是一个可以运行JavaScript 的服务
器,vue-cli 是运行在NodeJS 环境下帮助我们开发基于Vue框架的项目的脚
手架
后端是用来干什么的,同前端有什么区别?
- 网站的“前端”是与用户直接交互的部分,包括你在浏览网页时接触的所有视
觉内容–从字体到颜色,以及下拉菜单和侧边栏。这些视觉内容,都是由浏览器
解析、处理、渲染相关HTML、CSS、Java 文件后呈现而来。前端开发,就是要创
造上面提到的网站面向用户的部分背后的代码,并通过建立框架,构建沉浸性
的用户体验。为了实现这个目标,开发需要熟练运用下列语言、框架、工具库 - 为了让服务器、应用、数据库能够彼此交互,后端工程师需要具有用于应用构
建的服务器端语言,数据相关工具,PHP框架,版本控制工具,还要熟练使用
Linux 作为开发和部署环境。后端开发者使用这些工具编写干净、可移植、具
有良好文档支持的代码来创建或更新 Web 应用。但在写代码之前,他们需要
与客户沟通,了解其实际需求并转化为技术目标,制定最有效且精简的方案
来进行实现
单体应用
参考 https://www.zhihu.com/question/65502802
单体应用内部包含了所有需要的服务,而且每个服务功能模块都有很强的耦合
性,也就是相互依赖,很难拆分和扩容。单体应用也有优点
- 开发简洁 功能都在单个程序内部,便于软件设计和开发规划
- 容易部署 程序单一不存在分布式集群的复杂部署环境,降低部署难度
- 容易测试 没有各种复杂的服务调用关系,都是内部调用方便测试
缺点很明显,某个业务模块负载过高时,并不能单独扩展该服务,必须扩展整
个应用程序,迭代也很困难
微服务
微服务就是一些协同工作小而自治的服务,优先如下
- 不同服务内部的开发技术可以不一致,比如Java开发服务A,golang开发
服务B,还可以选用不同的存储技术,比如Redis或MySQL - 隔离性 各个服务相互独立和自治,一个服务不可用不会导致另一个服务
不可用 - 可扩展性 只需要对影响性能的服务进行扩展升级
- 简化部署 各个服务独立部署