创建cloud父组件,并将项目改为主服务+微服务MatchingSystem

This commit is contained in:
2023-03-05 19:27:35 +08:00
parent c3c890be51
commit 3cc2c3c698
66 changed files with 3086 additions and 0 deletions
@@ -0,0 +1,15 @@
package com.kob.matchingsystem;
import com.kob.matchingsystem.service.impl.MatchingServiceImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MatchingSystemApplication {
public static void main(String[] args) {
// 启动 MatchingSystem 服务之前启动 MatchingPool 线程
MatchingServiceImpl.matchingPool.start(); // 启动匹配线程
SpringApplication.run(MatchingSystemApplication.class, args);
}
}
@@ -0,0 +1,13 @@
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();
}
}
@@ -0,0 +1,34 @@
// 链接访问权限控制
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;
// TODO: 2023/2/19 WebSecurityConfigurerAdapter 已被弃用,注意替换
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
用于配置公开链接
.hasIpAddress("127.0.0.1") 用于限制访问的IP地址, 127.0.0.1 意为只允许本地访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/player/add/", "/player/remove/").hasIpAddress("127.0.0.1")
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
}
}
@@ -0,0 +1,33 @@
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;
// 涉及到对数据的修改,使用 POST 请求
// MultiValueMap 每个关键字对应一个列表的值, Map 每个关键字只能对应一个单值
@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);
}
}
@@ -0,0 +1,10 @@
// 定义接口
package com.kob.matchingsystem.service;
public interface MatchingService {
// 给匹配池添加一名玩家
String addPlayer(Integer userId, Integer rating);
// 从匹配池删除一名玩家
String removePlayer(Integer userId);
}
@@ -0,0 +1,28 @@
// 实现接口
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 {
// MatchingPool 全局只有一个线程,所以这里定义成静态
public final static 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";
}
}
@@ -0,0 +1,140 @@
// 用来维护线程
package com.kob.matchingsystem.service.impl.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
@Component
public class MatchingPool extends Thread {
// 把所有用户存下来
private static List<Player> playerList = new ArrayList<>();
// 定义一个锁, Reentrant Lock 是可重入锁
private final ReentrantLock lock = new ReentrantLock();
// 定义微服务传值 URL
private static final String startGameUrl = "http://127.0.0.1:3000/pk/start/";
// 定义 RestTemplate 用来微服务数据通信
private static RestTemplate restTemplate;
@Autowired
private void setRestTemplate(RestTemplate restTemplate) {
MatchingPool.restTemplate = restTemplate;
}
public void addPlayer(Integer userId, Integer rating) {
lock.lock();
try {
// 一开始匹配等待时间是 0
playerList.add(new Player(userId, rating, 0));
} finally {
lock.unlock();
}
}
public void removePlayer(Integer userId) {
lock.lock();
try {
// 先创建一个新列表,将没有删的用户先存下来
List<Player> newPlayerList = new ArrayList<>();
// 枚举所有的 player 并存入 newPlayerList
for (Player player : playerList) {
// 如果当前 player 的 id 不等于要删除的用户 id,则表示此用户保留,将要保留的用户信息存入新列表
// 否则不存入新列表
if (!player.getUserId().equals(userId))
newPlayerList.add(player);
}
// 将删除用户后的新 newPlayerList 赋值给 playerList
playerList = newPlayerList;
} finally {
lock.unlock();
}
}
// 将所有玩家的等待时间 +1
private void increaseWaitingTime() {
for (Player player : playerList) {
player.setWaitingTime(player.getWaitingTime() + 1);
}
}
// 判断两名玩家是否匹配
private boolean checkMatched(Player a, Player b) {
int ratingDelta = Math.abs(a.getRating() - b.getRating()); // 两名玩家的天梯积分之差
// a b 两名玩家的等待时间最小值
// min 是两方任意一方接受等待时间就可以匹配, max 是两方都接受的匹配
int minWaitingTime = Math.min(a.getWaitingTime(), b.getWaitingTime());
// 匹配结果可被 a 接受: a b 的分差小于等于 a和b 最小等待时间乘以 10
// 匹配结果可被 b 接受: a b 的分差小于等于 a和b 最小等待时间乘以 10
return ratingDelta <= minWaitingTime * 10;
}
// 返回 a 和 b 的匹配结果
private void sendResult(Player a, Player b) {
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("a_id", a.getUserId().toString());
data.add("b_id", b.getUserId().toString());
restTemplate.postForObject(startGameUrl, data, String.class);
}
// 尝试匹配所有玩家
private void matchPlayers() {
//TODO 后端调试
System.out.println("match players: " + playerList.toString());
// havaMatched 表示玩家已经匹配过了
boolean[] haveMatched = new boolean[playerList.size()];
for (int i = 0; i < playerList.size(); i++) {
if (haveMatched[i]) continue; // 如果当前枚举到的玩家已经匹配过了,则跳过改玩家
for (int j = i + 1; j < playerList.size(); j++) { // j 从 i+1 开始枚举
if (haveMatched[j]) continue;
// 如果 i 和 j 都没有匹配,则将 a 和 b 玩家取出来
Player a = playerList.get(i);
Player b = playerList.get(j);
// 判断 a 和 b 能否匹配:如果匹配,则将结果返回,并将 a 和 b 的位置置为 true
if (checkMatched(a, b)) {
haveMatched[i] = haveMatched[j] = true; // 置为已匹配
sendResult(a, b); // 返回匹配玩家结果
break;
}
}
}
// 匹配成功后,需要将匹配过的玩家从匹配池移除
List<Player> newPlayerList = new ArrayList<>();
for (int i = 0; i < playerList.size(); i++) {
// 如果当前枚举到的玩家没有匹配,则将该玩家存入新的 list
if (!haveMatched[i])
newPlayerList.add(playerList.get(i));
}
// 将新的 newPlayerList 重新赋值 playerList
playerList = newPlayerList;
}
// 实现线程
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
lock.lock();
try {
// 每隔一秒钟调用“等待时间增加”函数
increaseWaitingTime();
// 尝试匹配玩家
matchPlayers();
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 如果捕获到异常,则输出异常并终止运行
e.printStackTrace();
break;
}
}
}
}
@@ -0,0 +1,15 @@
// 存储玩家
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; // 等待时间
}
@@ -0,0 +1 @@
server.port=3001