HRM项目总结
一、Git
1.git的介绍
Git和SVN类似,都是版本控制的工具,用作项目的协同开发,不同的是,SVN是集中式的版本控制工
具,Git是分布式的版本控制工具
2.git的使用
①将新写的代码git add
②执行git commit,进行代码的提交,提交到本地
③推送【git push】
④拉取【git pull】
3.git图形化界面操作
TortoiseGIT
Idea使用Git
4.冲突的处理
和svn类型,svn冲突后会有多个版本文件,但是git不会产生多个版本文件,我们只需要更改冲突的文
件后,标记为“解决”,再提交代码就OK了,其他和svn解决冲突一模一样。
5.Git的远程仓库
GitLab
二、搭建后端项目结构
1.注册中心Eureka
1.1.依赖
<!--eureka服务端的场景启动器-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
1.2.配置
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版
1.3.启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class,args);
}
}
2.配置中心服务端
2.1.创建一个springboot项目,只保留resources,存放配置文件
2.2.依赖
<!--配置中心服务端场景启动器-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--eureka-client的场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2.3.配置
server:
port: 6969
#springcloud从本地读取配置文件
#spring:
# cloud:
# config:
# server:
# native:
# search-locations: D:/itsource/workspace/hrm-config/src/main/resources
# profiles:
# active: native
spring:
application:
name: CONFIG-SERVER
#从gitee中拉取配置文件
cloud:
config:
server:
git:
uri: https://gitee.com/solargen_admin/hrm-config.git #git仓库的路径
searchPaths: src/main/resources #从仓库路径中哪个目录中加载
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: config-server:6969 #服务实例的标识
prefer-ip-address: true #以ip注册
2.4.启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
3.网关
3.1.依赖
<dependencies>
<!--网关的场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--eureka-client的场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
2.2.配置
#配置中心客户端配置
spring:
cloud:
config:
discovery:
enabled: true
service-id: CONFIG-SERVER
name: application-zuul
profile: dev
#eureka客户端配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
配置文件仓库中:application-zuul-dev.yml
server:
port: 1299
spring:
application:
name: ZUUL-SERVICE
eureka:
instance:
instance-id: zuul-service:1299 #服务实例的标识
prefer-ip-address: true #以ip注册
zuul:
ignored-services: "*"
prefix: /servies
#路由配置
routes:
user:
path: /myusers/**
serviceId: users
2.3.启动类
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
三、Mybatis-Plus
mybatis-plus是使用mybatis的工具,无缝的整合到mybatis的项目中,只做增强,不做改变,如丝般顺滑。
1.mybatis-plus的核心
(1)CRUD接口
(2)条件构造器
(3)分页插件
(4)代码生成器
四、租户入驻
1.租户入驻的流程
前端展示一个租户入驻的页面,填写租户的基本信息和选购的套餐,将参数传递到后端,把租户信息插入到租户表,再将租户信息里面的用户名和密码和刚刚返回的租户主键插入员工表,然后再将返回的员工表主键插入到租户表,最后再将租户主键和租户信息里面的套餐id插入到租户-套餐中间表,这个业务需要加入事务管理,需要保证事务一致性。
2.租户logo上传(分布式文件系统fastdfs)
2.1.fastdfs的架构原理【tracker storager】
- tracker 和storager通过心跳连接
- tracker 负责接收用户请求并负载均衡分配storager给用户
- storager接收数据并存储,并返回用户fileId或者根据fileId查找文件
2.2.使用fastdfs实现文件上传的流程
- racker 和storager保持心跳链接,用户请求tracker ,tracker 负载均衡分配storager给用户,用户上传文件到storager中,storager将文件持久化并返回给用户一个fileId(storager组名+文件名),用户将fileId储存到数据库。
- 用户需要下载文件时,也是请求tracker ,tracker 负载均衡分配storager给用户,用户通过fileId在storager中查找文件并下载。
五、课程类型管理+redis中央缓存
1.类型树,获取无线级别的课程类型实现方式
- 第一种是采用递归,从父级类型开始查,然后递归,一直查到无父类型位置,这种方式代码可读性较差,还有可能导致栈溢出,基本没用过
- 第二种是嵌套循环,先把所有类型查出来,再循环遍历,然后把父级类型添加到List中,然后嵌套循环所有类型,查找父级类型,就是把第一层循环的类型的父id一一与第二层循环的类型的id对应,相同的就是父子类型,然后把对应的子类型添加到父类型即可,这种数据多的时候查询的此树多,效率不高,用的少
- 第三种是循环+map,把所有类型查出来并添加到map中(id为key,类型对象为value),然后把所有类型遍历并把父级类型添加到List中,然后把其他类型的父id用作map的key,找到该类型的所有父级类型,如果有父类型,将它添加到父类型即可,这种方式效率高,用得多
2.redis优化课程类型
-
为什么将课程类型缓存到redis中?
- 因为课程类型属于查询多,数据改动很少的类型,这种数据很适合做缓存,可以减少数据库压力,解决高并发问题
-
课程类型是以什么数据类型缓存到reids中的?为什么?
- 以JSON字符串缓存到Redis中的,因为课程类型数据比较小,用这种方式很方便,效率高
3.redis缓存的流程
①接收到前端请求课程类型的请求后直接查询redis;
②判断缓存中的数据是否存在;
③如果存在,直接返回数据;
④如果不存在,从数据库中查询数据,将查出来的数据同步到redis中后再返回给前端。
4.redis遇到的问题
4.1.缓存穿透
(1)案例:
将用户信息缓存到redis中,使用用户的id作为key,我们知道,id通常不为负数,如果某些人发送大量请求访问id=-1的用户信息,缓存中查询不到,会直接查询数据库。如果并发量很高,则这种现象直接穿透了redis缓存,大量请求直接访问数据库,会压垮数据库,造成数据库宕机。
(2)解决方案:
- 布隆过滤器
- 如果缓存不存在,则访问数据库,如果数据库也没有查询到数据,则存储一个null值在redis中,并设置一个过期时间,[key(-1):value(null)],直接返回出去。
4.2.缓存击穿
(1)案例:
同时大量请求访问某一个key,而这个key由于刚刚过期或者redis刚刚启动缓存中还没有,造成大量的请求访问数据库
(2)解决方案:
同步代码块+双重校验所
@Override
public List<CourseType> loadDataTree() {
//1、从缓存中获取数据
AjaxResult result = redisClient.getStr(KEY);
if(!result.isSuccess()){
return null;
}
//2、判断缓存中的数据是否存在
String courseTypes = (String) result.getResultObj();
if(StringUtils.isEmpty(courseTypes)){
//缓存不存在
synchronized (this){
result = redisClient.getStr(KEY);
if(!result.isSuccess()){
return null;
}
courseTypes = (String) result.getResultObj();
if(StringUtils.isEmpty(courseTypes)){
logger.debug("缓存中没有数据,查询数据库并缓存......");
List<CourseType> courseTypeList = null;
//4、如果不存在,则查询数据库,将查询结果缓存到redis中
courseTypeList = loopMapCourseTypes();
try {
//模拟这边业务要执行2000ms
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String jsonString = JSONArray.toJSONString(courseTypeList);
redisClient.setStr(KEY,jsonString);
//5、再将结果返回
return courseTypeList;
}
}
}
logger.debug("从缓存中获取到了数据......");
//3、如果存在,则将缓存中获取的数据直接返回 - fastjson
List<CourseType> courseTypeList = JSONArray.parseArray(courseTypes, CourseType.class);
return courseTypeList;
}
4.3.缓存雪崩
(1)案例
在同一时间,大量的缓存失效,导致查询这些缓存的请求都会访问DB,造成DB压力过大;
和缓存击穿不同的是,缓存击穿是“热点数据”失效(某几个key),
而缓存雪崩是大量的key同一时间失效。
比如,项目启动,从数据库中加载所有的用户信息,我们设置相同的过期时间,时间到后,这些用户全部过期了,查询所有的用户的请求都要访问数据库(缓存雪崩)
(2)解决方案
让过期时间均匀分布(过期时间与随机值配合,在一段时间内依次过期)
六、课程的上下线+ElasticSearch
1.课程上下线业务简述
- 在后台管理添加好新课程后,并不是马上就想让用户看到,需要等公司决定的上线日期按时上线,所以就需要上线操作之后才让用户能够搜索到
- 某个课程由于某些原因暂时不再出售了,就需要进行下线操作,下线后用户就不能看到该课程不能再购买了,但是数据库里面还是存在该课程的,如果需要再次出售,就可以进行上线操作
2.项目中为什么使用elasticsearch,解决了什么问题
- 上线时把课程数据同步到es,用户直接从es查询.也就意味着没有上线的课程用户查询不到,因为没有放到es库.
- 下线时把es库课程数据删除掉. 用户就查询不到了
- 查询效率高,支持集群,解决单点故障,支持高并发
七、页面静态化
页面静态化业务流程
- 课程服务收到页面静态化的请求后,将页面静态化的模板上传到fastdfs中,并拿到返回的模板templateFileId;
- 课程服务向后台查询模板需要的数据,通过feign调用页面静态化服务,并将templateFileId和数据传递过去;
- 页面静态化服务接收到这两个参数后,根据templateFileId在fastdfs中下载模板,通过模板和数据生成静态化页面,并将该页面上传到fastdfs中,返回静态页面的staticPageFiledId;
- 课程服务集成RabbitMq,将页面静态化返回的staticPageFiledId作为消息放入RabbitMq交换机中,交换机将消息分发给符合预先约定的队列,前端项目部署的服务器中再部署一个java程序,监听RabbitMq队列,获取staticPageFiledId,根据staticPageFiledId从fastdfs中下载页面,再在前端页面中展示。
发送短信验证码的业务流程
-
点击发送手机验证码按钮,携带图形验证码和标识uuid和手机号码;
-
后台根据uuid查询redis校验图形验证码是否正确,若不正确,返回图形验证码不正确的提示;
-
图形验证码正确,根据手机验证码标识(前缀+手机号)查询redis,看该手机号在缓存中是否存在短信验证码;
- 如果不存在,则生成短信验证码,将标识和验证码和时间存入redis,并调用短信服务发送短信验证码;
- 如果存在,则从redis中获取该手机号对应的短信验证码和时间,判断是否过期,若没过期,则认定为非法请求,若已经过期,则重新生成验证码,并将验证码和生成时间覆盖掉之前的,并调用服务发送短信。
单点登录的解决方案,单点登录时如何实现的
- 前端发送登录请求,后台验证用户名和密码,验证通过之后,生成一个随机的accessKey,将登录信息作为value存入redis中,并设置过期时间,然后将accessKey返回给前端;
- 登录成功后,前端将返回的accessKey保存到cookie中token,其他站点可以共享;
- 前端配置axios拦截器,每次请求如果存在token,就在请求头中加入token;
- 所有请求都会来到网关,在网关这里配置一个过滤器,拦截需要认证后才能访问的请求,通过请求头携带的token去查询redis中是否存在,存在则放行,不存在则返回登录。
Zuul网关在微服务中的作用
- 为全部微服务提供唯一入口点,网关起到内部和外部隔离,保障了后台服务的安全性;
- 识别每个请求的 权限,拒绝不符合要求的请求;(过滤器)
- 动态的将请求路由到不同的后端集群中;
- 减少客户端与服务的耦合,服务可以独立发展,通过网关层来做映射。
来源:oschina
链接:https://my.oschina.net/u/4409292/blog/3282569