微服务执行Bot代码
本节课也是新开一个微服务:BotRunningSystem
红色为本次实现:
代码执行,此项目只支持java代码的执行,用的是joor java 8
实现。
可扩展为docker实现,设置内存上限,时间,用命令可执行所有语言代码,并具备一定安全性,因为docker与运行环境隔绝。
1.让BotRunning System获得到前端选择的Bot
1.1.新建Bot执行微服务项目
右键backendcloud
->新建->maven
1.2.修改pom依赖
1.复制粘贴matchingsystem
的pom中的所有依赖
2.添加新依赖 joor-java-8
:可以在Java中动态编译Java代码
1.3.BotRunningSystem接收前端选择的botId
文件结构
botrunningsystem
config
RestTemplateConfig.java
SecurityConfig.java
controller
BotRunningController.java
service
impl
BotRunningServiceImpl.java
BotRunningService.java
BotRunningSystemApplication.java
将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);
}
}
接口
package com.kob.botrunningsystem.service;
public interface BotRunningService {
public String addBot(Integer userId,String botCode,String input);
}
接口实现
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";
}
}
控制器
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 org.springframework.web.bind.annotation.RestController;
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);
}
}
权限控制(网关)
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();
}
}
服务间发送消息的RestTemplate
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();
}
}
端口配置
在resources新建文件
1.4.前端选择Bot+发送bot_id给后端
匹配界面
添加选择操作方式
<template>
<div class="matchground">
<div class="row">
<!-- 自己 -->
<div class="col-4">
<div class="user-photo">
<img :src="$store.state.user.photo" alt="">
</div>
<div class="user-username">
{{$store.state.user.username}}
</div>
</div>
<!-- 选择Bot -->
<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 class="user-photo">
<img :src="$store.state.pk.opponent_photo" alt="">
</div>
<div class="user-username">
{{$store.state.pk.opponent_username}}
</div>
</div>
<div class="col-12" style="text-align : center; padding-top : 12vh;">
<button type="button" class="btn btn-warning btn-lg" @click="click_match_btn">{{match_btn_info}}</button>
</div>
</div>
</div>
</template>
<script>
import { ref } from "vue"
import { useStore } from "vuex"
import $ from "jquery"
export default {
setup(){
const store = useStore();
let match_btn_info = ref("开始匹配")
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,
}));
} else if(match_btn_info.value === "取消"){
match_btn_info.value = "开始匹配";
store.state.pk.socket.send(JSON.stringify({
event:"stop-matching",
}));
}
}
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 {
match_btn_info,
click_match_btn,
bots,
select_bot,
}
}
}
</script>
<style scoped>
div.matchground {
width: 60vw;
height: 70vh;
background-color:rgba(50 ,50 ,50 ,0.5);
margin: 40px auto;
}
div.user-photo {
text-align: center;
padding-top: 10vh;
}
div.user-photo > img{
border-radius: 50%;
width: 20vh;
}
div.user-username {
text-align: center;
font-size: 20px;
font-weight: 600;
color: white;
margin-top: 2vh;
}
div.user-select-bot {
padding-top: 20vh;
}
div.user-select-bot > select {
width: 60%;
margin: 0 auto;
}
</style>
1.5.后端接收bot
backend接收Bot
WebSocketServer
接收到匹配请求,将bot传给匹配服务
Matching System接收Bot
Matching System接收到backend传的botId,将bot传给BotRunningSystem服务
控制器
接口
实现接口
匹配池
匹配池的Player
匹配池返回结果加上botId
StartGameController.java
StartGameService.java
StartGameServiceImpl.java
WebSocketServer.java
Player.java
WebSocketServer.java
将RestTemplate变成public,若是代码输入则屏蔽人的输入
Game.java
private final Player playerA,playerB;
private final static String addBotUrl = "http://127.0.0.1:3002/bot/add/";
public Game(
Integer rows,
Integer cols,
Integer inner_walls_count,
Integer idA,
Bot botA,
Integer idB,
Bot botB
) {
this.rows = rows;
this.cols = cols;
this.inner_walls_count = inner_walls_count;
this.g = new int[rows][cols];
Integer botIdA = -1,botIdB = -1;
String botCodeA = "",botCodeB = "";
if(botA != null){
botIdA = botA.getId();
botCodeA = botA.getContent();
}
if(botB != null){
botIdB = botB.getId();
botCodeB = botB.getContent();
}
playerA = new Player(idA,botIdA,botCodeA,this.rows - 2, 1, new ArrayList<>());
playerB = new Player(idB,botIdB,botCodeB,1, this.cols - 2, new ArrayList<>());
}
//获得input
private String getInput(Player player){
Player me,you ;
if(playerA.getId().equals(player.getId())){
me = playerA ;
you = playerB ;
} else {
me = playerB ;
you = playerA ;
}
return getMapString() + "#" +
me.getSx() + "#" +
me.getSy() + "#(" +
me.getStepsString() + ")#" +
you.getSx() + "#" +
you.getSy() + "#(" +
you.getStepsString() + "#)" ;
}
private void sendBotCode(Player player){
if(player.getBotId().equals(-1)) return ;//亲自出马
MultiValueMap<String,String> data = new LinkedMultiValueMap<>();
data.add("user_id",player.getId().toString());
data.add("bot_code",player.getBotCode());
data.add("input",getInput(player));
WebSocketServer.restTemplate.postForObject(addBotUrl,data,String.class);
}
private boolean nextStep(){//两名玩家的下一步
try {
Thread.sleep(200);//因为前端走一格200ms
} catch (InterruptedException e) {
e.printStackTrace();
}
sendBotCode(playerA);
sendBotCode(playerB);
......
}
private String getMapString(){
StringBuilder res = new StringBuilder();
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
res.append(g[i][j]);
}
}
return res.toString();
}
成功
1.6.总结:实现了BotId传送到BotRunningSystem系统
2.BotRunning System的实现
2.1思路:生产者消费者模型
2.2文件结构
BotRunningSystem
service
impl
utils
Bot.java
BotPool.java
Consumer.java
utils
Bot.java
BotInterface.java
2.3Bot的实现
package com.kob.botrunningsystem.service.impl.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Bot {
private Integer userId;
private String botCode;
private String input;
}
2.4BotPoll的实现
虽然队列没用消息队列,但是因为我们写了条件变量与锁的操作,所以等价于消息队列
package com.kob.botrunningsystem.service.impl.utils;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BotPool extends Thread{
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();//条件变量
private final Queue<Bot> bots = new LinkedList<>();//消息队列-->add,remove-->锁
//添加一个任务-Bot
public void addBot(Integer userId,String botCode,String input){
lock.lock();//涉及到bots
try {
bots.add(new Bot(userId,botCode,input));
condition.signalAll();
} finally {
lock.unlock();
}
}
//消费一个任务
private void consume(Bot bot){
Comsumer comsumer = new Comsumer();
comsumer.startTimeut(2000,bot);
}
@Override
public void run() {
while (true){
lock.lock();
if(bots.isEmpty()){//空
try {
condition.await();//【消息队列空】阻塞当前线程,直到被唤醒(默认包含解锁)
} catch (InterruptedException e) {
e.printStackTrace();
lock.unlock();
break;
}
} else {
Bot bot = bots.remove();//取出当前任务+移除
lock.unlock();
consume(bot);//消耗任务,用时长,执行代码
}
}
}
}
2.5BotRunningServiceImpl.java
加任务
package com.kob.botrunningsystem.service.impl;
import com.kob.botrunningsystem.service.BotRunningService;
import com.kob.botrunningsystem.service.impl.utils.BotPool;
import org.springframework.stereotype.Service;
@Service
public class BotRunningServiceImpl implements BotRunningService {
6 public final static BotPool botPool = new BotPool();
@Override
public String addBot(Integer userId, String botCode, String input) {
System.out.println("add bot: " + userId + " " + botCode + " " + input);
botPool.addBot(userId,botCode,input);
return "add bot success";
}
}
2.6BotPool线程的启动
package com.kob.botrunningsystem;
import com.kob.botrunningsystem.service.impl.BotRunningServiceImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BotRunningSystemApplication {
public static void main(String[] args) {
BotRunningServiceImpl.botPool.start();//启动线程
SpringApplication.run(BotRunningSystemApplication.class,args);
}
}
2.7BotInterface.java
用户写Bot实现的接口
package com.kob.botrunningsystem.utils;
public interface BotInterface {
public Integer nextMove(String input);//下一步方向
}
2.8Bot.java
package com.kob.botrunningsystem.utils;
public class Bot implements com.kob.botrunningsystem.utils.BotInterface {
@Override
public Integer nextMove(String input) {
return 0;
}
}
2.9Consumer的实现
package com.kob.botrunningsystem.service.impl.utils;
import com.kob.botrunningsystem.utils.BotInterface;
import org.joor.Reflect;
import java.util.UUID;
public class Comsumer extends Thread{
private Bot bot;
public void startTimeut(long timeout,Bot bot){
this.bot = bot;
this.start();
try {
this.join(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
this.interrupt();
}
}
private String addUid(String code,String uid){
int k = code.indexOf(" implements com.kob.botrunningsystem.utils.BotInterface");
return code.substring(0,k) + uid +code.substring(k);
}
@Override
public void run() {
UUID uuid = UUID.randomUUID();
String uid = uuid.toString().substring(0,8);
BotInterface botInterface = Reflect.compile(
"com.kob.botrunningsystem.utils.Bot" + uid,
addUid(bot.getBotCode(),uid)
).create().get();
Integer direction = botInterface.nextMove(bot.getInput());
System.out.println("move-direction: " + bot.getUserId() + " " + direction);
}
}
2.10BotPool.java
2.11测试
package com.kob.botrunningsystem.utils;
public class Bot implements com.kob.botrunningsystem.utils.BotInterface {
@Override
public Integer nextMove(String input) {
return 0;
}
}
package com.kob.botrunningsystem.utils;
public class Bot implements com.kob.botrunningsystem.utils.BotInterface {
@Override
public Integer nextMove(String input) {
return 2;
}
}
2.12小Bug
在游戏结束后,点到其他页面再点回pk页面,结果没有消失
3.将Bot执行结果传给前端
3.1.BackEnd接收Bot代码的结果
文件结构
backend
controller
pk
ReceiveBotMoveController.java
service
impl
pk
ReceiveBotMoveServiceImpl.java
pk
ReceiveBotMoveService.java
接口
package com.kob.backend.service.pk;
public interface ReceiveBotMoveService {
public String receiveBotMove(Integer userId,Integer direction);
}
WebSocketServer操作类
接口实现
package com.kob.backend.service.impl.pk;
import com.kob.backend.consumer.WebSocketServer;
import com.kob.backend.consumer.utils.Game;
import com.kob.backend.service.pk.ReceiveBotMoveService;
import org.springframework.stereotype.Service;
@Service
public class ReceiveBotMoveServiceImpl implements ReceiveBotMoveService {
@Override
public String receiveBotMove(Integer userId, Integer direction) {
System.out.println("receive bot move: " + userId + " " +direction);
if(WebSocketServer.users.get(userId)!=null){
Game game = WebSocketServer.users.get(userId).game;
if(game != null){
if(game.getPlayerA().getId().equals(userId)){//当前链接是A用户
game.setNextStepA(direction);
} else if(game.getPlayerB().getId().equals(userId)){//当前链接是B用户
game.setNextStepB(direction);
}
}
}
return "receive bot move success";
}
}
控制器
package com.kob.backend.controller.pk;
import com.kob.backend.service.pk.ReceiveBotMoveService;
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 ReceiveBotMoveController {
@Autowired
private ReceiveBotMoveService receiveBotMoveService;
@PostMapping("/pk/receive/bot/move/")
public String receiveBotMove(@RequestParam MultiValueMap<String,String> data){
System.out.println("+++++++++++++++++++++++++++++++");
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
Integer direction = Integer.parseInt(Objects.requireNonNull(data.getFirst("direction")));
return receiveBotMoveService.receiveBotMove(userId,direction);
}
}
权限控制(网关)
//实现用户密码的加密存储。==>加上此文件,必须是密文存储
//实现公开页面
package com.kob.backend.config;
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
.antMatchers("/pk/start/game/","/pk/receive/bot/move/").hasIpAddress("127.0.0.1")
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/websocket/**");//放行这一类链接
}
}
3.2.BotRunningSystem返回Bot执行结果
Consumer.java
package com.kob.botrunningsystem.service.impl.utils;
import com.kob.botrunningsystem.utils.BotInterface;
import org.joor.Reflect;
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.UUID;
@Component
public class Consumer extends Thread{
private static RestTemplate restTemplate ;
@Autowired
public void setRestTemplate(RestTemplate restTemplate){
Consumer.restTemplate = restTemplate;
}
private Bot bot;
private final static String receiveBotMoveUrl = "http://127.0.0.1:3000/pk/receive/bot/move/";
public void startTimeout(long timeout,Bot bot){
this.bot = bot;
this.start();
try {
this.join(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
this.interrupt();
}
}
private String addUid(String code,String uid){
int k = code.indexOf(" implements com.kob.botrunningsystem.utils.BotInterface");
System.out.println(k);
return code.substring(0,k) + uid +code.substring(k);
}
@Override
public void run() {
UUID uuid = UUID.randomUUID();
String uid = uuid.toString().substring(0,8);
BotInterface botInterface = Reflect.compile(
"com.kob.botrunningsystem.utils.Bot" + uid,
addUid(bot.getBotCode(),uid)
).create().get();
Integer direction = botInterface.nextMove(bot.getInput());
System.out.println("move-direction: " + bot.getUserId() + " " + direction);
MultiValueMap<String,String> data = new LinkedMultiValueMap<>();
data.add("user_id",bot.getUserId().toString());
data.add("direction",direction.toString());
restTemplate.postForObject(receiveBotMoveUrl,data,String.class);
}
}
测试
y总bot代码:
package com.kob.botrunningsystem.utils;
import java.util.ArrayList;
import java.util.List;
public class Bot implements com.kob.botrunningsystem.utils.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) {
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;
}
}