一、概述
spring cloud其实是一个集合啦,不是什么新的技术,把一堆的技术,像spring boot那样打包好来用。用spring cloud必须得用到spring boot。
主要框架
- 服务发现——Netflix Eureka
- 服务调用——Netflix Feign
- 熔断器——Netflix Hystrix :是一个处理错误的东西。发生某些错误时,如果不及时更改,会发生什么奇怪得事情,熔断器就是直接返回一个错误的数据,虽然错误,但不会导致应用全部崩坏
- 服务网关——Netflix Zuul :像现在的每一个微服务的端口号都不一样,服务网关就是把这端口号集合成一个,然后用的时候分发出去
- 分布式配置——Spring Cloud Config :把所有微服务的application.yml放到一个统一的地方。因为Java程序打包不会把配置文件也放进去,启动的时候可以修改配置文件以便后续修改。
- 消息总线 —— Spring Cloud Bus:类似热部署,修改文件不需要再重新启动一遍
版本
spring boot和spring cloud的版本号一定要对上,官方链接比如我spring boot 2.3.4 ,就要用Hoxton.SR9
二、Eureka
Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目 spring-cloud-netflix中,实现SpringCloud的服务发现功能。Eureka包含两个组件: Eureka Server和Eureka Client。
人话模式:现在各个微服务模块之间都不认识,都不知道彼此是什么,那么就要注册一个Eureka Server,这个是空白的东西。然后再将各个微服务模块做成一个个Eureka Client,注册在Eureka Server中,这样他们就能彼此之间认识了。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注 册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点 的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也 就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会 向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有 接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90 秒)。
1、Eureka(微服务模块)服务端开发
1)导包
父类导包,固定版本
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- dependencyManagement是用来控制版本的
新建eureka模块导包
添加server模块,这个当作服务器,其他模块是用户
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
这里的导包,非常神奇呢,一开始是unknown的,然后上网找了几天解决方法都没有,过了几天再刷新一下他就自己OK了。。。
2)修改yml,主类添加注解@EnableEurekaServer
server:
port: 6868
eureka:
client:
register-with-eureka: false #是否将自己注册到Eureka服务中,本身就是服务端无需注册
fetch-registry: false #是否从Eureka中获取注册信息,服务端不需要获取注册信息
service-url: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:${server.port}/eureka/
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
http://localhost:6868/
2、其他模块实现Eureka客户端注册
其他模块全部添加包、yml和主程序类注解@EnableEurekaClient
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true #false只允许本地使用,true可以在服务器上使用
@SpringBootApplication
@EnableEurekaClient
public class ArticleApplication {
public static void main(String[] args) {
SpringApplication.run(ArticleApplication.class, args);
}
}
三、Feign实现服务间的调用
可以实现A模块调用B模块的方法,前提是开启Eureka服务和注册Eureka客户端
以QA调用Base模块为例子
1、以QA调用Base模块为例子
1)在主动调用一方导包
QA模块添加
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2)主动调用方启动类添加注解
QaApplication :
@EnableDiscoveryClient
@EnableFeignClients
public class QaApplication {
//xxx
}
3)主动调用方模块添加接口
QA模块-client包-LabelClient接口
@FeignClient("tensquare-base")
public interface LabelClient {
@GetMapping("/label/{labelId}")
public Result findById(@PathVariable("labelId") String labelId);
}
-
@FeignClient()
参数输入调用哪个服务(模块)名字,名字就是yml中的spring: application: name:
,切不能包含下划线_
spring: application: # 微服务通过application-name来进行连接,不能用_ name: tensquare-base #就是这个名字
- 然后接口里面,要什么方法,就写什么方法,这个方法就是base模块中的方法。接口嘛,就写到名字和参数这一步就OK,具体逻辑就是调用模块中的逻辑
3.5)Result类记得添加无参构造器
远古时代忘记加无参构造器了
在实体类添加@NoArgsConstructor
4)主动调用方添加方法
QA模块中的ProblemController
这里的方法是第3步中调用的方法
//localhost:9003/problem/label/{labelId}
@GetMapping("label/{labelId}")
public Result findLabelById(@PathVariable("labelId") String labelId){
Result result = labelClient.findById(labelId);
return result;
}
四、交友微服务
1、说明
交友数据库表:
@Entity
@Data
@Table(name = "tb_friend")
@IdClass(Friend.class)
public class Friend implements Serializable {
@Id
private String userid;
@Id
private String friendid;
private String isLike;
}
@Entity
@Data
@Table(name = "tb_nofriend")
@IdClass(NoFriend.class)
public class NoFriend {
@Id
private String userid;
@Id
private String friendid;
}
- 这里需要用到
@IdClass
,我也不知道为啥要,反正不加就出错。。
用户数据库表:
1)当A向B点击喜欢时
A的关注数+1,B的粉丝+1。判断,如果B也喜欢A,那么双方isLike都变成1;如果A喜欢B,B不喜欢A,那么A的isLike为0
2)当A对B取消喜欢时
就是取关,A的关注数-1,B的粉丝-1。并且, 对tb_nofriend表添加两个人的ID。
2、添加好友
它是这样的,推荐列表,一个人,显示2个按钮,一个喜欢
按钮,一个X
按钮,点了喜欢,就在Friend表中添加数据。当点击X
,就是不喜欢该人,就在NoFriend表中添加数据,推荐表就不会出现该人了
controller
@RestController
@RequestMapping("/friend")
public class FriendController {
@Autowired
private HttpServletRequest httpServletRequest;
@Autowired
private FriendService friendService;
@Autowired
private JwtUtil jwtUtil;
/*
*
* @param friendid 对方ID
* @param type 1喜欢,2不喜欢
* @return entity.Result
*/
@PutMapping(value = "like/{friendid}/{type}")
public Result addFriend(@PathVariable String friendid,@PathVariable String type){
//验证登陆
String token = (String) httpServletRequest.getAttribute("claims_user");
if (token==null || "".equals(token)){
//如果当前用户没有user角色
return new Result(false, StatusCode.ERROR,"无权限操作");
}
//判断是添加好友好事添加非好友
if (type!=null) {//非空
//添加好友
if (type.equals("1")) {
//将token转义
Claims claims = jwtUtil.parseJWT(token);
String userid = claims.getId();
//根据不同情况添加好友
int flag = friendService.addFriend(userid, friendid);
//重复添加好友
if (flag==0){
return new Result(false, StatusCode.ERROR,"重复添加好友");
}
//添加成功
if (flag == 1) {
return new Result(true, StatusCode.OK,"添加成功");
}
}
//添加非好友
else if (type.equals("2")){
}
//如果都不是
else return new Result(false, StatusCode.ERROR,"参数异常");
}
//如果type为空
return new Result(false, StatusCode.ERROR,"参数异常");
}
}
- 用到了JWT权限登陆验证
-
addFriend()
在service用于返回一个int类型,0就重复添加,1就添加成功
service
@Service
public class FriendService {
@Autowired
private FriendDao friendDao;
@Transactional
public int addFriend(String userid,String friendid){
//如果已经加为好友了
if (friendDao.findByUseridAndFriendid(userid, friendid)!=null){
return 0;
}
//向喜欢表添加记录
Friend friend = new Friend();
friend.setUserid(userid);
friend.setFriendid(friendid);
friend.setIslike("0");
friendDao.save(friend);
//判断对方是否喜欢你,如果喜欢,就把isLike变成1
if (friendDao.findByUseridAndFriendid(friendid,userid)!=null){
friendDao.updateLike(userid,friendid,"1");
friendDao.updateLike(friendid,userid,"1");
}
return 1;
}
}
dao
public interface FriendDao extends JpaRepository<Friend,String> {
/**
* 根据用户ID与被关注用户ID查询记录个数,作用是为了查找该用户是否关注了另外一用户
* @param userid
* @param friendid
* @return int
*/
public Friend findByUseridAndFriendid(String userid,String friendid);
/**
* 更新为互相喜欢
* @param userid
* @param friendid
* @param isLike
* @return void
*/
@Modifying
@Query(value="update tb_friend f set f.islike=?3 where f.userid = ?1 and f.friendid=?2",nativeQuery=true)
public void updateLike(String userid,String friendid,String isLike);
}
当8也向2表示喜欢的时候
3、添加非好友(点击X
,就是不喜欢该人)
dao:
public interface NoFriendDao extends JpaRepository<NoFriend,String> {
/**
* 根据用户ID与被关注用户ID查询记录个数,作用是为了查找该用户是否关注了另外一用户
* @param userid
* @param friendid
* @return int
*/
public NoFriend findByUseridAndFriendid(String userid,String friendid);
}
FriendService
public int addNoFriend(String userid, String friendid) {
//判断表中是否已经有该数据了
NoFriend noFriend = noFriendDao.findByUseridAndFriendid(userid, friendid);
if (noFriend!=null){
return 0;
}
noFriend = new NoFriend();
noFriend.setUserid(userid);
noFriend.setFriendid(friendid);
noFriendDao.save(noFriend);
return 1;
}
FriendController
@PutMapping(value = "like/{friendid}/{type}")
public Result addFriend(@PathVariable String friendid,@PathVariable String type){
//xxxx
//添加非好友
else if (type.equals("2")){
int flag = friendService.addNoFriend(userid, friendid);
//重复添加非好友
if (flag==0){
return new Result(false, StatusCode.ERROR,"重复点击了不喜欢");
}
//添加成功
if (flag == 1) {
return new Result(true, StatusCode.OK,"添加成功");
}
}
ID8点击不喜欢ID5
4、粉丝数和关注数的变更
粉丝数和关注数变更,是在user模块中写的。所以我们要跨服务了。
1)user模块
controller
/*
* 关注或者删除别人,自己的关注数+或者-1,别人的粉丝数+或者-1
* @param userid
* @param friendid
* @param x +1自己关注别人,-1自己删除别人
* @return void
*/
@PutMapping("{userid}/{friendid}/{x}")
public void updateMyFollowcountAndHisFanscount(@PathVariable("userid") String userid,@PathVariable("friendid") String friendid,@PathVariable("x") int x){
userService.updateMyFollowcountAndHisFanscount(userid,friendid,x);
}
service
public void updateMyFollowcountAndHisFanscount(String userid, String friendid, int x) {
userDao.updateFollowcount(userid,x);
userDao.updateFanscount(friendid,x);
}
dao
@Transactional
public interface UserDao extends JpaRepository<User,String>,JpaSpecificationExecutor<User>{
public User findByMobile(String mobile);
/*
* 跟新自己的关注数
* @param userid
* @param x
* @return void
*/
@Modifying
@Query(value="update tb_user set followcount=followcount+?2 where id =?1",nativeQuery=true)
public void updateFollowcount(String userid, int x);
/*
* 更新别人的粉丝数
* @param userid
* @param x
* @return void
*/
@Modifying
@Query(value="update tb_user set fanscount=fanscount+?2 where id =?1",nativeQuery=true)
public void updateFanscount(String friendid, int x);
}
2)交友模块调用user,进行粉丝数和关注数的修改
像上面一样,调用Feign
UserClient
@FeignClient("tensquare-user")
public interface UserClient {
/*
* 关注或者删除别人,自己的关注数+或者-1,别人的粉丝数+或者-1
* @param userid
* @param friendid
* @param x +1自己关注别人,-1自己删除别人
* @return void
*/
@PutMapping("/user/{userid}/{friendid}/{x}")
public void updateMyFollowcountAndHisFanscount(@PathVariable("userid") String userid, @PathVariable("friendid") String friendid, @PathVariable("x") int x);
}
friendController
//添加好友
if (type.equals("1")) {
//根据不同情况添加好友
int flag = friendService.addFriend(userid, friendid);
//重复添加好友
if (flag==0){
return new Result(false, StatusCode.ERROR,"重复添加好友");
}
//添加成功
if (flag == 1) {
//修改关注数和粉丝数
userClient.updateMyFollowcountAndHisFanscount(userid,friendid,1);
return new Result(true, StatusCode.OK,"添加成功");
}
}
5、删除好友
- 在friend表中删除对应数据
- 更新双方关注数和粉丝数
- 在nofriend表中添加数据
/*
*删除好友,在friend表中删除对应数据,更新双方关注数和粉丝数,在nofriend表中添加数据
* @param friendid
* @return entity.Result
*/
@DeleteMapping("{friendid}")
public Result deleteFriend(@PathVariable("friendid") String friendid){
//验证登陆
String token = (String) httpServletRequest.getAttribute("claims_user");
if (token==null || "".equals(token)){
//如果当前用户没有user角色
return new Result(false, StatusCode.ERROR,"无权限操作");
}
//将token转义
Claims claims = jwtUtil.parseJWT(token);
String userid = claims.getId();
friendService.deleteFriend(userid,friendid);
userClient.updateMyFollowcountAndHisFanscount(userid,friendid,-1);
return new Result(true,StatusCode.OK,"删除成功");
}
service
public void deleteFriend(String userid,String friendid) {
//删除friend表中的数据
friendDao.deleteFriend(userid,friendid);
//更新friendid到userid的数据为0
friendDao.updateLike(friendid,userid,"0");
//nofriend表中添加数据
NoFriend noFriend = new NoFriend();
noFriend.setUserid(userid);
noFriend.setFriendid(friendid);
noFriendDao.save(noFriend);
}
dao
/*
*根据userid和friendid进行删除
* @param userid
* @param friendid
* @return void
*/
@Query(value="delete from tb_friend where userid = ?1 and friendid=?2",nativeQuery=true)
@Modifying
public void deleteFriend(String userid, String friendid);
五、熔断器Hystrix
1、概述
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障, 进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种 因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如该图,ABCD都是微服务,CD调用B,B调用A,但是如果A出错了或者A关闭了,B就也触发错误不可用了,就会导致CD也出错不可用了。
这个时候,熔断器Hystrix就出现了
2、什么是熔断器Hystrix
当A出错的时候,会返回一堆错误代码,因为微服务是同步而不是异步,就不会跳过这个错误继续进行下去了。而熔断器Hystrix就是当A出现错误的时候,返回一个Java可以识别的东西,比如类i,JSON之类的。可以让项目继续进行下去
3、QA调用Base模块
正常访问:
关掉base模块后
这里是做了所有异常返回result才这样,如果没有说明异常返回result,就会显示一堆奇奇怪怪的东西。
4、使用
1、yml
feign:
hystrix:
enabled: true
2、添加LabelClientImpl类
在clitnt-impl中添加
@Component
public class LabelClientImpl implements LabelClient {
@Override
public Result findById(String labelId) {
return new Result(true, StatusCode.ERROR,"熔断器启动了");
}
}
- 记得将其加入容器中
3、修改LabelClient
修改@FeignClient参数
@FeignClient(value = "tensquare-base",fallback = LabelClientImpl.class)
public interface LabelClient {
//这里要补全请求路径
@GetMapping("/label/{labelId}")
public Result findById(@PathVariable("labelId") String labelId);
}
-
@FeignClient(fallback = LabelClientImpl.class)
:如果触发了错误,返回LabelClientImpl类。如果突然好了,就返回正常请求,会自动检测的
4、效果
QA开,Base关
突然把Base开
六、网关Zuul
为什么要用网关?
我们用了那么多微服务,端口号都记不住了,base是9001,qa是9003.。。那么多记不住的。
这个图,所有微服务都是在eureka中,我们加入一个网关,用户用的时候,访问网关。网关我们自己设置,什么请求对应什么端口号,这样就不用记住了。
1、管理后台微服务网关
1)导包:
<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-netflix-zuul</artifactId>
</dependency>
2)配置文件:
server:
port: 9011
spring:
application:
name: tensquare-manager
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true #false只允许本地使用,true可以在服务器上使用
zuul:
routes:
tensquare-base: #
path: /base/** #路径
serviceId: tensquare-base #ID名
tensquare-friend:
path: /friend/**
serviceId: tensquare-friend #ID名
tensquare-gathering:
path: /gathering/**
serviceId: tensquare-gathering #ID名
tensquare-qa:
path: /qa/**
serviceId: tensquare-qa #ID名
tensquare-recruit:
path: /recruit/**
serviceId: tensquare-recruit #ID名
tensquare-search:
path: /search/**
serviceId: tensquare-search #ID名
tensquare-spit:
path: /spit/**
serviceId: tensquare-spit #ID名
tensquare-user:
path: /user/**
serviceId: tensquare-user #ID名
tensquare-sms:
path: /sms/**
serviceId: tensquare-sms #ID名
3)application
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ManagerApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerApplication.class, args);
}
}
4)使用方法
开启eureka、base、manager,这时候,Label有两种访问方法
①localhost:9001/label
这样子就是普通地访问全部数据
②localhost:9011/base/label
通过网关访问
这里简单理解为localhost:9011/base/
取代了localhost:9001/
2、前台网关微服务
和管理员后台微服务网关一样 ,改下端口号9012
3、Zuul过滤器
@Component
public class ManagerFilter extends ZuulFilter {
/**
*
* @param
* @return pre 前置过滤器
* route 请求时调用
* post路由请求和error过滤器之后调用
* error处理请求时发生错误时调用
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器级别,越小越先执行
* @param
* @return int
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否开启过滤器
* @param
* @return boolean
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 具体逻辑
* @param
* @return java.lang.Object
*/
@Override
public Object run() throws ZuulException {
System.out.println("经过了Zuul过滤器");
return null;
}
}
4、用网关会导致头信息丢失
做个测试,比如get的label请求
@GetMapping("")
public Result findAll(){
//测试
String header = httpServletRequest.getHeader("Authorization");
System.out.println(header);
return new Result(true, StatusCode.OK,"查询成功",labelService.findAll());
}
测下是否有头信息
1)不经过网关
asdasdasdasd
Hibernate: select label0_.id as id1_0_, label0_.count as count2_0_, label0_.fans as fans3_0_, label0_.labelname as labelnam4_0_, label0_.recommend as recommen5_0_, label0_.state as state6_0_ from tb_label label0_
2)经过网关
null
Hibernate: select label0_.id as id1_0_, label0_.count as count2_0_, label0_.fans as fans3_0_, label0_.labelname as labelnam4_0_, label0_.recommend as recommen5_0_, label0_.state as state6_0_ from tb_label label0_
经过网关后,数据就丢失了?!!
5、解决用网关丢失头消息问题(web)
先改YML
zuul:
sensitive-headers:
- 如果不加
sensitive-headers
,就会把authorization、set-cookie、cookie、host、connection、content-length、content-encoding、server、transfer-encoding、x-application-context给过滤掉,所以要把该值设为空
再改过滤器中的run方法
@Override
public Object run() throws ZuulException {
//得到request上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//得到request域
HttpServletRequest request = requestContext.getRequest();
//得到头消息
String header = request.getHeader("Authorization");
//判断是否有该头消息
if (header != null && !"".equals(header)) {
requestContext.addZuulRequestHeader("Authorization",header);
}
return null;
}
qqqqqqqqqq
Hibernate: select label0_.id as id1_0_, label0_.count as count2_0_, label0_.fans as fans3_0_, label0_.labelname as labelnam4_0_, label0_.recommend as recommen5_0_, label0_.state as state6_0_ from tb_label label0_
6、后台网关验证权限
这就是为了双层验证而已啦,加强安全。上面做的虽然有token转法到微服务,user可以这么做,但是admin不能这么做,要严格一点。
导包tensquare_commom包,还有JKD旧版本的包
<dependency>
<groupId>org.example</groupId>
<artifactId>tensquare_commom</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 太高版本了,要下回旧版本的东西-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
加yml
jwt:
config:
key: tensquare
添加Bean
@Bean
public JwtUtil jwtUtil(){
return new JwtUtil();
}
1)过滤器
/**
* @description:TODO
*/
@Component
public class ManagerFilter extends ZuulFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
/**
* 具体逻辑,安全验证,返回null就是放行,requestContext.setSendZuulResponse(false); 就是终止运行
* @param
* @return java.lang.Object
*/
@Override
public Object run() throws ZuulException {
//得到request上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//得到request域
HttpServletRequest request = requestContext.getRequest();
//我也不懂。。。
if (request.getMethod().equals("OPTIONS")) {
return null;
}
//登陆的时候不做拦截
//查找改请求路径是否有login字段,有的话就放行
if (request.getRequestURI().indexOf("login")>0) {
return null;
}
//得到头消息
String header = request.getHeader("Authorization");
//判断是否有该头消息且Bearer 开头
if (header != null && !"".equals(header)&&header.startsWith("Bearer ")) {
//从第7个开始
String token = header.substring(7);
try {
//转换
Claims claims = jwtUtil.parseJWT(token);
//如果不是空
if (claims!=null){
//得到角色
if ("admin".equals(claims.get("roles"))){
//转发
requestContext.addZuulRequestHeader("Authorization",header);
return null;//null放行
}
}
}catch (Exception e){
e.printStackTrace();
requestContext.setSendZuulResponse(false);//终止运行
}
}
//没有admin权限的话
requestContext.setSendZuulResponse(false);//终止运行
requestContext.setResponseStatusCode(401);//http状态码
requestContext.setResponseBody("无权访问");
requestContext.getResponse().setContentType("text/html;charset=UTF-8");
return null;
}
}
2)效果
错误
正确
七、集中配置组件SpringCloudConfig
1、概述
现在可以变成这种,代码放在内部公司服务器中,配置文件放在网上,反正别人只拿配置文件也没用,这样做的优势就是,配合SpringCloudBus使用,上线后,修改参数可以在网上修改而不用重新打开编译一遍。
2、流程
以base模块中的yml放在gitee上,使用SpringCloudConfig使本地代码读取gitee中的yml进行使用。
1)上传yml文件到gitee
文件名是有讲究的:{application}-{profile}.yml
或{application}-{profile}.properties
- application为应用名称,profile指的开发环境(用于区分开发环境,测试环境、生产环境 等),例子为base-dev.yml,就是base应用的开发环境
2)配置中心微服务
这个的yml不用放在网上,放在本地,其他的微服务可放网上
导包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
yml
server:
port: 12000
spring:
application:
name: tensquare-config
cloud:
config:
server:
git:
uri: https://gitee.com/E-10000/tensquare.git
- uri就是gitee对应仓库的位置
启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class,args);
}
}
http://localhost:12000/base-dev.yml:
这里可以读到yml了呢
3)配置客户端
以base为例子的,导包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
添加bootstrap.yml(名字固定的),删除application.yml
spring:
cloud:
config:
#base-dev.yml
name: base
profile: dev
uri: http://localhost:12000
- bootstrap级别比application高,如果同时存在,优先指定bootstrap。
- base-dev,对应
name: base
和profile: dev
,所以名字不是随便起的
以web网关服务(该网关yml暂时没到网上)调用base服务
八、消息总线组件SpringCloudBus
SpringCloudConfig 将配置文件上传到网上,SpringCloudBus就类似一个热部署。放再网上修改配置文件的内容后,发送一个请求,就会自动更新了,而不用再次重启应用。(运维福音)
这里还是以base为例子,修改base中的配置参数
1、服务端(tensquare_config),修改yml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
server:
port: 12000
spring:
application:
name: tensquare-config
cloud:
config:
server:
git:
uri: https://gitee.com/E-10000/tensquare.git
force-pull: true
rabbitmq:
host: 192.168.12.128
username: guest
password: guest
port: 5672
management:
endpoints:
web:
exposure:
include: "*"
# include: bus-refresh
- force-pull: true:强制更新
- 这个用到rabbitmq,所以要写圈
- management往下这部分,照抄就行
2、客户端导包(tensquare_base)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
yml补充:
server:
port: 9001
spring:
application:
# 微服务通过application-name来进行连接,不能用_
name: tensquare-base
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.12.128/tensquare_base?characterEncoding=UTF-8
username: root
password: root
jpa:
show-sql: true
database: MySQL
rabbitmq:
host: 192.168.12.128
username: guest
password: guest
port: 5672
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true #false只允许本地使用,true可以在服务器上使用
test: 这个B不会真的更新了吧
- rabbitmq补充完整
- 最后的test是我们自己写的,我们就是要用bus热部署使它更新
3、效果
1)在base的控制类中修改下方法
@RestController
@CrossOrigin
@RequestMapping("/label")//基础请求都是localhost:9001/label
@RefreshScope
public class LabelController {
@Value("${test}")
private String test;
@Autowired
private LabelService labelService;
@Autowired
private HttpServletRequest httpServletRequest;
@GetMapping("")
public Result findAll(){
//测试
String header = httpServletRequest.getHeader("Authorization");
System.out.println(test);
return new Result(true, StatusCode.OK,"查询成功",labelService.findAll());
}
- @RefreshScope所有控制类都要加这个
2)运行localhost:9001/label
,显示
这个B不会真的更新了吧
3)此时,更新yml中test的数据
test: 阿巴阿巴阿巴
4)然后用post请求的127.0.0.1:12000/actuator/bus-refresh
,进行刷新
- 没错,还是要进行手动刷新,还没有自动刷新呢
5)然后再运行localhost:9001/label
,显示
阿巴阿巴阿巴