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 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 + + + + +