Spring Boot 6.3 两个SpringBoot项目之间的如何通信?重点

总结:重点是两个Springboot如何通信。
1.backendmatchingsystem发信息:backend使用 restTemplate发送,matchingsystem使用controller层接收(重点)—>matchingsystemSecurity要放行
2.匹配成功后,matcingsystembackend发送信息:matcingsystem使用RestTemplate发送,backend使用Controller接收(重点)—>backendSecurity要放行

概念

微服务详细解释
微服务概念:
(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 Cloudhttp请求,所以可能会接收到用户的伪请求,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.13001/player/add/127.0.0.13001/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.backendmatchingsystem发信息: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.匹配成功后,matcingsystembackend发送信息,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 ;

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...