创建cloud父组件,并将项目改为主服务+微服务MatchingSystem

This commit is contained in:
flykhan 2023-03-05 19:27:35 +08:00
parent c3c890be51
commit 3cc2c3c698
66 changed files with 3086 additions and 0 deletions

33
backendcloud/.gitignore vendored Normal file
View File

@ -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/

Binary file not shown.

View File

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

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kob</groupId>
<artifactId>backendcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.kob.backend</groupId>
<artifactId>backend</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- dependencies 里面存放依赖-->
<dependencies>
<!-- JDBC 依赖,用于操作连接数据库-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>3.0.2</version>
</dependency>
<!-- 简化代码,可以帮助写一些构造函数, set() , get() 函数等-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
<!-- mysql 连接驱动-->
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
</dependency>
<!-- mybatis-plus 用于低代码开发-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- mybatis-plus-generator 用于自动生成一些函数-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 用于安全验证-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.0.2</version>
</dependency>
<!-- 用于使用 JWT 验证-->
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 用于配置使用 websocket-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>3.0.2</version>
</dependency>
<!-- json 用于前后端通信-->
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.24</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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();
}
}

View File

@ -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/**");
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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<Integer, WebSocketServer> 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<String, String> 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<String, String> 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();
}
}
}
}

View File

@ -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;
}

View File

@ -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<Cell> cellsA, List<Cell> 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<Cell> cellsA = playerA.getCells();
List<Cell> 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();
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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<Integer> steps;
// 检验当前回合蛇的长度(蛇尾)是否需要增加
public boolean checkTailIncreasing(int step) {
// 10 回合每次都增加一节蛇尾,后面每 3 回合增加一节蛇尾
if (step <= 10) return true;
if (step % 3 == 1) return true;
return false;
}
// 蛇身 Cell 列表:记录蛇身各部分坐标
public List<Cell> getCells() {
List<Cell> 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();
}
}

View File

@ -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<String, String> 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);
}
}

View File

@ -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<User> getAll() {
// null 表示查询所有的
return userMapper.selectList(null);
}
/*
@GetMapping("/user/{userId}/")
public User getUser(@PathVariable int userId) {
// 查询某个用户
// return userMapper.selectById(userId);
// 封装查询语句:查询某个 id 的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", userId);
return userMapper.selectOne(queryWrapper);
}
*/
@GetMapping("/user/{userId}")
public List<User> getUser(@PathVariable int userId) {
// 封装查询语句:查询某个范围 id 的用户: ge 是大于等于, gt 是大于, le 是小于等于, lt 是小于
QueryWrapper<User> 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";
}
}

View File

@ -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<String,String> getInfo(){
return infoService.getInfo();
}
}

View File

@ -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<String,String> getToken(@RequestParam Map<String,String> map){
String username = map.get("username");
String password = map.get("password");
return loginService.getToken(username,password);
}
}

View File

@ -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<String, String> register(@RequestParam Map<String, String> map) {
String username = map.get("username");
String password = map.get("password");
String confirmedPassword = map.get("confirmedPassword");
return registerService.register(username, password, confirmedPassword);
}
}

View File

@ -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<String,String> add(@RequestParam Map<String,String> data){
return addService.add(data);
}
}

View File

@ -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<Bot> getList(){
return getListService.getList();
}
}

View File

@ -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<String,String> remove(@RequestParam Map<String,String> data){
return removeService.remove(data);
}
}

View File

@ -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<String,String> update(@RequestParam Map<String,String> data){
return updateService.update(data);
}
}

View File

@ -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<Bot> {
}

View File

@ -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<Record> {
}

View File

@ -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<User> {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
User user = userMapper.selectOne(queryWrapper);
if(user == null){
throw new RuntimeException("用户不存在");
}
return new UserDetailsImpl(user);
}
}

View File

@ -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";
}
}

View File

@ -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<String, String> getInfo() {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = loginUser.getUser();
Map<String,String> 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;
}
}

View File

@ -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<String, String> 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<String,String> map = new HashMap<>();
map.put("error_message","success");
map.put("token",jwt);
return map;
}
}

View File

@ -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<String, String> register(String username, String password, String confirmedPassword) {
Map<String, String> 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<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq("username", username);
List<User> 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;
}
}

View File

@ -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<String, String> add(Map<String, String> 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<String, String> 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;
}
}

View File

@ -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<Bot> getList() {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authenticationToken.getPrincipal();
User user = loginUser.getUser();
QueryWrapper<Bot> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",user.getId());
return botMapper.selectList(queryWrapper);
}
}

View File

@ -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<String, String> remove(Map<String, String> 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<String, String> 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;
}
}

View File

@ -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<String, String> update(Map<String, String> 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<String, String> 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;
}
}

View File

@ -0,0 +1,61 @@
package com.kob.backend.service.impl.utils;
import com.kob.backend.pojo.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
//实现 UserDetails 接口:需要加上 lombok 的注解,来自动生成构造函数
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailsImpl implements UserDetails {
// 定义一个 pojo User 类对象
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
// 获取 pojo -> User 类对象 user 的用户密码
@Override
public String getPassword() {
return user.getPassword();
}
// 获取 pojo -> User 类对象 user 的用户名
@Override
public String getUsername() {
return user.getUsername();
}
// 用户账号是否没有过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 用户是否没有被锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 用户授权是否没有过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 用户是否被启用
@Override
public boolean isEnabled() {
return true;
}
}

View File

@ -0,0 +1,7 @@
// 匹配接口:配合 MatchingSystem 微服务进行使用
package com.kob.backend.service.pk;
public interface StartGameService {
// 参数是匹配的两位玩家的 id
String startGame(Integer aId, Integer bId);
}

View File

@ -0,0 +1,8 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//根据令牌返回用户信息
public interface InfoService {
public Map<String,String> getInfo();
}

View File

@ -0,0 +1,8 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//验证用户名密码验证成功后返回jwt token令牌
public interface LoginService {
public Map<String,String> getToken(String username, String password);
}

View File

@ -0,0 +1,9 @@
package com.kob.backend.service.user.account;
import java.util.Map;
//注册账号
public interface RegisterService {
// confirmedPassword 密码确认
public Map<String,String> register(String username, String password, String confirmedPassword);
}

View File

@ -0,0 +1,7 @@
package com.kob.backend.service.user.bot;
import java.util.Map;
public interface AddService {
public Map<String,String> add(Map<String,String> data);
}

View File

@ -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<Bot> getList(); // getList() 不需要传参数
}

View File

@ -0,0 +1,6 @@
package com.kob.backend.service.user.bot;
import java.util.Map;
public interface RemoveService {
public Map<String,String> remove(Map<String,String> data);
}

View File

@ -0,0 +1,7 @@
package com.kob.backend.service.user.bot;
import java.util.Map;
public interface UpdateService {
Map<String,String> update(Map<String,String> data);
}

View File

@ -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();
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="text-align: center">
<img src="/image/img.png">
</div>
</body>
</html>

View File

@ -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"));
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kob</groupId>
<artifactId>backendcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.kob.matchingsystem</groupId>
<artifactId>matchingsystem</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 简化代码,可以帮助写一些构造函数, set() , get() 函数等-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
<!-- 用于安全验证-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<String, String> 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<String, String> data) {
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
return matchingService.removePlayer(userId);
}
}

View File

@ -0,0 +1,10 @@
// 定义接口
package com.kob.matchingsystem.service;
public interface MatchingService {
// 给匹配池添加一名玩家
String addPlayer(Integer userId, Integer rating);
// 从匹配池删除一名玩家
String removePlayer(Integer userId);
}

View File

@ -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";
}
}

View File

@ -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<Player> 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<Player> 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<String, String> 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<Player> 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;
}
}
}
}

View File

@ -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; // 等待时间
}

View File

@ -0,0 +1 @@
server.port=3001

316
backendcloud/mvnw vendored Normal file
View File

@ -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 "$@"

188
backendcloud/mvnw.cmd vendored Normal file
View File

@ -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%

57
backendcloud/pom.xml Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kob</groupId>
<artifactId>backendcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backendcloud</name>
<description>backendcloud</description>
<modules>
<module>matchingsystem</module>
<module>backend</module>
</modules>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- springCloud 依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>