diff --git a/backendcloud/.gitignore b/backendcloud/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/backendcloud/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/backendcloud/.mvn/wrapper/maven-wrapper.jar b/backendcloud/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..bf82ff0
Binary files /dev/null and b/backendcloud/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/backendcloud/.mvn/wrapper/maven-wrapper.properties b/backendcloud/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..ca5ab4b
--- /dev/null
+++ b/backendcloud/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
diff --git a/backendcloud/backend/pom.xml b/backendcloud/backend/pom.xml
new file mode 100644
index 0000000..9738650
--- /dev/null
+++ b/backendcloud/backend/pom.xml
@@ -0,0 +1,130 @@
+
+
+ 4.0.0
+
+ com.kob
+ backendcloud
+ 0.0.1-SNAPSHOT
+
+
+ com.kob.backend
+ backend
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+ 3.0.2
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.26
+ provided
+
+
+
+
+
+ com.mysql
+ mysql-connector-j
+ 8.0.32
+
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.5.3.1
+
+
+
+
+
+ com.baomidou
+ mybatis-plus-generator
+ 3.5.3.1
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+ 3.0.2
+
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+ runtime
+
+
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+ runtime
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+ 3.0.2
+
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.24
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.jetbrains
+ annotations
+ 13.0
+ compile
+
+
+
+
\ No newline at end of file
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/BackendApplication.java b/backendcloud/backend/src/main/java/com/kob/backend/BackendApplication.java
new file mode 100644
index 0000000..4a7c2bf
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/BackendApplication.java
@@ -0,0 +1,13 @@
+package com.kob.backend;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class BackendApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BackendApplication.class, args);
+ }
+
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/CorsConfig.java b/backendcloud/backend/src/main/java/com/kob/backend/config/CorsConfig.java
new file mode 100644
index 0000000..c06a711
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/config/CorsConfig.java
@@ -0,0 +1,44 @@
+package com.kob.backend.config;
+
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+// 这个类用于解决CORS跨域问题
+@Configuration
+public class CorsConfig implements Filter {
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+ HttpServletResponse response = (HttpServletResponse) res;
+ HttpServletRequest request = (HttpServletRequest) req;
+
+ String origin = request.getHeader("Origin");
+ if (origin != null) {
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ }
+
+ String headers = request.getHeader("Access-Control-Request-Headers");
+ if (headers != null) {
+ response.setHeader("Access-Control-Allow-Headers", headers);
+ response.setHeader("Access-Control-Expose-Headers", headers);
+ }
+
+ response.setHeader("Access-Control-Allow-Methods", "*");
+ response.setHeader("Access-Control-Max-Age", "3600");
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
\ No newline at end of file
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/RestTemplateConfig.java b/backendcloud/backend/src/main/java/com/kob/backend/config/RestTemplateConfig.java
new file mode 100644
index 0000000..6d82461
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/config/RestTemplateConfig.java
@@ -0,0 +1,18 @@
+// 需要用到哪些东西的时候,就定义一个它的 @Configuration 再加一个 @Bean 注解,返回它的实例即可
+// 在需要使用的类里面加上一个 @Autowired 注入即可
+// 向后端(匹配系统)发请求的工具,想取得什么,就加一个 Bean 注解
+package com.kob.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+// 加注解 Configuration
+// RestTemplate 用于处理 Spring 服务之间的 http 请求
+@Configuration
+public class RestTemplateConfig {
+ @Bean
+ public RestTemplate getRestTemplate(){
+ return new RestTemplate();
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java b/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java
new file mode 100644
index 0000000..6ca863f
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java
@@ -0,0 +1,64 @@
+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;
+
+// TODO: 2023/2/19 WebSecurityConfigurerAdapter 已被弃用,注意替换
+@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();
+ }
+
+ /*
+ .antMatchers("/user/account/token/", "/user/account/register/").permitAll()
+ 用于配置公开链接
+ */
+ @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/").hasIpAddress("127.0.0.1")
+ .antMatchers(HttpMethod.OPTIONS).permitAll()
+ .anyRequest().authenticated();
+
+ http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+ }
+
+ // 用于配置 websocket:放行所有的 websocket/ 链接
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring().antMatchers("/websocket/**");
+ }
+}
\ No newline at end of file
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/WebSocketConfig.java b/backendcloud/backend/src/main/java/com/kob/backend/config/WebSocketConfig.java
new file mode 100644
index 0000000..f4a09f9
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/config/WebSocketConfig.java
@@ -0,0 +1,15 @@
+package com.kob.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+
+@Configuration
+public class WebSocketConfig {
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/filter/JwtAuthenticationTokenFilter.java b/backendcloud/backend/src/main/java/com/kob/backend/config/filter/JwtAuthenticationTokenFilter.java
new file mode 100644
index 0000000..670d63d
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/config/filter/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,73 @@
+package com.kob.backend.config.filter;
+
+//filter 类用于验证
+/*
+主要作用:
+ 1. 用来验证jwt token,如果验证成功,则将User信息注入上下文中
+*/
+
+import com.kob.backend.mapper.UserMapper;
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.utils.JwtUtil;
+import com.sun.xml.internal.bind.v2.TODO;
+import io.jsonwebtoken.Claims;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+ @Autowired
+ private UserMapper userMapper;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
+ String token = request.getHeader("Authorization");
+
+// TODO 这里可以更改 bearar 为其他字符作为验证前缀
+ if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ token = token.substring(7);
+
+
+
+// 核心验证逻辑
+ String userid;
+ try {
+// 将 token 解析,如果能成功解析出 userid 表示合法,否则表示不合法
+ Claims claims = JwtUtil.parseJWT(token);
+ userid = claims.getSubject();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ User user = userMapper.selectById(Integer.parseInt(userid));
+
+// User user = userMapper.selectById(new JwtAuthenticationUtil().getUserId(token));
+ if (user == null) {
+ throw new RuntimeException("用户名未登录");
+ }
+
+ UserDetailsImpl loginUser = new UserDetailsImpl(user);
+ UsernamePasswordAuthenticationToken authenticationToken =
+ new UsernamePasswordAuthenticationToken(loginUser, null, null);
+
+ SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java
new file mode 100644
index 0000000..69043ca
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java
@@ -0,0 +1,231 @@
+package com.kob.backend.consumer;
+
+// WebSocket用于前后端通信
+
+import com.alibaba.fastjson2.JSONObject;
+import com.kob.backend.config.RestTemplateConfig;
+import com.kob.backend.consumer.utils.Game;
+import com.kob.backend.consumer.utils.JwtAuthenticationUtil;
+import com.kob.backend.mapper.RecordMapper;
+import com.kob.backend.mapper.UserMapper;
+import com.kob.backend.pojo.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.web.WebProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import javax.xml.stream.events.StartDocument;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+@Component
+@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
+public class WebSocketServer {
+ /*
+ 存储所有链接:对所有 websocket 可见的全局变量,存储为 static 静态变量
+ 因为每个 websocket 实例都在一个独立的线程里,所以该公共变量应该是线程安全的
+ 使用线程安全的 Hash 表 ConcurrentHashMap<>(),
+ 将 userId 映射到 webSocketServer
+ */
+ public static final ConcurrentHashMap users = new ConcurrentHashMap<>();
+ // 用于和 MatchingSystem 进行通信
+ private static RestTemplate restTemplate;
+ // 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
+ private static UserMapper userMapper;
+ // 注入 RecordMapper 用于调用实现游戏数据到数据库的存储
+ public static RecordMapper recordMapper;
+ // 后端向前端发送信息,首先需要创建一个 session
+ private Session session = null;
+ // 用户信息:定义一个成员变量
+ private User user;
+ private Game game = null;
+ // addPlayer 添加用户到匹配池的 URL; removePlayer 从匹配池移除用户的 URL
+ private final static String addPlayerUrl = "http://127.0.0.1:3001/player/add/";
+ private final static String removePlayerUrl = "http://127.0.0.1:3001/player/remove/";
+
+ @Autowired
+ public void setRestTemplate(RestTemplate restTemplate) {
+ // 注入 RestTemplate:作用是微服务之间发送请求
+ WebSocketServer.restTemplate = restTemplate;
+ }
+
+ // 注入方法
+ @Autowired
+ public void setUserMapper(UserMapper userMapper) {
+ // 静态变量 userMapper 访问需要使用类名 WebSocketServer 访问
+ WebSocketServer.userMapper = userMapper;
+ }
+
+ @Autowired
+ public void setRecordMapper(RecordMapper recordMapper) {
+ WebSocketServer.recordMapper = recordMapper;
+ }
+
+
+ // @OnOpen 创建链接时自动触发这个函数
+ @OnOpen
+ public void onOpen(Session session, @PathParam("token") String token) throws IOException {
+ // 建立链接时需要将 session 存下来
+ this.session = session;
+ // 成功建立连接时,输出 connected!
+ System.out.println("backend connected!");
+ // 将 userId 取出来,通过 userId 将 user 找出来
+ // int userId = Integer.parseInt(token);
+ int userId = JwtAuthenticationUtil.getUserId(token);
+ this.user = userMapper.selectById(userId);
+
+ // 如果 user 存在, 表示用户登录成功, 用户信息是存在的
+ if (this.user != null) {
+ // 将 user 存到 users HashMap里
+ users.put(userId, this);
+
+ // (测试)后台输出看用户信息
+ // System.out.println(user);
+ }
+ // 否则,断开连接(这里需要抛出异常)
+ else {
+ this.session.close();
+ }
+
+
+ }
+
+ @OnClose
+ public void onClose() {
+ // 关闭链接
+ System.out.println("backend disconnected!");
+ // 断开连接时,需要将 user 从 users 里面删掉
+ if (this.user != null) {
+ users.remove(this.user.getId());
+ }
+ }
+
+ // 匹配逻辑函数
+ public static void startGame(Integer aId, Integer bId) {
+ User a = userMapper.selectById(aId);
+ User b = userMapper.selectById(bId);
+
+ // 匹配成功时,创建联机地图
+ Game game = new Game(13, 14, 20, a.getId(), b.getId());
+ game.createMap(); // 初始化地图
+ // 当用户不为空时,才能执行下面的操作(防止有人退出游戏后用户已经成为空指针)
+ if (users.get(a.getId()) != null)
+ users.get(a.getId()).game = game; // 将 game 赋给 a 玩家
+
+ if (users.get(b.getId()) != null)
+ users.get(b.getId()).game = game;
+
+ game.start(); // 开启新线程,执行函数
+
+ JSONObject respGameData = new JSONObject();
+ respGameData.put("game_map", game.getG());
+ respGameData.put("rows", game.getRows());
+ respGameData.put("cols", game.getCols());
+ respGameData.put("inner_walls_count", game.getInnerWallsCount());
+ respGameData.put("a_id", game.getPlayerA().getId());
+ respGameData.put("a_sx", game.getPlayerA().getSx());
+ respGameData.put("a_sy", game.getPlayerA().getSy());
+ respGameData.put("b_id", game.getPlayerB().getId());
+ respGameData.put("b_sx", game.getPlayerB().getSx());
+ respGameData.put("b_sy", game.getPlayerB().getSy());
+
+ // 将 a 配对成功的消息传回客户端
+ JSONObject respA = new JSONObject();
+ respA.put("event", "start-matching");
+ respA.put("opponent_username", b.getUsername());
+ respA.put("opponent_photo", b.getPhoto());
+ respA.put("game_data", respGameData);
+ // 获取 a 的链接,并通过 sendMessage 将消息传给前端
+ if (users.get(a.getId()) != null)
+ users.get(a.getId()).sendMessage(respA.toJSONString());
+
+ // 同理,将 b 的匹配成功信息传回给前端
+ JSONObject respB = new JSONObject();
+ respB.put("event", "start-matching");
+ respB.put("opponent_username", a.getUsername());
+ respB.put("opponent_photo", a.getPhoto());
+ respB.put("game_data", respGameData);
+ if (users.get(b.getId()) != null)
+ users.get(b.getId()).sendMessage(respB.toJSONString());
+ }
+
+ // 开始匹配的逻辑部分:开始匹配时向 MatchingSystem 服务端发一个请求
+ private void startMatching() {
+ System.out.println("start matching");
+ // 这里的参数列表需要与 MatchingSystem(微服务)->MatchingController->addPlayer 的参数对应
+ MultiValueMap data = new LinkedMultiValueMap<>();
+ data.add("user_id", this.user.getId().toString());
+ data.add("rating", this.user.getRating().toString());
+ // postForObject(请求的URL,传出的数据,返回值的类型.class) -> Java 反射机制
+ String res = restTemplate.postForObject(addPlayerUrl, data, String.class);
+ System.out.println(this.user.getId().toString() + " " + this.user.getUsername() + " " + res);
+ }
+
+ // 取消匹配的逻辑部分:向 MatchingSystem 发送请求:从匹配池移除一个用户
+ private void stopMatching() {
+ System.out.println("stop matching");
+ MultiValueMap data = new LinkedMultiValueMap<>();
+ data.add("user_id", this.user.getId().toString());
+ String res = restTemplate.postForObject(removePlayerUrl, data, String.class);
+ System.out.println(this.user.getId().toString() + " " + this.user.getUsername() + " " + res);
+ }
+
+ // direction 传入 move(移动) 方向参数
+ private void move(int direction) {
+ // 判断角色:如果是 A 角色,则将获取到的方向设置为 A 的 nextStep 方向
+ // user.getId() 是获取当前链接的用户 id
+ if (game.getPlayerA().getId().equals(user.getId())) {
+ game.setNextStepA(direction);
+ } else if (game.getPlayerB().getId().equals(user.getId())) {
+ game.setNextStepB(direction);
+ }
+ }
+
+ // @OnMessage 用于从前端接收请求: 一般 onMessage 当成路由使用,做为消息判断处理的中间部分
+ @OnMessage
+ public void onMessage(String message, Session session) {
+ // 从 Client 接收消息
+ System.out.println("receive message");
+ // 解析从前端接收到的请求
+ JSONObject data = JSONObject.parseObject(message);
+ String event = data.getString("event");
+ // System.out.println(event);
+
+ // 匹配状态函数调用
+ if ("start-matching".equals(event)) {
+ startMatching();
+ } else if ("stop-matching".equals(event)) {
+ stopMatching();
+ } else if ("move".equals(event)) {
+ move(data.getInteger("direction"));
+ }
+ }
+
+ @OnError
+ public void onError(Session session, Throwable error) {
+ error.printStackTrace();
+ }
+
+ // 从后端向前端发送信息
+ public void sendMessage(String message) {
+ // 异步通信过程,先加一个锁
+ synchronized (this.session) {
+ try {
+ // 将 message 发送到前端
+ this.session.getBasicRemote().sendText(message);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
+
+
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Cell.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Cell.java
new file mode 100644
index 0000000..f74e2a3
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Cell.java
@@ -0,0 +1,12 @@
+package com.kob.backend.consumer.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Cell {
+ int x, y;
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java
new file mode 100644
index 0000000..86a83a9
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java
@@ -0,0 +1,370 @@
+package com.kob.backend.consumer.utils;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.kob.backend.consumer.WebSocketServer;
+import com.kob.backend.pojo.Record;
+
+import javax.swing.event.InternalFrameEvent;
+import java.sql.Time;
+import java.util.*;
+import java.util.concurrent.locks.ReentrantLock;
+
+// 用来管理整个游戏流程:这里需要多线程
+public class Game extends Thread {
+ // 游戏地图:行数,列数,内部障碍物数量
+ private final Integer rows;
+ private final Integer cols;
+ private final Integer inner_walls_count;
+ // 地图数组
+ private final int[][] g;
+ // 定义"上右下左"四个方向的 dx, dy偏移量
+ private final int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
+ private final Player playerA, playerB;
+ // 玩家下一步操作状态定义
+ private Integer nextStepA = null;
+ private Integer nextStepB = null;
+ // 加锁:解决读写冲突用
+ private ReentrantLock lock = new ReentrantLock();
+ // 整个游戏的当前状态: playing(正在进行) --> finished(游戏结束)
+ private String status = "playing";
+ // 定义失败者: all(平局), A(A输), B(B输)
+ private String loser = "";
+
+
+ // 初始化(有参)构造函数
+ public Game(Integer rows, Integer cols, Integer inner_walls_count, Integer idA, Integer idB) {
+ this.rows = rows;
+ this.cols = cols;
+ this.inner_walls_count = inner_walls_count;
+ this.g = new int[rows][cols];
+ playerA = new Player(idA, rows - 2, 1, new ArrayList<>());
+ playerB = new Player(idB, 1, cols - 2, new ArrayList<>());
+ }
+
+ public Player getPlayerA() {
+ return playerA;
+ }
+
+ public Player getPlayerB() {
+ return playerB;
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ public int getCols() {
+ return cols;
+ }
+
+ public int getInnerWallsCount() {
+ return inner_walls_count;
+ }
+
+ private String getGameMapString() {
+ StringBuilder res = new StringBuilder();
+ // 将地图数据展开成一维
+/* for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ res.append(g[i][j]);
+ }
+ }*/
+ for (int[] row : g) {
+ for (int col : row) {
+ res.append(col);
+ }
+ }
+
+ return res.toString();
+ }
+
+ public void setNextStepA(Integer nextStepA) {
+ // 为了防止两方读写冲突,需要加线程锁之后操作 nextStep 值
+ lock.lock();
+ try {
+ this.nextStepA = nextStepA;
+ } finally {
+ // 操作完成后解锁
+ lock.unlock();
+ }
+ }
+
+ public void setNextStepB(Integer nextStepB) {
+ lock.lock();
+ try {
+ this.nextStepB = nextStepB;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // 返回生成的地图
+ public int[][] getG() {
+ return g;
+ }
+
+ // 联通检测方法---true(联通)---false(不通),参数: 起点坐标 sx,sy ,终点坐标 tx,ty
+ private boolean check_connectivity(int sx, int sy, int tx, int ty) {
+ // 起点就是终点时,结果联通,直接返回 true
+ if (sx == tx && sy == ty) return true;
+ g[sx][sy] = 1;
+
+ //枚举"上右下左"四个方向,求当前点下一个相邻点的坐标
+ for (int i = 0; i < 4; i++) {
+ int x = sx + dx[i];
+ int y = sy + dy[i];
+
+ // 判断是否撞到障碍物( g[x][y] == 0 表示没有碰到障碍物 ),如果没有赚到障碍物,且可以找到重点的话,返回 true(联通)
+ if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) {
+ if (check_connectivity(x, y, tx, ty)) {
+ // 还原状态
+ g[sx][sy] = 0;
+ return true;
+ }
+ }
+ }
+
+ // 还原状态
+ g[sx][sy] = 0;
+ return false;
+ }
+
+ // 画地图
+ public boolean draw() {
+ // 一开始现将所有障碍物初始化为 false
+ for (int r = 0; r < this.rows; r++) {
+ for (int c = 0; c < this.cols; c++) {
+ // 0 表示空地, 1 表示障碍物(墙)
+ g[r][c] = 0;
+ }
+ }
+
+ // 地图左右边缘障碍物
+ for (int r = 0; r < this.rows; r++) {
+ g[r][0] = g[r][this.cols - 1] = 1;
+ }
+ // 地图上下边缘障碍物
+ for (int c = 0; c < this.cols; c++) {
+ g[0][c] = g[this.rows - 1][c] = 1;
+ }
+
+ Random random = new Random();
+ // 创建内部随机障碍物,每次计算时会生成两个障碍物,因此这里的循环次数 this.inner_walls_count 需要处以 2
+ for (int i = 0; i < this.inner_walls_count / 2; i++) {
+ // 避免位置重复:重复遍历 1000 次,只要找到了已经存在障碍物的位置就禁止随机
+ for (int j = 0; j < 1000; j++) {
+ // 找出本次随机到的行-r 列-c 坐标
+ int r = random.nextInt(this.rows); // random.nextInt(7) 返回 0-7之间的随机整数
+ int c = random.nextInt(this.cols);
+
+ // 中心对称:当本坐标或者它的中心对称坐标已经存在障碍物了,则重新计算下一个坐标
+ if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1)
+ continue;
+
+ // 避免内部障碍物覆盖掉左下角和右上角的 A-B 角色出发点,如果是这两个位置,则重新计算下一个坐标
+ if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2)
+ continue;
+
+ // 将计算求得的随机障碍物合法的位置赋值为 1 ,以对该位置进行绘制(包括本坐标及其中心对称坐标)
+ g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1;
+
+ // 1000 次遍历中,规定数量的内部障碍物已经够数之后就 break 掉
+ break;
+ }
+ }
+
+ // 确保 A-B 角色的运动区域是联通的:如果不连通,则直接在创建地图对象之前取消绘制( return false )
+ return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);
+ }
+
+ public void createMap() {
+ // 循环绘制:如果发现哪次循环中画地图成功了,则跳出循环,绘制结束
+ for (int i = 0; i < 1000; i++) {
+ if (draw())
+ break;
+ }
+ }
+
+ // 获取两名玩家的下一步操作
+ private boolean nextStep() {
+ try {
+ // 返回下一步操作之前,先睡眠 200 毫秒,用于等待前端渲染完成,避免渲染速度跟不上后端运算处理速度
+ // 前端定义了每 1 秒钟 5 个格子,则每 200 毫秒走一格(每一步至少需要 200 毫秒的时间)
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ // 循环 50 秒钟,每次 100 毫秒,判断用户输入(50 * 100 = 5000 ms后如果没有接收到输入,则判断输赢)
+ for (int i = 0; i < 50; i++) {
+ try {
+ // 两次接收输入的等待间隔
+ Thread.sleep(100);
+ lock.lock();
+ try {
+ // 判断两名玩家是否都已经读取到输入:如果读入都非空,则表示读取结束,返回 true
+ if (nextStepA != null && nextStepB != null) {
+ // 将下一步操作存下来
+ playerA.getSteps().add(nextStepA);
+ playerB.getSteps().add(nextStepB);
+
+ // 成功获取到两名玩家的下一步操作,返回 true
+ return true;
+ }
+ } finally {
+ lock.unlock();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+
+ // 碰撞合法性检测辅助函数:需要判断 snakeA 和 snakeB 是否重合
+ private boolean checkValid(List cellsA, List cellsB) {
+ int n = cellsA.size(); // cellsA 的当前长度
+ Cell cell = cellsA.get(n - 1); // 取出 cellsA 的蛇头位置(数组最后一位)(第0个元素是蛇尾巴)
+ if (g[cell.x][cell.y] == 1) return false; // 判断 A 的最后一位(蛇头)的坐标是否是障碍物(值为1),如果是障碍物则返回false(判断已碰撞)
+
+ // 判断 A蛇头位置是否和A除了蛇头以外的其他身体部分坐标有重合
+ for (int i = 0; i < n - 1; i++) {
+ if (cellsA.get(i).x == cell.x && cellsA.get(i).y == cell.y)
+ return false;
+ }
+ for (Cell cellB : cellsB) { // 判断 A蛇头位置是否和B蛇身体各部分坐标有重合:有重合返回不合法(表示已经碰撞)
+ if (cellB.x == cell.x && cellB.y == cell.y)
+ return false;
+ }
+
+ // 以上判断条件如果都合法,则返回 true合法(表示没有碰撞)
+ return true;
+ }
+
+ // 判断两名玩家下一步操作是否合法
+ private void judge() {
+ // 取出 A,B 两蛇
+ List cellsA = playerA.getCells();
+ List cellsB = playerB.getCells();
+
+ boolean validA = checkValid(cellsA, cellsB);
+ boolean validB = checkValid(cellsB, cellsA);
+
+ // 如果 A 和 B 有人这一步不合法(已经产生碰撞),将游戏状态改为 "finished",表示游戏结束
+ if (!validA || !validB) {
+ status = "finished";
+
+ // 游戏结束时,判断输赢
+ if (!validA && !validB) {
+ loser = "all";
+ } else if (!validA && validB) {
+ loser = "A";
+ } else if (validA && !validB) {
+ loser = "B";
+ }
+ }
+ }
+
+ // 向两个 Client 广播信息
+ private void sendAllMessage(String message) {
+ if (WebSocketServer.users.get(playerA.getId()) != null)
+ WebSocketServer.users.get(playerA.getId()).sendMessage(message);
+ if (WebSocketServer.users.get(playerB.getId()) != null)
+ WebSocketServer.users.get(playerB.getId()).sendMessage(message);
+ }
+
+ // 向两个 Client 传递移动信息
+ private void sendMove() {
+ //由于这里需要读入 nextStepA, nextStepB, 这里需要加一下线程锁
+ lock.lock();
+ try {
+ JSONObject resp = new JSONObject();
+ resp.put("event", "move"); // event(事件类型): move(移动信息)
+ resp.put("a_direction", nextStepA); // a_direction( playerA 移动的方向)
+ resp.put("b_direction", nextStepB); // b_direction( playerB 移动的方向)
+ sendAllMessage(resp.toJSONString());
+ // 本次获取完下一步操作的同时需要进行再下一步操作输入,在这之前,需要将当前的 nextStep 清空
+ nextStepA = nextStepB = null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // 将游戏结果存到数据库中
+ private void saveToDataBase() {
+ Record record = new Record(
+ null,
+ playerA.getId(),
+ playerA.getSx(),
+ playerA.getSy(),
+ playerB.getId(),
+ playerB.getSx(),
+ playerB.getSy(),
+ playerA.getStepsString(),
+ playerB.getStepsString(),
+ getGameMapString(),
+ loser,
+ new Date()
+ );
+
+ WebSocketServer.recordMapper.insert(record);
+ }
+
+
+ // 向两个 Client 公布结果
+ private void sendResult() {
+ lock.lock();
+ try {
+ JSONObject resp = new JSONObject();
+ resp.put("event", "result"); // event(事件类型): result(结果)
+ resp.put("loser", loser); // 失败者
+ // 将最后碰撞时的蛇的眼睛指向传给前端
+ resp.put("a_eyes_finally_direction", nextStepA);
+ resp.put("b_eyes_finally_direction", nextStepB);
+ // 发送结果之前,先将结果存到数据库中
+ saveToDataBase();
+ sendAllMessage(resp.toJSONString());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // 重写线程函数
+ @Override
+ public void run() {
+ // 循环 1000 步: 13格x14格x(最大)3步/格 < 1000,保证 1000 次一定可以走完
+ for (int i = 0; i < 1000; i++) {
+ if (nextStep()) { // 是否获取到两条蛇的下一步操作
+ // 判断下一步操作是否合法
+ judge();
+
+ // 如果游戏状态还是 playing,则向两个玩家广播移动信息
+ if ("playing".equals(status)) {
+ sendMove();
+ } else if ("finished".equals(status)) {
+ // 如果游戏状态已经更改为了 finished,则将结果返回给 Client 并 break 中断执行后续操作
+ sendResult();
+ break;
+ }
+
+ } else {
+ // 未获取到某蛇的下一步操作,则将游戏状态改为 finished
+ status = "finished";
+ // 判断失败者:这里需要加锁,因为涉及到对 nextStep 的读操作
+ lock.lock();
+ try {
+ if (nextStepA == null && nextStepB == null) {
+ loser = "all";
+ } else if (nextStepA == null) {
+ loser = "A";
+ } else {
+ loser = "B";
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java
new file mode 100644
index 0000000..2606ae7
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java
@@ -0,0 +1,20 @@
+package com.kob.backend.consumer.utils;
+
+import com.kob.backend.utils.JwtUtil;
+import io.jsonwebtoken.Claims;
+
+//Jwt 验证配置工具类
+public class JwtAuthenticationUtil {
+ public static Integer getUserId(String token) {
+ // 默认 userid 赋值为 -1 ,表示不存在
+ int userId = -1;
+ try {
+ // 将 token 解析,如果能成功解析出 userid 表示合法,否则表示不合法
+ Claims claims = JwtUtil.parseJWT(token);
+ userId = Integer.parseInt(claims.getSubject());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return userId;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java
new file mode 100644
index 0000000..66be0ff
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java
@@ -0,0 +1,61 @@
+package com.kob.backend.consumer.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/// 玩家信息
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Player {
+ private Integer id;
+ private Integer sx;
+ private Integer sy;
+ // 玩家走过的路径中每一步的方向
+ private List steps;
+
+ // 检验当前回合蛇的长度(蛇尾)是否需要增加
+ public boolean checkTailIncreasing(int step) {
+ // 前 10 回合每次都增加一节蛇尾,后面每 3 回合增加一节蛇尾
+ if (step <= 10) return true;
+ if (step % 3 == 1) return true;
+
+ return false;
+ }
+
+ // 蛇身 Cell 列表:记录蛇身各部分坐标
+ public List getCells() {
+ List res = new ArrayList<>();
+
+ // 四方向偏移量
+ int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
+ int x = sx, y = sy; // 起始坐标
+ int step = 0; // 定义当前回合数: 开始时为 0 回合
+ res.add(new Cell(x, y)); // 现将蛇头添加进 List
+ for (int d : steps) { // 使用下一步方向求出下一个结点的坐标
+ x += dx[d];
+ y += dy[d];
+ res.add(new Cell(x, y)); // 添加新的身体结点
+ step++; // 回合数 +1
+ // 判断蛇尾要不要增加:如果本回合蛇尾不增加,则将本回合新生成的蛇尾(列表的第0个元素)删掉
+ if (!checkTailIncreasing(step)) {
+ res.remove(0);
+ }
+ }
+ return res;
+ }
+
+ // steps 转换为 string 的辅助函数
+ public String getStepsString() {
+ StringBuilder res = new StringBuilder();
+ for (int d : steps) {
+ res.append(d);
+ }
+
+ return res.toString();
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java
new file mode 100644
index 0000000..c49ad2d
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java
@@ -0,0 +1,25 @@
+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/")
+ public String startGame(@RequestParam MultiValueMap 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);
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/UserController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/UserController.java
new file mode 100644
index 0000000..c3b562e
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/UserController.java
@@ -0,0 +1,72 @@
+package com.kob.backend.controller.user;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.kob.backend.mapper.UserMapper;
+import com.kob.backend.pojo.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+//实现对用户类 User 的增删查操作->这个类仅用于调试,故 Deparecated 掉
+@Deprecated
+@RestController
+public class UserController {
+
+// 用到数据库里的 Mapper 时,需要加 @Autowired 注解
+ @Autowired
+ UserMapper userMapper;
+
+// 返回所有用户
+ @GetMapping("/user/all")
+ public List getAll() {
+// null 表示查询所有的
+ return userMapper.selectList(null);
+ }
+
+/*
+ @GetMapping("/user/{userId}/")
+ public User getUser(@PathVariable int userId) {
+// 查询某个用户
+// return userMapper.selectById(userId);
+
+// 封装查询语句:查询某个 id 的用户
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("id", userId);
+ return userMapper.selectOne(queryWrapper);
+ }
+*/
+
+ @GetMapping("/user/{userId}")
+ public List getUser(@PathVariable int userId) {
+// 封装查询语句:查询某个范围 id 的用户: ge 是大于等于, gt 是大于, le 是小于等于, lt 是小于
+ QueryWrapper queryWrapper1 = new QueryWrapper<>();
+ queryWrapper1.ge("id", 2).le("id", 4);
+ return userMapper.selectList(queryWrapper1);
+ }
+
+ @GetMapping("/user/add/{userId}/{username}/{password}")
+ public String addUser(@PathVariable int userId,
+ @PathVariable String username,
+ @PathVariable String password) {
+ if(password.length() < 6){
+ return "密码少于6位,请重设密码";
+ }
+// 加密明文密码并存入密文到数据库
+ PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+ String encodedPassword = passwordEncoder.encode(password);
+ User user = new User(userId, username, encodedPassword,"",null);
+ userMapper.insert(user);
+ return "Add User Successfully";
+ }
+
+ @GetMapping("/user/delete/{userId}")
+ public String deleteUser(@PathVariable int userId){
+ userMapper.deleteById(userId);
+ return "Delete User Successfully";
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/InfoController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/InfoController.java
new file mode 100644
index 0000000..e41cc7b
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/InfoController.java
@@ -0,0 +1,20 @@
+package com.kob.backend.controller.user.account;
+
+import com.kob.backend.service.user.account.InfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+public class InfoController {
+ @Autowired
+ private InfoService infoService;
+
+// 获取信息
+ @GetMapping("/user/account/info/")
+ public Map getInfo(){
+ return infoService.getInfo();
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/LoginController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/LoginController.java
new file mode 100644
index 0000000..60acf37
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/LoginController.java
@@ -0,0 +1,27 @@
+package com.kob.backend.controller.user.account;
+
+import com.kob.backend.service.user.account.LoginService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+//@RestController 配合 @PostMapping("/.../.../") 使用:映射 Url 地址
+@RestController
+public class LoginController {
+// @Autowired 注入接口
+ @Autowired
+ private LoginService loginService;
+
+// 登录使用 POST 请求,密文传输,更安全
+ @PostMapping("/user/account/token/")
+ public Map getToken(@RequestParam Map map){
+ String username = map.get("username");
+ String password = map.get("password");
+
+ return loginService.getToken(username,password);
+ }
+
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/RegisterController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/RegisterController.java
new file mode 100644
index 0000000..1ecf8dc
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/account/RegisterController.java
@@ -0,0 +1,24 @@
+package com.kob.backend.controller.user.account;
+
+import com.kob.backend.service.user.account.RegisterService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+public class RegisterController {
+ @Autowired
+ private RegisterService registerService;
+
+ @PostMapping("/user/account/register/")
+ public Map register(@RequestParam Map map) {
+ String username = map.get("username");
+ String password = map.get("password");
+ String confirmedPassword = map.get("confirmedPassword");
+ return registerService.register(username, password, confirmedPassword);
+ }
+
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/AddController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/AddController.java
new file mode 100644
index 0000000..2a958aa
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/AddController.java
@@ -0,0 +1,18 @@
+package com.kob.backend.controller.user.bot;
+
+import com.kob.backend.service.user.bot.AddService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+public class AddController {
+ @Autowired
+ private AddService addService;
+
+ @PostMapping("/user/bot/add/")
+ public Map add(@RequestParam Map data){
+ return addService.add(data);
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/GetListController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/GetListController.java
new file mode 100644
index 0000000..69c4848
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/GetListController.java
@@ -0,0 +1,22 @@
+package com.kob.backend.controller.user.bot;
+
+import com.kob.backend.pojo.Bot;
+import com.kob.backend.service.user.bot.GetListService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+public class GetListController {
+ @Autowired
+ private GetListService getListService;
+
+ @GetMapping("/user/bot/getlist/")
+ public List getList(){
+ return getListService.getList();
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/RemoveController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/RemoveController.java
new file mode 100644
index 0000000..630f709
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/RemoveController.java
@@ -0,0 +1,20 @@
+package com.kob.backend.controller.user.bot;
+
+import com.kob.backend.service.user.bot.RemoveService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+public class RemoveController {
+ @Autowired
+ private RemoveService removeService;
+
+ @PostMapping("/user/bot/remove/")
+ public Map remove(@RequestParam Map data){
+ return removeService.remove(data);
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/UpdateController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/UpdateController.java
new file mode 100644
index 0000000..8ee59e8
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/user/bot/UpdateController.java
@@ -0,0 +1,20 @@
+package com.kob.backend.controller.user.bot;
+
+import com.kob.backend.service.user.bot.UpdateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+public class UpdateController {
+ @Autowired
+ private UpdateService updateService;
+
+ @PostMapping("/user/bot/update/")
+ public Map update(@RequestParam Map data){
+ return updateService.update(data);
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/mapper/BotMapper.java b/backendcloud/backend/src/main/java/com/kob/backend/mapper/BotMapper.java
new file mode 100644
index 0000000..51c3372
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/mapper/BotMapper.java
@@ -0,0 +1,9 @@
+package com.kob.backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.kob.backend.pojo.Bot;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BotMapper extends BaseMapper {
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/mapper/RecordMapper.java b/backendcloud/backend/src/main/java/com/kob/backend/mapper/RecordMapper.java
new file mode 100644
index 0000000..4541ca4
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/mapper/RecordMapper.java
@@ -0,0 +1,9 @@
+package com.kob.backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.kob.backend.pojo.Record;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface RecordMapper extends BaseMapper {
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/mapper/UserMapper.java b/backendcloud/backend/src/main/java/com/kob/backend/mapper/UserMapper.java
new file mode 100644
index 0000000..71646a8
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/mapper/UserMapper.java
@@ -0,0 +1,11 @@
+//用于将 class 的操作转化成 sql 语句
+package com.kob.backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.kob.backend.pojo.User;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface UserMapper extends BaseMapper {
+
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/pojo/Bot.java b/backendcloud/backend/src/main/java/com/kob/backend/pojo/Bot.java
new file mode 100644
index 0000000..eade9f0
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/pojo/Bot.java
@@ -0,0 +1,35 @@
+// 用于将 Mysql 表 Bot 转换为 class
+// 数据库中 user_id 这种在 pojo 里要命名为 userId
+package com.kob.backend.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Bot {
+ // 主键自增
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ // 注意:在pojo中需要定义成userId,在queryWrapper中的名称仍然为user_id
+ private Integer userId;
+ private String title;
+ private String description;
+ private String content;
+ // pojo中定义日期格式的注解:@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ // timezone 用于修改时区
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+ private Date createtime;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+ private Date modifytime;
+
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/pojo/Record.java b/backendcloud/backend/src/main/java/com/kob/backend/pojo/Record.java
new file mode 100644
index 0000000..dd00e62
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/pojo/Record.java
@@ -0,0 +1,34 @@
+package com.kob.backend.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.Timer;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Record {
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+
+ private Integer aId;
+ private Integer aSx;
+ private Integer aSy;
+ private Integer bId;
+ private Integer bSx;
+ private Integer bSy;
+
+ private String aSteps;
+ private String bSteps;
+ private String map;
+ private String loser;
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
+ private Date createtime;
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/pojo/User.java b/backendcloud/backend/src/main/java/com/kob/backend/pojo/User.java
new file mode 100644
index 0000000..6251019
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/pojo/User.java
@@ -0,0 +1,30 @@
+//用于将 mysql 表 User 转换为 class
+package com.kob.backend.pojo;
+
+/*
+Data 用于编译时自动生成 getter setter 方法;
+NoArgsConstructor 用于生成无参构造函数;
+AllArgsConstructor 用于生成所有参数构造函数.
+*/
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class User {
+
+// 使用对象类型定义而不是 Int 防止 Mybatis 报错
+// @TableId(type = IdType.AUTO) 用于 id 的自增
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+ private String username;
+ private String password;
+ private String photo;
+
+ private Integer rating;
+
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/UserDetailsServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..223512a
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/UserDetailsServiceImpl.java
@@ -0,0 +1,35 @@
+package com.kob.backend.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.kob.backend.mapper.UserMapper;
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+//自定义 Spring-Starter-Security 登录信息; @Autowired 需要在外层加入 @Service 注解
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+ @Autowired
+ private UserMapper userMapper;
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+/*
+ 从数据库里读取用户信息,比对用户名密码与数据库存在的用户名密码,信息一致,给用户发一个 sessionID ,
+ 存到用户本地浏览器,服务器同时会自己存一份。未来用户再登录时服务器会将自己的 sessionID 与用户发过来
+ 请求包含的 sessionID 进行比照,如果一致,则授权成功。
+*/
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("username",username);
+ User user = userMapper.selectOne(queryWrapper);
+ if(user == null){
+ throw new RuntimeException("用户不存在");
+ }
+ return new UserDetailsImpl(user);
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java
new file mode 100644
index 0000000..387e8bf
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java
@@ -0,0 +1,15 @@
+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); // 调用 WebSocketServer->startGame 函数
+ return "start game success";
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/InfoServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/InfoServiceImpl.java
new file mode 100644
index 0000000..119a3d4
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/InfoServiceImpl.java
@@ -0,0 +1,34 @@
+package com.kob.backend.service.impl.user.account;
+
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.service.user.account.InfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+@Service
+public class InfoServiceImpl implements InfoService {
+ @Override
+ public Map getInfo() {
+ UsernamePasswordAuthenticationToken authenticationToken =
+ (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+
+ UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
+ User user = loginUser.getUser();
+
+ Map map = new HashMap<>();
+ map.put("error_message","success");
+ map.put("id",user.getId().toString());
+ map.put("username",user.getUsername());
+ map.put("photo",user.getPhoto());
+
+
+ return map;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/LoginServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/LoginServiceImpl.java
new file mode 100644
index 0000000..19071fe
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/LoginServiceImpl.java
@@ -0,0 +1,50 @@
+package com.kob.backend.service.impl.user.account;
+
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.service.user.account.LoginService;
+import com.kob.backend.utils.JwtUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+//@Service 注解用于注入接口实现到 Spring 里面
+@Service
+public class LoginServiceImpl implements LoginService {
+
+// @Autowired 用于注入
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Override
+ public Map getToken(String username, String password) {
+
+// 封装用户名和密码成 UsernamePasswordAuthenticationToken 类:此类不存储明文,而是存储加密之后的字符串
+ UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
+
+// 验证是否正常登录,登录验证失败时会自动处理(报异常)
+ Authentication authenticate = authenticationManager.authenticate(authenticationToken);
+// 登录成功,取出用户
+ UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
+ User user = loginUser.getUser();
+
+// 封装 jwt 信息:将 userID 转换为 String
+ String jwt = JwtUtil.createJWT(user.getId().toString());
+
+/*
+ 成功之后返回结果:
+ 1. "error_message" 为 success;失败之后会报异常,自动处理掉
+ 2. "token" 返回 jwt-token 信息
+*/
+ Map map = new HashMap<>();
+ map.put("error_message","success");
+ map.put("token",jwt);
+
+ return map;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/RegisterServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/RegisterServiceImpl.java
new file mode 100644
index 0000000..57de7c9
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/account/RegisterServiceImpl.java
@@ -0,0 +1,85 @@
+package com.kob.backend.service.impl.user.account;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.kob.backend.pojo.User;
+import com.kob.backend.mapper.UserMapper;
+import com.kob.backend.service.user.account.RegisterService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+
+@Service
+public class RegisterServiceImpl implements RegisterService {
+ // 这里使用 @Autowired 注入是为了调用数据库,然后进行数据库查询,比较是否有用户名重复
+ @Autowired
+ private UserMapper userMapper;
+
+ @Override
+ public Map register(String username, String password, String confirmedPassword) {
+ Map map = new HashMap<>();
+// 如果用户名为空,则提示用户返回
+ if (username == null) {
+ map.put("error_message", "用户名不能为空");
+ return map;
+ }
+ if (password == null || confirmedPassword == null) {
+ map.put("error_message", "密码不能为空");
+ return map;
+ }
+// 用户名需要将输入的首尾空格删掉
+ username = username.trim();
+ if (username.length() == 0) {
+ map.put("error_message", "用户名不能为空");
+ return map;
+ }
+ if (password.length() == 0) {
+ map.put("error_message", "密码不能为空");
+ return map;
+ }
+ if (username.length() > 100) {
+ map.put("error_message", "用户名过长");
+ return map;
+ }
+
+
+
+/*
+ 查询数据库里是否有用户名 this.username 已存在的用户,并将结果存入 users 中,
+ 如果发现 users 不是空的,则告诉注册用户当前用户名已存在
+*/
+ QueryWrapper queryWrapper = new QueryWrapper();
+ queryWrapper.eq("username", username);
+ List users = userMapper.selectList(queryWrapper);
+ if (!users.isEmpty()) {
+ map.put("error_message", "用户名已存在");
+ return map;
+ }
+ // 密码验证是 String 类型比较,应该用 equals() 方法
+ else if (!confirmedPassword.equals(password)) {
+ map.put("error_message", "两次密码输入不一致");
+ return map;
+ } else if (password.length() < 2) {
+ map.put("error_message", "密码不能少于2位");
+ return map;
+ } else if (password.length() > 100) {
+ map.put("error_message", "密码过长");
+ return map;
+ }
+
+// 异常情况判断结束,开始将合法用户注册信息注入数据库
+// 对密码进行加密
+ String encodedPassword = new BCryptPasswordEncoder().encode(password);
+// 默认头像
+ String photo = "https://typoraflykhan.oss-cn-beijing.aliyuncs.com/202302251824054.png";
+// id 是数据库自增,这里生成新用户只需要将 id 参数写为 null 即可
+ User user = new User(null, username, encodedPassword, photo, 1500);
+ userMapper.insert(user);
+
+ map.put("error_message", "successRegister");
+ return map;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/AddServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/AddServiceImpl.java
new file mode 100644
index 0000000..df1afa5
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/AddServiceImpl.java
@@ -0,0 +1,78 @@
+package com.kob.backend.service.impl.user.bot;
+
+import com.kob.backend.mapper.BotMapper;
+import com.kob.backend.pojo.Bot;
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.service.user.bot.AddService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class AddServiceImpl implements AddService {
+ // 使用 @Autowired 注入接口,调用 BotMapper 来修改数据库
+ @Autowired
+ private BotMapper botMapper;
+
+ @Override
+ public Map add(Map data) {
+// 取出 User
+ UsernamePasswordAuthenticationToken authenticationToken =
+ (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
+ User user = loginUser.getUser();
+
+ String title = data.get("title");
+ String description = data.get("description");
+ String content = data.get("content");
+
+ Map map = new HashMap<>();
+
+ if (title == null || title.length() == 0) {
+ map.put("error_message", "标题不能为空");
+ return map;
+ }
+
+ if (title.length() > 100) {
+ map.put("error_message", "标题长度不能大于100");
+ return map;
+ }
+
+ if (description == null || description.length() == 0) {
+ // 提供默认描述信息
+ description = "这个用户很懒,什么也没留下~";
+ }
+
+ if (description != null && description.length() > 300) {
+ map.put("error_message", "描述信息长度不能大于300");
+ return map;
+ }
+
+ if (content == null || content.length() == 0) {
+ map.put("error_message", "代码不能为空");
+ return map;
+ }
+
+ if (content.length() > 10000) {
+ map.put("error_message", "代码长度不能超过一万");
+ return map;
+ }
+
+// 定义当前时间
+ Date now = new Date();
+// 定义一个新 Bot ;创建时间和修改时间开始应该一样
+ Bot bot = new Bot(null, user.getId(), title, description, content, now, now);
+
+// 将新建的 Bot 注入数据库中
+ botMapper.insert(bot);
+ map.put("error_message", "success");
+
+ return map;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/GetListServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/GetListServiceImpl.java
new file mode 100644
index 0000000..37e6bc9
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/GetListServiceImpl.java
@@ -0,0 +1,33 @@
+package com.kob.backend.service.impl.user.bot;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.kob.backend.mapper.BotMapper;
+import com.kob.backend.pojo.Bot;
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.service.user.bot.GetListService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class GetListServiceImpl implements GetListService {
+ @Autowired
+ private BotMapper botMapper;
+
+ @Override
+ public List getList() {
+ UsernamePasswordAuthenticationToken authenticationToken =
+ (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
+ User user = loginUser.getUser();
+
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("user_id",user.getId());
+
+ return botMapper.selectList(queryWrapper);
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/RemoveServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/RemoveServiceImpl.java
new file mode 100644
index 0000000..60f763f
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/RemoveServiceImpl.java
@@ -0,0 +1,53 @@
+package com.kob.backend.service.impl.user.bot;
+
+import com.kob.backend.mapper.BotMapper;
+import com.kob.backend.pojo.Bot;
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.service.user.bot.RemoveService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class RemoveServiceImpl implements RemoveService {
+ @Autowired
+ private BotMapper botMapper;
+
+ @Override
+ public Map remove(Map data) {
+// 先取出当前用户信息
+ UsernamePasswordAuthenticationToken authenticationToken =
+ (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
+ User user = loginUser.getUser();
+
+// 从用户输入参数判断要删除的 bot id
+ int bot_id = Integer.parseInt(data.get("bot_id"));
+ Bot bot = botMapper.selectById(bot_id);
+
+// 定义返回值
+ Map map = new HashMap<>();
+
+// 如果 bot 不存在
+ if (bot == null) {
+ map.put("error_message", "Bot 不存在或已被删除");
+ return map;
+ }
+
+// 如果 bot 的 user_id 不等于 User 的 id
+ if (!bot.getUserId().equals(user.getId())) {
+ map.put("error_message", "你无权删除别人的 bot");
+ return map;
+ }
+
+// 判断成功后进行删除
+ botMapper.deleteById(bot_id);
+ map.put("error_message", "success");
+ return map;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/UpdateServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/UpdateServiceImpl.java
new file mode 100644
index 0000000..7894f0a
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/user/bot/UpdateServiceImpl.java
@@ -0,0 +1,90 @@
+package com.kob.backend.service.impl.user.bot;
+
+import com.kob.backend.mapper.BotMapper;
+import com.kob.backend.pojo.Bot;
+import com.kob.backend.pojo.User;
+import com.kob.backend.service.impl.utils.UserDetailsImpl;
+import com.kob.backend.service.user.bot.UpdateService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class UpdateServiceImpl implements UpdateService {
+
+ @Autowired
+ private BotMapper botMapper;
+
+ @Override
+ public Map update(Map data) {
+// 先获取当前用户,用于判断更新对象是否有权限
+ UsernamePasswordAuthenticationToken authenticationToken =
+ (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
+ User user = loginUser.getUser();
+
+ int bot_id = Integer.parseInt(data.get("bot_id"));
+
+ String title = data.get("title");
+ String description = data.get("description");
+ String content = data.get("content");
+
+ Map map = new HashMap<>();
+
+ if (title == null || title.length() == 0) {
+ map.put("error_message", "标题不能为空");
+ return map;
+ }
+ if (title.length() > 100) {
+ map.put("error_message", "标题长度不能大于100");
+ return map;
+ }
+ if (description == null || description.length() == 0) {
+ // 提供默认描述信息
+ description = "这个用户很懒,什么也没留下~";
+ }
+ if (description != null && description.length() > 300) {
+ map.put("error_message", "描述信息长度不能大于300");
+ return map;
+ }
+ if (content == null || content.length() == 0) {
+ map.put("error_message", "代码不能为空");
+ return map;
+ }
+ if (content.length() > 10000) {
+ map.put("error_message", "代码长度不能超过一万");
+ return map;
+ }
+
+ Bot bot = botMapper.selectById(bot_id);
+
+ if (bot == null) {
+ map.put("error_message", "所查 bot 不存在或已被删除");
+ return map;
+ }
+
+ if (!bot.getUserId().equals(user.getId())) {
+ map.put("error_message", "你无权更改别人的 bot");
+ return map;
+ }
+
+ if (bot.getTitle().equals(title) && bot.getDescription().equals(description) && bot.getContent().equals(content)) {
+ map.put("error_message", "未作出修改");
+ return map;
+ }
+
+ Date now = new Date();
+ Date createTime = bot.getCreatetime();
+
+ Bot newBot = new Bot(bot.getId(), user.getId(), title, description, content, createTime, now);
+
+ botMapper.updateById(newBot);
+ map.put("error_message", "success");
+ return map;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/utils/UserDetailsImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/utils/UserDetailsImpl.java
new file mode 100644
index 0000000..aa04c18
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/utils/UserDetailsImpl.java
@@ -0,0 +1,61 @@
+package com.kob.backend.service.impl.utils;
+
+import com.kob.backend.pojo.User;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+
+//实现 UserDetails 接口:需要加上 lombok 的注解,来自动生成构造函数
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserDetailsImpl implements UserDetails {
+
+// 定义一个 pojo 的 User 类对象
+ private User user;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return null;
+ }
+
+// 获取 pojo -> User 类对象 user 的用户密码
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+// 获取 pojo -> User 类对象 user 的用户名
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+// 用户账号是否没有过期
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+// 用户是否没有被锁定
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+// 用户授权是否没有过期
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+// 用户是否被启用
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java
new file mode 100644
index 0000000..3b06180
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java
@@ -0,0 +1,7 @@
+// 匹配接口:配合 MatchingSystem 微服务进行使用
+package com.kob.backend.service.pk;
+
+public interface StartGameService {
+ // 参数是匹配的两位玩家的 id
+ String startGame(Integer aId, Integer bId);
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/InfoService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/InfoService.java
new file mode 100644
index 0000000..9826259
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/InfoService.java
@@ -0,0 +1,8 @@
+package com.kob.backend.service.user.account;
+
+import java.util.Map;
+
+//根据令牌返回用户信息
+public interface InfoService {
+ public Map getInfo();
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/LoginService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/LoginService.java
new file mode 100644
index 0000000..16b813c
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/LoginService.java
@@ -0,0 +1,8 @@
+package com.kob.backend.service.user.account;
+
+import java.util.Map;
+
+//验证用户名密码,验证成功后返回jwt token(令牌)
+public interface LoginService {
+ public Map getToken(String username, String password);
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/RegisterService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/RegisterService.java
new file mode 100644
index 0000000..5e59592
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/account/RegisterService.java
@@ -0,0 +1,9 @@
+package com.kob.backend.service.user.account;
+
+import java.util.Map;
+
+//注册账号
+public interface RegisterService {
+// confirmedPassword 密码确认
+ public Map register(String username, String password, String confirmedPassword);
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/AddService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/AddService.java
new file mode 100644
index 0000000..5bc0d1c
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/AddService.java
@@ -0,0 +1,7 @@
+package com.kob.backend.service.user.bot;
+
+import java.util.Map;
+
+public interface AddService {
+ public Map add(Map data);
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/GetListService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/GetListService.java
new file mode 100644
index 0000000..6997651
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/GetListService.java
@@ -0,0 +1,9 @@
+package com.kob.backend.service.user.bot;
+
+import com.kob.backend.pojo.Bot;
+
+import java.util.List;
+
+public interface GetListService {
+ List getList(); // getList() 不需要传参数
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/RemoveService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/RemoveService.java
new file mode 100644
index 0000000..3f256b9
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/RemoveService.java
@@ -0,0 +1,6 @@
+package com.kob.backend.service.user.bot;
+
+import java.util.Map;
+public interface RemoveService {
+ public Map remove(Map data);
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/UpdateService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/UpdateService.java
new file mode 100644
index 0000000..d05e02d
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/service/user/bot/UpdateService.java
@@ -0,0 +1,7 @@
+package com.kob.backend.service.user.bot;
+
+import java.util.Map;
+
+public interface UpdateService {
+ Map update(Map data);
+}
diff --git a/backendcloud/backend/src/main/java/com/kob/backend/utils/JwtUtil.java b/backendcloud/backend/src/main/java/com/kob/backend/utils/JwtUtil.java
new file mode 100644
index 0000000..b23d22b
--- /dev/null
+++ b/backendcloud/backend/src/main/java/com/kob/backend/utils/JwtUtil.java
@@ -0,0 +1,74 @@
+package com.kob.backend.utils;
+
+//jwt工具类,用来创建、解析 jwt token
+/*
+主要作用:
+ 1. 将字符串加上密钥,加上有效期,转换为加密后的字符串;
+ 2. 给一个令牌,将其 userID 解析出来.
+
+依赖:添加到 pom.xml 中,然后使用 Maven 重新加载依赖项
+ jjwt-apt;
+ jjwt-impl;
+ jjwt-jackson.
+*/
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+import java.util.Date;
+import java.util.UUID;
+
+@Component
+public class JwtUtil {
+ public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期14天:单位-毫秒 ms
+ public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232113afasdfad"; // 密钥:随机字符串-大小写英文字母+数字
+
+ public static String getUUID() {
+ return UUID.randomUUID().toString().replaceAll("-", "");
+ }
+
+ public static String createJWT(String subject) {
+ JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
+ return builder.compact();
+ }
+
+ private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+ SecretKey secretKey = generalKey();
+ long nowMillis = System.currentTimeMillis();
+ Date now = new Date(nowMillis);
+ if (ttlMillis == null) {
+ ttlMillis = JwtUtil.JWT_TTL;
+ }
+
+ long expMillis = nowMillis + ttlMillis;
+ Date expDate = new Date(expMillis);
+ return Jwts.builder()
+ .setId(uuid)
+ .setSubject(subject)
+ .setIssuer("sg")
+ .setIssuedAt(now)
+ .signWith(signatureAlgorithm, secretKey)
+ .setExpiration(expDate);
+ }
+
+ public static SecretKey generalKey() {
+ byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
+ return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
+ }
+
+ public static Claims parseJWT(String jwt) throws Exception {
+ SecretKey secretKey = generalKey();
+ return Jwts.parserBuilder()
+ .setSigningKey(secretKey)
+ .build()
+ .parseClaimsJws(jwt)
+ .getBody();
+ }
+}
\ No newline at end of file
diff --git a/backendcloud/backend/src/main/resources/application.properties b/backendcloud/backend/src/main/resources/application.properties
new file mode 100644
index 0000000..2a217c6
--- /dev/null
+++ b/backendcloud/backend/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+#default server port
+server.port=3000
+
+#mysql database connect profile
+spring.datasource.username=root
+spring.datasource.password=mysqlPassword
+spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
diff --git a/backendcloud/backend/src/main/resources/static/image/img.png b/backendcloud/backend/src/main/resources/static/image/img.png
new file mode 100644
index 0000000..857520a
Binary files /dev/null and b/backendcloud/backend/src/main/resources/static/image/img.png differ
diff --git a/backendcloud/backend/src/main/resources/templates/pk/index.html b/backendcloud/backend/src/main/resources/templates/pk/index.html
new file mode 100644
index 0000000..4af4c51
--- /dev/null
+++ b/backendcloud/backend/src/main/resources/templates/pk/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Title
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backendcloud/backend/src/test/java/com/kob/backend/BackendApplicationTests.java b/backendcloud/backend/src/test/java/com/kob/backend/BackendApplicationTests.java
new file mode 100644
index 0000000..b7a66fd
--- /dev/null
+++ b/backendcloud/backend/src/test/java/com/kob/backend/BackendApplicationTests.java
@@ -0,0 +1,18 @@
+package com.kob.backend;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@SpringBootTest
+class BackendApplicationTests {
+
+// 测试明文加密
+ @Test
+ void contextLoads() {
+ PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+ System.out.println(passwordEncoder.encode("123"));
+ }
+
+}
diff --git a/backendcloud/matchingsystem/pom.xml b/backendcloud/matchingsystem/pom.xml
new file mode 100644
index 0000000..4dc1bfc
--- /dev/null
+++ b/backendcloud/matchingsystem/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+ com.kob
+ backendcloud
+ 0.0.1-SNAPSHOT
+
+
+ com.kob.matchingsystem
+ matchingsystem
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.26
+ provided
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+ 3.0.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
\ No newline at end of file
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/MatchingSystemApplication.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/MatchingSystemApplication.java
new file mode 100644
index 0000000..d4754c8
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/MatchingSystemApplication.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/config/RestTemplateConfig.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/config/RestTemplateConfig.java
new file mode 100644
index 0000000..3e9ab47
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/config/RestTemplateConfig.java
@@ -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();
+ }
+}
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/config/SecurityConfig.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/config/SecurityConfig.java
new file mode 100644
index 0000000..df09e44
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/config/SecurityConfig.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java
new file mode 100644
index 0000000..d022657
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java
@@ -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 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 data) {
+ Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
+ return matchingService.removePlayer(userId);
+ }
+
+}
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java
new file mode 100644
index 0000000..39df470
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java
@@ -0,0 +1,10 @@
+// 定义接口
+package com.kob.matchingsystem.service;
+
+public interface MatchingService {
+ // 给匹配池添加一名玩家
+ String addPlayer(Integer userId, Integer rating);
+
+ // 从匹配池删除一名玩家
+ String removePlayer(Integer userId);
+}
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java
new file mode 100644
index 0000000..c4e4018
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java
@@ -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";
+ }
+}
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java
new file mode 100644
index 0000000..9e00040
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java
@@ -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 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 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 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 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;
+ }
+ }
+ }
+}
diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java
new file mode 100644
index 0000000..a6576af
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java
@@ -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; // 等待时间
+}
diff --git a/backendcloud/matchingsystem/src/main/resources/application.properties b/backendcloud/matchingsystem/src/main/resources/application.properties
new file mode 100644
index 0000000..032aec6
--- /dev/null
+++ b/backendcloud/matchingsystem/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port=3001
\ No newline at end of file
diff --git a/backendcloud/mvnw b/backendcloud/mvnw
new file mode 100644
index 0000000..8a8fb22
--- /dev/null
+++ b/backendcloud/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/backendcloud/mvnw.cmd b/backendcloud/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/backendcloud/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/backendcloud/pom.xml b/backendcloud/pom.xml
new file mode 100644
index 0000000..d4b524d
--- /dev/null
+++ b/backendcloud/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.7
+
+
+ com.kob
+ backendcloud
+ 0.0.1-SNAPSHOT
+ backendcloud
+ backendcloud
+
+ matchingsystem
+ backend
+
+ pom
+
+
+ 1.8
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ 2022.0.1
+ pom
+ import
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
| | | | | |