目录
一、整体梳理
本节课把代码执行的微服务写完
负责接收一段代码,把代码扔到我们的队列当中、每一次我们去运行一段代码
运行结束之后、把我们的结果返回给我们的服务器
二、创建后端
先创建这节课的一个后端们
先把依赖复制过来、我们需要动态的把用户传过来的Java代码编译然后执行
需要加入依赖joor-java-8、用Java的代码的写法举例子
未来自己实现的时候可以换成任意语言、在这个线程里面是有一堆的bot代码等待执行
然后我们会有一个队列、每次我们从队列中取出一个bot代码来执行
执行的时候用的是一个Java的包会方便的去帮我们动态编译一段Java代码
未来可以在云端把他换成一个docker、你在云端自动启动一个docker容器
给docker设置一个内存上限200M再去docker中动态执行一段Java代码用timeout
去限制某一个程序的执行时间、这样又安全又可以支持其他语言
所以这里我们用Java一个包来动态执行一段Java代码
开一个线程用我们的joor去动态执行一段代码
botrunningsystem/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>backendcloud</artifactId>
<groupId>com.kob</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.kob.botrunningsystem</groupId>
<artifactId>botrunningsystem</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<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>
</dependency>
<!-- 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>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jooq/joor-java-8 -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joor-java-8</artifactId>
<version>0.9.14</version>
</dependency>
</dependencies>
</project>
将BotRunningSystem/Main.java 更名为 BotRunningSystemApplication.java:
package com.kob.botrunningsystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BotRunningSystemApplication {
public static void main(String[] args) {
SpringApplication.run(BotRunningSystemApplication.class, args);
}
}
三、实现Service和Controlle
实现完service之后
去实现我们的controller、怎么将SpringBoot和docker关联?
SpringBoot里面可以直接执行shell命令
注意要用127.0.0.1因为我们的config里写的是127.0.0.1
BotRunningService.java:
package com.kob.botrunningsystem.service;
public interface BotRunningService {
String addBot(Integer userId, String botCode, String input);
}
BotRunningServiceImpl.java:
package com.kob.botrunningsystem.service.impl;
import com.kob.botrunningsystem.service.BotRunningService;
import org.springframework.stereotype.Service;
@Service
public class BotRunningServiceImpl implements BotRunningService {
@Override
public String addBot(Integer userId, String botCode, String input) {
System.out.println("add bot: " + userId + " " + botCode + " " + input);
return "add bot success";
}
}
BotRunningController.java:
package com.kob.botrunningsystem.controller;
import com.kob.botrunningsystem.service.BotRunningService;
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 java.util.Objects;
@RestController
public class BotRunningController {
@Autowired
private BotRunningService botRunningService;
@PostMapping("/bot/add/")
public String addBot(@RequestParam MultiValueMap<String, String> data) {
Integer userID = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
String botCode = data.getFirst("bot_code");
String input = data.getFirst("input");
return botRunningService.addBot(userID, botCode, input);
}
}
SecurityConfig.java:
package com.kob.botrunningsystem.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("/bot/add/").hasIpAddress("127.0.0.1")
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
}
}
RestTemplateConfig.java:
package com.kob.botrunningsystem.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();
}
}
四、前端加入选择框
每一步的传递都需要加上额外的参数
botid、前端先加上一个框
中间加上一个复选框、需要动态的获得我们的bot列表
这样我们的bot就传过来了、我们需要把bot的信息传回到前端
MatchGround.vue::
<template>
<div class="matchground">
<div class="row">
<div class="col-4">
...
</div>
<div class="col-4">
<div class="user-select-bot">
<select class="form-select" aria-label="Default select example" v-model="select_bot">
<option value="-1" selected>亲自出马</option>
<option v-for="bot in bots" :key="bot.id" :value="bot.id">{{ bot.title }}</option>
</select>
</div>
</div>
<div class="col-4">
...
</div>
<div class="col-12" style="text-align: center; padding-top: 15vh;">
...
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import { useStore } from 'vuex';
import $ from 'jquery';
export default {
setup() {
...
let bots = ref([]);
let select_bot = ref("-1");
const click_match_btn = () => {
if(match_btn_info.value === "开始匹配") {
match_btn_info.value = "取消";
store.state.pk.socket.send(JSON.stringify({
event: "start-matching",
bot_id: select_bot.value,
}));
}
...
};
const refresh_bots = () => {
$.ajax({
url: "http://127.0.0.1:3000/user/bot/getlist/",
type: "get",
headers: {
'Authorization': "Bearer " + store.state.user.token,
},
success(resp) {
bots.value = resp;
}
})
};
refresh_bots();
return {
...
bots,
select_bot,
}
}
}
</script>
<style scoped>
...
div.user-select-bot {
padding-top: 20vh;
}
div.user-select-bot > select {
width: 60%;
margin: 0 auto;
}
</style>
BackEnd接收Bot:
WebSocketServer.java::
package com.kob.backend.consumer;
...
@Component
// url链接:ws://127.0.0.1:3000/websocket/**
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
public class WebSocketServer {
...
private void startMatching(Integer botId) {
System.out.println("start matching!");
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("user_id", this.user.getId().toString());
data.add("rating", this.user.getRating().toString());
data.add("bot_id", botId.toString());
restTemplate.postForObject(addPlayerUrl, data, String.class);
}
...
@OnMessage
public void onMessage(String message, Session session) {
...
if("start-matching".equals(event)) {
startMatching(data.getInteger("bot_id"));
}
...
}
...
}
五、加上BotID
前面通信的时候每一次通信都是没有加botid的
userid、我们需要把前面每一个通信都加上botid 对应service层也要加上
发的时候收的时候都需要传我们的id
Matching System接收Bot
Matching System接收到backend传的botId,将bot传给BotRunningSystem服务:
package com.kob.matchingsystem.controller;
...
import java.util.Objects;
@RestController
public class MatchingController {
@Autowired
private MatchingService matchingService;
// 参数不能使用普通map,MultiValueMap和普通map的区别时,这个是一个键对应多个值
@PostMapping("/player/add/")
public String addPlayer(@RequestParam MultiValueMap<String, String> data) {
...
Integer botId = Integer.parseInt(Objects.requireNonNull(data.getFirst("bot_id")));
return matchingService.addPlayer(userId, rating, botId);
}
...
}
MatchingService.java:
package com.kob.matchingsystem.service;
public interface MatchingService {
String addPlayer(Integer userId, Integer rating, Integer botId);
...
}
MatchingServiceImpl.java:
package com.kob.matchingsystem.service.impl;
...
@Service
public class MatchingServiceImpl implements MatchingService {
public static final MatchingPool matchingPool = new MatchingPool();
@Override
public String addPlayer(Integer userId, Integer rating, Integer botId) {
System.out.println("Add Player: " + userId + " " + rating + " " + botId);
matchingPool.addPlayer(userId, rating, botId);
return "add player success";
}
...
}
MatchingPool.java:
package com.kob.matchingsystem.service.impl.utils;
...
// 匹配池是多线程的
@Component
public class MatchingPool extends Thread {
...
public void addPlayer(Integer userId, Integer rating, Integer botId) {
// 在多个线程(匹配线程遍历players时,主线程调用方法时)会操作players变量,因此加锁
lock.lock();
try {
players.add(new Player(userId, rating, botId, 0));
} finally {
lock.unlock();
}
}
...
}
Player.java:
package com.kob.matchingsystem.service.impl.utils;
...
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player {
private Integer userId;
private Integer rating;
private Integer botId;
private Integer waitingTime;
}
package com.kob.matchingsystem.service.impl.utils;::
// 匹配池是多线程的
@Component
public class MatchingPool extends Thread {
...
private void sendResult(Player a, Player b) { // 返回匹配结果
System.out.println("send result: " + a + " " + b);
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
data.add("a_id", a.getUserId().toString());
data.add("a_bot_id", a.getBotId().toString());
data.add("b_id", b.getUserId().toString());
data.add("b_bot_id", b.getBotId().toString());
restTemplate.postForObject(startGameUrl, data, String.class);
}
...
}
StartGameController.java:
package com.kob.backend.controller.pk;
...
import java.util.Objects;
@RestController
public class StartGameController {
...
@PostMapping("/pk/start/game/")
public String startGame(@RequestParam MultiValueMap<String, String> data) {
Integer aId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_id")));
Integer aBotId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_bot_id")));
Integer bId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_id")));
Integer bBotId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_bot_id")));
return startGameService.startGame(aId, aBotId, bId, bBotId);
}
}
StartGameService.java:
package com.kob.backend.service.pk;
public interface StartGameService {
String startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId);
}
StartGameServiceImpl.java:
package com.kob.backend.service.impl.pk;
...
@Service
public class StartGameServiceImpl implements StartGameService {
@Override
public String startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId) {
System.out.println("start gameL: " + aId + " " + bId);
WebSocketServer.startGame(aId, aBotId, bId, bBotId);
return "start game success";
}
}
WebSocketServer.java:
package com.kob.backend.consumer;
...
import com.kob.backend.pojo.Bot;
import com.kob.backend.mapper.BotMapper;
@Component
// url链接:ws://127.0.0.1:3000/websocket/**
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
public class WebSocketServer {
...
public static BotMapper botMapper;
...
@Autowired
public void setBotMapper(BotMapper botMapper) {
WebSocketServer.botMapper = botMapper;
}
...
public static void startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId) {
User a = userMapper.selectById(aId), b = userMapper.selectById(bId);
Bot botA = botMapper.selectById(aBotId), botB = botMapper.selectById(bBotId);
Game game = new Game(
13,
14,
20,
a.getId(),
botA,
b.getId(),
botB);
...
}
...
}
六、人机分离
在创建完地图执行nextstep的时候
判断一下当前的玩家是人还是代码、如果是代码的话就需要像我们的微服务发送一
段代码、然后让她自动去算、如果是人来操作的话就要等待用户的输入
所以这里需要判断一下如果player的botid=-1的话表示是由人来操作
编码的时候我们随意编只要把我们的编码编成字符串就可以
第一段传的是地图信息、中间用#号隔开
七、生产者消费者模型
微服务可以不断的去接收用户的一个输入
当接收的代码比较多的时候、我们应该把他们所有接收到的代码放到一个队列里面
我们每接收到一个任务、消费者是一个单独的线程
苦力不断是完成任务,每来一个任务检测队列是否为空如果队列不空,从对头拿出
代码执行、执行完之后再去检查
八、consume开新线程
consume里面开一个新的线程
如果被awit阻塞住后就会被唤醒、然后执行
实现一下consume操作、需要在consume里面开一个新的线程
这样我们就可以从前端动态的传一份代码过来传完之后动态的编译一遍
编译完之后我们就可以动态的调用它编译后的结果
Bot.java:
package com.kob.botrunningsystem.utils;
import java.util.ArrayList;
import java.util.List;
public class Bot implements BotInterface {
static class Cell {
public int x, y;
public Cell(int x, int y) {
this.x = x;
this.y = y;
}
}
// 检查当前回合,蛇的长度是否会增加
private boolean check_tail_increasing(int step) {
if(step <= 10) return true;
return step % 3 == 1;
}
public List<Cell> getCells(int sx, int sy, String steps) {
steps = steps.substring(1, steps.length() - 1);
List<Cell> res = new ArrayList<>();
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
int x = sx, y = sy;
int step = 0;
res.add(new Cell(x, y));
for(int i = 0; i < steps.length(); i++) {
int d = steps.charAt(i) - '0';
x += dx[d];
y += dy[d];
res.add(new Cell(x, y));
if(!check_tail_increasing(++step)) {
res.remove(0);
}
}
return res;
}
@Override
public Integer nextMove(String input) {
// 地图#my.sx#my.sy#(my操作)#you.sx#you.sy#(you操作)
String[] strs = input.split("#");
int[][] g = new int[13][14];
for(int i = 0, k = 0; i < 13; i++) {
for(int j = 0; j < 14; j++, k++) {
if(strs[0].charAt(k) == '1') {
g[i][j] = 1;
}
}
}
int aSx = Integer.parseInt(strs[1]), aSy = Integer.parseInt(strs[2]);
int bSx = Integer.parseInt(strs[4]), bSy = Integer.parseInt(strs[5]);
List<Cell> aCells = getCells(aSx, aSy, strs[3]);
List<Cell> bCells = getCells(bSx, bSy, strs[6]);
for(Cell c : aCells) g[c.x][c.y] = 1;
for(Cell c : bCells) g[c.x][c.y] = 1;
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
for(int i = 0; i < 4; i++) {
int x = aCells.get(aCells.size() - 1).x + dx[i];
int y = aCells.get(aCells.size() - 1).y + dy[i];
if(x >= 0 && x < 13 && y >= 0 && y < 14 && g[x][y] == 0) {
return i;
}
}
return 0;
}
}