实现bot代码执行的微服务 botrunningsystem, 主服务前后端做出对应修改
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.kob</groupId>
|
||||
<artifactId>backendcloud</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.kob.botrunningsystem</groupId>
|
||||
<artifactId>botrunningsystem</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- 用于动态编译 java-8 代码 -->
|
||||
<!-- 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>
|
||||
|
||||
<!-- 用于安全验证-->
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 简化代码,可以帮助写一些构造函数, set() , get() 函数等-->
|
||||
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.26</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.kob.botrunningsystem;
|
||||
|
||||
import com.kob.botrunningsystem.service.BotRunningService;
|
||||
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) {
|
||||
// 启动 BotRunningSystem 服务之前启动 BotPool 线程
|
||||
BotRunningServiceImpl.botPool.start(); // 启动 Bot 执行线程
|
||||
|
||||
SpringApplication.run(BotRunningSystemApplication.class, args);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
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.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;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
@Override
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.kob.botrunningsystem.service;
|
||||
|
||||
public interface BotRunningService {
|
||||
// 参数: userId 用户, botCode bot代码, input 输入的地图信息(障碍物,两条蛇的位置,走过的路径等信息)
|
||||
String addBot(Integer userId, String botCode, String input);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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 {
|
||||
// BotPool 全局只有一个线程,所以这里定义成静态
|
||||
public static final BotPool botPool = new BotPool();
|
||||
|
||||
@Override
|
||||
public String addBot(Integer userId, String botCode, String input) {
|
||||
System.out.println(userId + " add a bot: " + botCode + " " + input);
|
||||
botPool.addBot(userId, botCode, input);
|
||||
return "add bot success";
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.kob.botrunningsystem.service.impl.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Bot {
|
||||
Integer userId;
|
||||
String botCode;
|
||||
String input;
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
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();
|
||||
// 定义队列: Queue 会在两个线程中操作 ==> 生产者会生产任务 ; 消费者会消费任务
|
||||
private final Queue<Bot> bots = new LinkedList<>();
|
||||
|
||||
// 往队列中插入一个 bot
|
||||
public void addBot(Integer userId, String botCode, String input) {
|
||||
// 这里涉及对 Queue 的操作,需要枷锁
|
||||
lock.lock();
|
||||
try {
|
||||
bots.add(new Bot(userId, botCode, input)); // 给队列添加新的 bot
|
||||
condition.signalAll(); // 唤醒另外一个线程
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// 消费任务:编译执行代码(取出来的任务) ==> 使用 JOOR
|
||||
// 每次执行一个代码都开一个线程,用来操控执行时间
|
||||
private void consume(Bot bot) {
|
||||
// 将代码发送给 Consumer
|
||||
Consumer consumer = new Consumer(); // 定义一个 consumer
|
||||
consumer.startTimeout(2000,bot); // 每个 bot 每回合最多执行 2 秒(每个玩家每回合设定等待 5 秒输入)
|
||||
}
|
||||
|
||||
|
||||
// 如果队列空,线程会阻塞;一旦有新的任务输入,则唤醒线程
|
||||
@Override
|
||||
public void run() {
|
||||
// 循环消费任务
|
||||
while (true) {
|
||||
lock.lock();
|
||||
// 如果 bots 队列为空,则阻塞当前线程
|
||||
if (bots.isEmpty()) {
|
||||
try {
|
||||
condition.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
lock.unlock(); // 报异常要解锁
|
||||
break; // 报异常则直接 break 中断进程
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 bots 不空,则
|
||||
else {
|
||||
// 取出队头任务
|
||||
Bot bot = bots.remove(); // Queue.remove() 返回并删除对头元素
|
||||
lock.unlock(); // 取完队头,即解锁
|
||||
|
||||
// 消费取出来的任务: consume() 比较耗时,因此需要在执行之前先解锁 lock.unlock
|
||||
consume(bot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
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 Bot bot;
|
||||
// 定义返回信息 URL
|
||||
private final static String receiveBotMoveUrl = "http://127.0.0.1:3000/pk/receive/bot/move/";
|
||||
|
||||
//注入 RestTemplate 用于给 backend 服务发送信息
|
||||
private static RestTemplate restTemplate;
|
||||
|
||||
//注入 RestTemplate 用于给 backend 服务发送信息
|
||||
@Autowired
|
||||
public void setRestTemplate(RestTemplate restTemplate) {
|
||||
Consumer.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
// 传入 等待时间 和 bot
|
||||
public void startTimeout(long timeout, Bot bot) {
|
||||
this.bot = bot;
|
||||
this.start(); // 执行线程
|
||||
|
||||
try {
|
||||
// 最多阻塞 timeout 时间后继续执行后面的操作(上面的线程最多等待执行 timeout 秒)
|
||||
// 相比 sleep 的好处是, join 最多执行 timeout 秒,如果这个早跑完任务就早点结束阻塞; sleep 则必须阻塞够 timeout 才会结束阻塞
|
||||
this.join(timeout);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
this.interrupt(); // 中断当前线程:一旦等待时间超过 timeout 秒,就中断线程
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Code 中的 Bot 类名后面," implements com.kob.botrunningsystem.utils.BotInterface" 之前加上 uid
|
||||
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 = UUID.randomUUID();
|
||||
String uid = uuid.toString().substring(0, 8); // 返回前 8 位 uuid 作为 uid
|
||||
|
||||
// Reflect 来自 JOOR 依赖: 可以动态的编译 java 代码
|
||||
// 重名类只会编译一遍,为了每次新线程都重新编译,
|
||||
// 在类名后加一个随机字符串 UUID(每次返回一个不一样的ID)
|
||||
BotInterface botInterface = Reflect.compile(
|
||||
"com.kob.botrunningsystem.utils.Bot" + uid,
|
||||
addUid(bot.getBotCode(), uid)
|
||||
).create().get(); // compile(编译) 完这个类之后 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());
|
||||
// 返回结果信息给 backend 服务端
|
||||
restTemplate.postForObject(receiveBotMoveUrl, data, String.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// 实现前端用户编写的 AI 的接口(api)
|
||||
package com.kob.botrunningsystem.utils;
|
||||
|
||||
public interface BotInterface {
|
||||
Integer nextMove(String input); // 下一步要走的方向
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
server.port=3002
|
||||
Reference in New Issue
Block a user