困死了,睡觉
version-2.3[实现连个SpringBoot间的通信(将匹配系统放在微服务模块)]
https://git.acwing.com/dyh/kob/-/tree/371e68cfdd4cf08b88638aaf05ecece9aa122ec9
总结:重点是两个Springboot如何通信。
1.backend
给matchingsystem
发信息:backend
使用restTemplate
发送,matchingsystem
使用controller
层接收(重点)—>matchingsystem的Security
要放行
2.匹配成功后,matcingsystem
给backend
发送信息:matcingsystem
使用RestTemplate
发送,backend
使用Controller
接收(重点)—>backend的Security
要放行
概念
微服务详细解释
微服务概念:
(1)微服务就是一种架构风格
(2)微服务就是把一个项目拆分成独立的多个服务,并且多个服务是可以独立运行的,而每个服务都会占用线程。
目前微服务的开发框架
最常用的有以下四个:
Spring Cloud:http://projects.spring.io/spring-cloud
(现在非常流行的微服务架构)
Dubbo:
http://dubbo.io
Dropwizard:
http://www.dropwizard.io (关注单个微服务的开发)
Consul、etcd&etc
.(微服务的模块)
创建流程
项目流程:(红色-微服务)
创建backendcloud
软件包名称:作用是下图
父级项目没有逻辑:删除src文件
修改pom文件:添加spring-cloud-dependencies
依赖
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
新建模块
创建模块–>maven选项
将父级中pom文件中的所有依赖复制粘贴到matchingsystem
下的pom文件
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
改变端口
创建resources/application.properties
server.port=3001
逻辑实现
0.matchingsystem的接口(三种)+ 放行
创建matchingsystem
下的controller,service等文件夹
创建接口MatchingService
package com.kob.matchingsystem.service;
public interface MatchingService {
public String addPlayer(Integer userId,Integer rating);
public String removePlayer(Integer userId);
}
实现接口MatchingService
package com.kob.matchingsystem.service.impl;
import com.kob.matchingsystem.service.MatchingService;
import org.springframework.stereotype.Service;
@Service
public class MatchingServiceImpl implements MatchingService {
@Override
public String addPlayer(Integer userId, Integer rating) {
System.out.println("add player: "+userId+" "+rating);
return "add player success";
}
@Override
public String removePlayer(Integer userId) {
System.out.println("remove player: "+userId);
return "remove player success";
}
}
实现Controller控制
package com.kob.matchingsystem.controller;
import com.kob.matchingsystem.service.MatchingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
public class MatchingController {
@Autowired
private MatchingService matchingService;
@PostMapping("/player/add/")
public String addPlayer(@RequestParam MultiValueMap<String,String> data){
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
Integer rating = Integer.parseInt(Objects.requireNonNull(data.getFirst("rating")));
return matchingService.addPlayer(userId,rating);
}
@PostMapping("/player/remove/")
public String removePlayer(@RequestParam MultiValueMap<String,String> data){
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
return matchingService.removePlayer(userId);
}
}
给matchingsystem
添加依赖spring-boot-starter-security
由于Spring Cloud
是http
请求,所以可能会接收到用户的伪请求,matchingsystem
只能对于后端请求,因此需要防止外部请求,通过Spring Security
来实现权限控制。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.1</version>
</dependency>
添加配置类SecurityConfig
只能接受后端访问
package com.kob.matchingsystem.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/player/add/").hasIpAddress("127.0.0.1")
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
}
}
建立matchingsystem
的入口文件,点击文件左边绿色按钮
package com.kob.matchingsystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MatchingSystemApplication {
public static void main(String[] args) {
SpringApplication.run(MatchingSystemApplication.class, args);
}
}
目前只放行了/player/add/
测试:
add:405–>只是post类型不对
remove:403–>拒绝访问
然后将remove放行
.antMatchers("/player/add/","/player/remove/").hasIpAddress("127.0.0.1")
此处发现问题:
此处:无论127.0.0.1还是localhost,
访问都要用127.0.0.1-->405
如果localhost访问---->403
.antMatchers("/player/add/","/player/remove/").hasIpAddress("127.0.0.1")
访问链接一定要写
//正确访问 出现405
127.0.0.1:3001/player/add/
或127.0.0.1:3001/player/remove/
//错误访问 出现403
localhost:3001/player/add/
或localhost:3001/player/remove/
创建backend
模块,删除src,导入源项目的src
修改新的pom,将源项目的pom中的依赖复制粘贴过来
1.先封装向前端发送地图,操作等信息的函数startGame
//封装向前端传地图,操作等信息
private void startGame(Integer aId,Integer bId){
User a =userMapper.selectById(aId),b = userMapper.selectById(bId);
//一局游戏的线程
Game game =new Game(13,14,20,a.getId(),b.getId());
game.createMap();
//a,b共同的地图==>将地图赋给a,b对应的连接
users.get(a.getId()).game = game ;
users.get(b.getId()).game = game ;//b连接的地图
game.start();
JSONObject respGame = new JSONObject();
respGame.put("a_id",game.getPlayerA().getId());
respGame.put("a_sx",game.getPlayerA().getSx());
respGame.put("a_sy",game.getPlayerA().getSy());
respGame.put("b_id",game.getPlayerB().getId());
respGame.put("b_sx",game.getPlayerB().getSx());
respGame.put("b_sy",game.getPlayerB().getSy());
respGame.put("map",game.getG());
JSONObject respA = new JSONObject();//返回给a
respA.put("event", "start-matching");
respA.put("opponent_username", b.getUsername());
respA.put("opponent_photo", b.getPhoto());
respA.put("game", respGame);
users.get(a.getId()).sendMessage(respA.toJSONString());//获取a对应的连接,向前端传递信息(String)
JSONObject respB = new JSONObject();//返回给b
respB.put("event", "start-matching");
respB.put("opponent_username", a.getUsername());
respB.put("opponent_photo", a.getPhoto());
respB.put("game", respGame);
users.get(b.getId()).sendMessage(respB.toJSONString());//获取b对应的连接,向前端传递信息(String)
}
2.配置RestTemplateConfig
RestTemplate
用于两个Spring boot的通信
package com.kob.backend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
在WebSocketServer
中引用
private static RestTemplate restTemplate;
@Autowired
public void setRestTemplate(RestTemplate restTemplate){ WebSocketServer.restTemplate = restTemplate ;}
3.设计问题:修改数据库
把rating放到user表,bot表删除Rating
修改代码:
1.pojo
层:User添加属性rating,Bot删除属性rating
2.所有构造修改
4.backend
给matchingsystem
发信息:backend使用 restTemplate发送,matchingsystem使用controller层接收(重点)
修改startMatching,stopMatching
函数
如何给另一个SpringBoot
发信息呢?
restTemplate.postForObject(addPlayerUrl,data,String.class);
//前端点击 开始匹配 触发--》加入一个用户进行匹配--->发送请求给matchingsystem
private void startMatching(){
System.out.println("start matching!");
//向另一个Spring Boot发送的数据
MultiValueMap<String,String> data = new LinkedMultiValueMap<>();
data.add("user_id",this.user.getId().toString());
data.add("rating",this.user.getRating().toString());
restTemplate.postForObject(addPlayerUrl,data,String.class);
}
//前端点击 取消 触发 ---》取消一个用户的匹配--->发送请求给matchingsystem
private void stopMatching(){
System.out.println("stop matching!");
MultiValueMap<String,String> data = new LinkedMultiValueMap<>();
data.add("user_id",this.user.getId().toString());
restTemplate.postForObject(removePlayerUrl,data,String.class);
}
启动backend这个SpringBoot
启动时,报错,3000被占用,使用win+r杀死进程
目前实现的是红色路线
5.创建Player,MatchingPool类,简单编写逻辑(逻辑)
创建utils工具包
在utils
下创建Player,MatchingPool
类
package com.kob.matchingsystem.service.impl.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player {
private Integer userId;
private Integer rating;
private Integer waitingTime;//等待时间
}
package com.kob.matchingsystem.service.impl.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class MatchingPool extends Thread {
private static List<Player> players = new ArrayList<>();
private ReentrantLock lock = new ReentrantLock();
//两个线程访问:1.匹配线程 2.传入参数的线程,开始匹配
public void addPlayer(Integer userId,Integer rating){
lock.lock();
try {
players.add(new Player(userId,rating,0));
} finally {
lock.unlock();
}
}
public void removePlayer(Integer userId){
lock.lock();
try {
List<Player> newPlayers = new ArrayList<>();
for(Player player:players){
if(!player.getUserId().equals(userId)){
newPlayers.add(player);
}
}
players = newPlayers ;
} finally {
lock.lock();
}
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
调用函数 addPlayer,removePlayer
调用处:MatchingServiceImpl
package com.kob.matchingsystem.service.impl;
import com.kob.matchingsystem.service.MatchingService;
import com.kob.matchingsystem.service.impl.utils.MatchingPool;
import org.springframework.stereotype.Service;
@Service
public class MatchingServiceImpl implements MatchingService {
//就一个线程
public static final MatchingPool matchingPool = new MatchingPool();
@Override
public String addPlayer(Integer userId, Integer rating) {
System.out.println("add player: "+userId+" "+rating);
matchingPool.addPlayer(userId,rating);
return "add player success";
}
@Override
public String removePlayer(Integer userId) {
System.out.println("remove player: "+userId);
matchingPool.removePlayer(userId);
return "remove player success";
}
}
6.MatchingPool的逻辑,具体逻辑(逻辑)
package com.kob.matchingsystem.service.impl.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class MatchingPool extends Thread {
private static List<Player> players = new ArrayList<>();
private ReentrantLock lock = new ReentrantLock();
//两个线程访问:1.匹配线程 2.传入参数的线程,开始匹配
public void addPlayer(Integer userId,Integer rating){
lock.lock();
try {
players.add(new Player(userId,rating,0));
} finally {
lock.unlock();
}
}
public void removePlayer(Integer userId){
lock.lock();
try {
List<Player> newPlayers = new ArrayList<>();
for(Player player:players){
if(!player.getUserId().equals(userId)){
newPlayers.add(player);
}
}
players = newPlayers ;
} finally {
lock.lock();
}
}
private void increaseWaitingTime(){//所有玩家的等待加时间1
for(Player player:players){
player.setWaitingTime(player.getWaitingTime()+1);
}
}
private void matcingPlayers(){//匹配所有玩家
boolean[] used = new boolean[players.size()];
for(int i = 0 ; i < players.size() ; i++){
if(used[i])continue;
for(int j = i+1 ;j < players.size() ;j++){
if(used[j])continue;
Player a = players.get(i),b = players.get(j);
if(checkMatched(a,b)){
used[i] = used[j] = true;
sendResult(a,b);
break;
}
}
}
//将匹配成功的删除
List<Player> newPlayers = new ArrayList<>();
for(int i= 0;i<players.size();i++){
if(!used[i]){
newPlayers.add(players.get(i));
}
}
players = newPlayers;
}
private boolean checkMatched(Player a,Player b){//判断两个玩家是否匹配
int ratingDelta = Math.abs(a.getRating() - b.getRating());
int waitingTime = Math.min(a.getWaitingTime(),b.getWaitingTime());
return ratingDelta <= 10*waitingTime ;
}
private void sendResult(Player a,Player b){
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
lock.lock();
try {
increaseWaitingTime();
matcingPlayers();
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
7.匹配成功后,matcingsystem
给backend
发送信息,matcingsystem使用RestTemplate发送,backend使用Controller接收(重点)
为了让backend
端接受信息,创建接口,实现接口,实现控制器
backend
放行:
.antMatchers("/pk/start/game/").hasIpAddress("127.0.0.1")
创建接口
package com.kob.backend.service.pk;
public interface StartGameService {
public String startGame(Integer aId,Integer bId);
}
实现接口
package com.kob.backend.service.impl.pk;
import com.kob.backend.consumer.WebSocketServer;
import com.kob.backend.service.pk.StartGameService;
import org.springframework.stereotype.Service;
@Service
public class StartGameServiceImpl implements StartGameService {
@Override
public String startGame(Integer aId, Integer bId) {
System.out.println("start game: "+ aId+" "+bId);
WebSocketServer.startGame(aId,bId);
return "start game success";
}
}
实现控制器
package com.kob.backend.controller.pk;
import com.kob.backend.service.pk.StartGameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
public class StartGameController {
@Autowired
private StartGameService startGameService;
@PostMapping("/pk/start/game/")
public String StartGame(@RequestParam MultiValueMap<String,String> data){
Integer aId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_id")));
Integer bId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_id")));
return startGameService.startGame(aId,bId);
}
}
给backend发信息需要RestTemplate
package com.kob.matchingsystem.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
在matchingsys的MatchingPool类下添加
private final static String startGameUrl = "http://127.0.0.1:3000/pk/start/game/";
//因为是Bean
private static RestTemplate restTemplate ;
@Autowired
public void setRestTemplate(RestTemplate restTemplate) { MatchingPool.restTemplate = restTemplate ;}
......
private void sendResult(Player a,Player b){
System.out.println("send result: " + a + " " + b);
MultiValueMap<String,String> data = new LinkedMultiValueMap<>();
data.put("a_id", Collections.singletonList(a.getUserId().toString()));
data.put("b_id", Collections.singletonList(b.getUserId().toString()));
restTemplate.postForObject(startGameUrl,data,String.class);
}
测试结果
8.优化
若玩家在匹配中突然停电,则会爆异常,需要特判
情况描述:当user1
突然关闭页面,此时user1
还在匹配池中,可能会与其他玩家匹配成功,成功后,sendResult()
,然后startGame()
函数,但startGame
函数中的users里早已经没有user1
,因此users.get(a.getId()).game = game ;
就会报错,因为是null.
解决:将所有获取用户的语句,加一个判空操纵
总共修改六处,eg:
if(users.get(a.getId()) != null)
users.get(a.getId()).game = game ;
if(users.get(b.getId()) != null)
users.get(b.getId()).game = game ;
大佬,想问下,spring-cloud-dependencies这个依赖添加了之后,后续在哪里应用到了呢,我去掉这个依赖还是可以启动并进行匹配呀
这么努力,要注意身体。
快干,不然开学了