version-2.1[前后端实现简单匹配+Jwt验证+websocket依赖,使用]

pull/1/head
dyh 3 years ago
parent 6c1744c37e
commit 521ba31fa8

@ -16,6 +16,24 @@
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>

@ -9,6 +9,7 @@ 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;
@ -45,4 +46,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/websocket/**");//放行这一类链接
}
}

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

@ -0,0 +1,134 @@
package com.kob.backend.consumer;
import com.alibaba.fastjson.JSONObject;
import com.kob.backend.consumer.utils.Game;
import com.kob.backend.consumer.utils.JwtAuthentication;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
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 {
//ConcurrentHashMap 的优势在于兼顾性能和线程安全,一个线程进行写操作时,
//它会锁住一小部分,其他部分的读写不受影响,其他线程访问没上锁的地方不会被阻塞。
//(userId,WebSocketServer实例连接)
final private static ConcurrentHashMap<Integer,WebSocketServer> users = new ConcurrentHashMap<>();
//匹配池
final private static CopyOnWriteArraySet<User> matchpool = new CopyOnWriteArraySet<>();
//当前用户
private User user ;
//session维护链接
private Session session = null ;
//地图
private Game game ;
//加入数据库
private static UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper){
WebSocketServer.userMapper = userMapper;
}
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) throws IOException//token是前端url里的参数
{
// 建立连接
this.session = session ;//将session存起来--》一个用户一个session
System.out.println("connected!");
Integer userId = JwtAuthentication.getUserId(token);//获取id
this.user = userMapper.selectById(userId);//获取用户
if(this.user != null){//存在用户 加入连接
users.put(userId,this);//用户链接--》加入到集合中
} else {//不存在用户 断开连接
this.session.close();
}
System.out.println(user);
}
@OnClose
public void onClose() {
// 关闭链接
System.out.println("disconnexted!");
if(this.user != null){
users.remove(this.user.getId());
matchpool.remove(this.user);
}
}
//前端点击 开始匹配 触发
private void startMatching(){
System.out.println("start matching!");
matchpool.add(this.user);
while(matchpool.size() >= 2) {
Iterator<User> it = matchpool.iterator();//迭代器
User a = it.next(), b = it.next();
matchpool.remove(a);
matchpool.remove(b);
Game game =new Game(13,14,20);
game.createMap();
JSONObject respA = new JSONObject();//返回给a
respA.put("event", "start-matching");
respA.put("opponent_username", b.getUsername());
respA.put("opponent_photo", b.getPhoto());
respA.put("gamemap", game.getG());
users.get(a.getId()).sendMessage(respA.toJSONString());//获取a对应的连接向前端传递信息String
JSONObject respB = new JSONObject();//返回给b
respB.put("event", "start-matching");
respB.put("opponent_username", a.getUsername());
respB.put("opponent_photo", a.getPhoto());
respB.put("gamemap", game.getG());
users.get(b.getId()).sendMessage(respB.toJSONString());//获取b对应的连接向前端传递信息String
}
}
//前端点击 取消 触发
private void stopMatching(){
System.out.println("stop matching!");
matchpool.remove(this.user);
}
@OnMessage
public void onMessage(String message, Session session) {
// 接收前端信息
System.out.println("receive message!");
JSONObject data = JSONObject.parseObject(message);//解析message
String event = data.getString("event");//类似map
if("start-matching".equals(event)){
startMatching();
} else if("stop-matching".equals(event)){
stopMatching();
}
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
//给前端发送信息
public void sendMessage(String message){
//由于是异步的,加上同步锁(只有一个线程能执行该块)
synchronized (this.session){
try{
//后端向当前链接发送信息
this.session.getBasicRemote().sendText(message);
}catch(IOException e){
e.printStackTrace();
}
}
}
}

@ -0,0 +1,98 @@
package com.kob.backend.consumer.utils;
import java.util.Random;
public class Game {
final private Integer rows;
final private Integer cols;
final private Integer inner_walls_count;
final private int[][] g;
final private static int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
//构造map规模
public Game(Integer rows, Integer cols, Integer inner_walls_count) {
this.rows = rows;
this.cols = cols;
this.inner_walls_count = inner_walls_count;
this.g = new int[rows][cols];
}
//货物gamemap
public int[][] getG()
{
return g;
}
//地图是否连通
private boolean check_connectivity(int sx, int sy, int tx, int ty)
{
if (sx == tx && sy == ty) return true;
g[sx][sy] = 1;
for (int i = 0; i < 4; i ++ ) {
int x = sx + dx[i], y = sy + dy[i];
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;
}
private boolean draw() // 画地图
{
for (int i = 0; i < this.rows; i ++ )
{
for (int j = 0; j < this.cols; j ++ )
{
g[i][j] = 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();
for (int i = 0; i < this.inner_walls_count / 2; i ++ )
{
for (int j = 0; j < 1000; j ++ )
{
int r = random.nextInt(this.rows);
int c = random.nextInt(this.cols);
if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1)
continue;
if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2)
continue;
g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1;
break;
}
}
return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);
}
public void createMap()
{
for (int i = 0; i < 1000; i ++ )
{
if (draw()) break;
}
}
}

@ -0,0 +1,20 @@
package com.kob.backend.consumer.utils;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
public class JwtAuthentication {
//根据token返回userId
public static Integer getUserId(String token)//不用实例访问
{
int userid = -1;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = Integer.parseInt(claims.getSubject());
} catch (Exception e) {
throw new RuntimeException(e);
}
return userid;
}
}

@ -6,11 +6,13 @@ import { Wall } from "./Wall";
export class GameMap extends AcGameObject
{
//画布+父元素
constructor(ctx,parent)
constructor(ctx,parent,store)
{
super();
this.ctx = ctx;//画布
this.parent = parent;//父元素
this.store = store;//全局变量(有地图)
this.L = 0;//小正方形的边长
this.rows = 13;
@ -18,7 +20,7 @@ export class GameMap extends AcGameObject
this.walls=[];
this.inner_walls_count = 5;
this.inner_walls_count = 20;
this.snakes = [
new Snake({id:0,color:"#4876ED",r:this.rows-2,c:1},this),
@ -28,62 +30,10 @@ export class GameMap extends AcGameObject
}
check_connectivity(g,sx,sy,tx,ty)
{
if(sx==tx&&sy==ty)return true;
g[sx][sy]=true;
let dx=[-1,0,1,0],dy=[0,1,0,-1];
for(let i=0;i<4;i++)
{
let x = sx + dx[i],y = sy + dy[i];
if(g[x][y]==false && this.check_connectivity(g,x,y,tx,ty))return true;
}
return false;
}
//创建墙
create_wall(){
//初始化【没有墙 false】
const g=[]
for(let r=0;r<this.rows;r++)
{
g[r]=[];
for(let c=0;c<this.cols;c++)
{
g[r][c]=false;
}
}
// g数组与画布方向相反
//左右加墙
for(let r=0;r<this.rows;r++){
g[r][0]=g[r][this.cols-1]=true;
}
// 上下加墙
for(let c=0;c<this.rows;c++){
g[0][c]=g[this.rows-1][c]=true;
}
//创建障碍物
for(let i=0;i<this.inner_walls_count/2;i++)
{
for(let j=0;j<=10000;j++)
{
let r = parseInt(Math.random()*this.rows);
let c = parseInt(Math.random()*this.cols);
// console.log(r,c);
//对称
if(g[r][c]||g[this.rows-1-r][this.cols-1-c])continue;//有障碍物
if(r==this.rows-2 && c==1||r==1&&c==this.cols-2)continue;//左下角右上角的蛇可能会被盖掉
g[r][c]=g[this.rows-1-r][this.cols-1-c]=true;//没有
break; //跳出
}
}
//是否连通
const copy_g = JSON.parse(JSON.stringify(g));
if(!this.check_connectivity(copy_g,this.rows-2,1,1,this.cols-2)) return false;//不连通
const g = this.store.state.pk.gamemap ;
console.log(g)
for(let r=0;r<this.rows;r++)
{
for(let c=0;c<this.cols;c++)
@ -93,19 +43,11 @@ export class GameMap extends AcGameObject
}
}
}
return true;//连通
}
start(){
this.create_wall();
this.add_listening_events();//聚焦&keydown事件
//创建一次墙(除非刷新) 直到创建成功
for(let i=0;i<=100000;i++)
{
if(this.create_wall())break;
}
}

@ -8,15 +8,18 @@
// {}
import { GameMap } from "@/assets/scripts/GameMap.js";
import { ref,onMounted } from "vue";
import { useStore } from "vuex"
export default {
setup(){
const store = useStore();
//DOM
let parent = ref(null);
let canvas = ref(null);
onMounted(()=>{
new GameMap(canvas.value.getContext('2d'),parent.value);
new GameMap(canvas.value.getContext('2d'),parent.value,store);
// console.log(parent.value);//DOM
// console.log(canvas.value);//DOM
})

@ -0,0 +1,92 @@
<template>
<div class="matchground">
<div class="row">
<!-- 自己 -->
<div class="col-6">
<div class="user-photo">
<img :src="$store.state.user.photo" alt="">
</div>
<div class="user-username">
{{$store.state.user.username}}
</div>
</div>
<!-- 对手 -->
<div class="col-6">
<div class="user-photo">
<img :src="$store.state.pk.opponent_photo" alt="">
</div>
<div class="user-username">
{{$store.state.pk.opponent_username}}
</div>
</div>
<div class="col-12" style="text-align : center; padding-top : 12vh;">
<button type="button" class="btn btn-warning btn-lg" @click="click_match_btn">{{match_btn_info}}</button>
</div>
</div>
</div>
</template>
<script>
import { ref } from "vue"
import { useStore } from "vuex"
export default {
setup(){
const store = useStore();
let match_btn_info = ref("开始匹配")
const click_match_btn = ( )=>{
if(match_btn_info.value === "开始匹配"){
match_btn_info.value = "取消";
//
store.state.pk.socket.send(JSON.stringify({
event:"start-matching",
}));
} else if(match_btn_info.value === "取消"){
match_btn_info.value = "开始匹配";
store.state.pk.socket.send(JSON.stringify({
event:"stop-matching",
}));
}
}
return {
match_btn_info,
click_match_btn,
}
}
}
</script>
<style scoped>
div.matchground {
width: 60vw;
height: 70vh;
background-color:rgba(50 ,50 ,50 ,0.5);
margin: 40px auto;
}
div.user-photo {
text-align: center;
padding-top: 10vh;
}
div.user-photo > img{
border-radius: 50%;
width: 20vh;
}
div.user-username {
text-align: center;
font-size: 20px;
font-weight: 600;
color: white;
margin-top: 2vh;
}
</style>

@ -2,7 +2,6 @@
<div class="playground">
<GameMap></GameMap>
</div>
</template>
<script>
@ -11,7 +10,7 @@ import GameMap from "@/components/GameMap.vue";
export default {
components:{
GameMap,
}
},
}
</script>
@ -23,5 +22,4 @@ div.playground {
/* background-color: lightblue; */
margin: 40px auto;
}
</style>

@ -0,0 +1,29 @@
<template>
<div class="talk">
<input v-model="username" type="text" class="left" placeholder="请输入用户名">
<button type="button" class="btn btn-warning" @click="sendMessage"></button>
<div>{{$store.state.pk.message}}</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
div.talk {
width: 30vw;
height: 10vh;
background-color: lightblue;
/* display: flex; */
margin-bottom: 10px;
}
.left {
display: inline;
width: 370px;
height: 28px;
}
</style>

@ -1,7 +1,7 @@
//主页面
import { createStore } from 'vuex'
import ModuleUser from './user';
import ModulePk from './pk';
export default createStore({
state: {
},
@ -13,5 +13,6 @@ export default createStore({
},
modules: {
user:ModuleUser,
pk:ModulePk,
}
})

@ -0,0 +1,32 @@
// import $ from "jquery"
export default {
state: {
status:"matching",//matching匹配页面 playing对战页面
socket:null,
opponent_username:"",
opponent_photo:"",
gamemap:null,
},
getters: {
},
mutations: {
updateSocket(state,socket){
state.socket = socket;
},
updateOpponent(state,opponent){
state.opponent_username = opponent.username;
state.opponent_photo = opponent.photo;
},
updateStatus(state,status){
state.status = status;
},
updateGamemap(state,gamemap){
state.gamemap = gamemap ;
}
},
actions: {
},
modules: {
}
}

@ -1,13 +1,69 @@
<template>
<PlayGround></PlayGround>
<PlayGround v-if="$store.state.pk.status === 'playing'"></PlayGround>
<MatchGround v-if="$store.state.pk.status === 'matching'"></MatchGround>
</template>
<script>
import PlayGround from "@/components/PlayGround"
import MatchGround from "@/components/MatchGround"
import { onMounted, onUnmounted } from "vue"
import { useStore } from 'vuex'
export default {
components:{
PlayGround,
MatchGround,
},
setup(){
const store = useStore();
const socketUrl = `ws://127.0.0.1:3000/websocket/${store.state.user.token}/`;
let socket = null;
onMounted(()=>{
socket = new WebSocket(socketUrl);//Js
//
store.commit("updateOpponent",{
username:"我的对手",
photo:"https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png",
});
//
socket.onopen = () =>{
console.log("connected!")
store.commit("updateSocket",socket)
}
//
socket.onmessage = (msg) => {
const data = JSON.parse(msg.data);//JSONdata-->JSON
if(data.event === "start-matching") {//
store.commit("updateOpponent",{
username:data.opponent_username,
photo:data.opponent_photo,
})
setTimeout(() => {
store.commit("updateStatus","playing");
}, 2000);
store.commit("updateGamemap",data.gamemap);
}
}
socket.onclose = () => {
console.log("disconnected!")
}
})
onUnmounted(()=>{
socket.close();
})
}
}
</script>

@ -877,4 +877,56 @@ body {
background-image: url("@/assets/images/background.png");
background-size: cover;
}
</style>
</style>
// false
const g=[]
for(let r=0;r<this.rows;r++)
{
g[r]=[];
for(let c=0;c<this.cols;c++)
{
g[r][c]=false;
}
}
// g
//
for(let r=0;r<this.rows;r++){
g[r][0]=g[r][this.cols-1]=true;
}
//
for(let c=0;c<this.rows;c++){
g[0][c]=g[this.rows-1][c]=true;
}
//
for(let i=0;i<this.inner_walls_count/2;i++)
{
for(let j=0;j<=10000;j++)
{
let r = parseInt(Math.random()*this.rows);
let c = parseInt(Math.random()*this.cols);
// console.log(r,c);
//
if(g[r][c]||g[this.rows-1-r][this.cols-1-c])continue;//
if(r==this.rows-2 && c==1||r==1&&c==this.cols-2)continue;//
g[r][c]=g[this.rows-1-r][this.cols-1-c]=true;//
break; //
}
}
//
const copy_g = JSON.parse(JSON.stringify(g));
if(!this.check_connectivity(copy_g,this.rows-2,1,1,this.cols-2)) return false;//
Loading…
Cancel
Save