创建 matching 匹配页面及完成到 playing 页面的跳转
This commit is contained in:
parent
51fda496d1
commit
e6e6bc15e9
|
@ -92,6 +92,22 @@
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
@ -36,6 +37,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
return super.authenticationManagerBean();
|
return super.authenticationManagerBean();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
|
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
|
||||||
用于配置公开链接
|
用于配置公开链接
|
||||||
|
@ -52,4 +54,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用于配置 websocket:放行所有的 websocket/ 链接
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,8 +43,12 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
token = token.substring(7);
|
token = token.substring(7);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 核心验证逻辑
|
||||||
String userid;
|
String userid;
|
||||||
try {
|
try {
|
||||||
|
// 将 token 解析,如果能成功解析出 userid 表示合法,否则表示不合法
|
||||||
Claims claims = JwtUtil.parseJWT(token);
|
Claims claims = JwtUtil.parseJWT(token);
|
||||||
userid = claims.getSubject();
|
userid = claims.getSubject();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -53,6 +57,7 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
User user = userMapper.selectById(Integer.parseInt(userid));
|
User user = userMapper.selectById(Integer.parseInt(userid));
|
||||||
|
|
||||||
|
// User user = userMapper.selectById(new JwtAuthenticationUtil().getUserId(token));
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new RuntimeException("用户名未登录");
|
throw new RuntimeException("用户名未登录");
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
package com.kob.backend.consumer;
|
||||||
|
|
||||||
|
// WebSocket用于前后端通信
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.kob.backend.consumer.utils.JwtAuthenticationUtil;
|
||||||
|
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 {
|
||||||
|
// 后端向前端发送信息,首先需要创建一个 session
|
||||||
|
private Session session = null;
|
||||||
|
|
||||||
|
// 用户信息:定义一个成员变量
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
/*
|
||||||
|
存储所有链接:对所有 websocket 可见的全局变量,存储为 static 静态变量
|
||||||
|
因为每个 websocket 实例都在一个独立的线程里,所以该公共变量应该是线程安全的
|
||||||
|
使用线程安全的 Hash 表 ConcurrentHashMap<>(),
|
||||||
|
将 userId 映射到 webSocketServer
|
||||||
|
*/
|
||||||
|
private static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 使用 CopyOnWriteArraySet 创建一个线程安全的匹配池 matchPool
|
||||||
|
private static final CopyOnWriteArraySet<User> matchPool = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
|
||||||
|
//在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
|
||||||
|
private static UserMapper userMapper;
|
||||||
|
|
||||||
|
// 注入方法
|
||||||
|
@Autowired
|
||||||
|
public void setUserMapper(UserMapper userMapper) {
|
||||||
|
// 静态变量 userMapper 访问需要使用类名 WebSocketServer 访问
|
||||||
|
WebSocketServer.userMapper = userMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// @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());
|
||||||
|
// 将匹配池数组删掉
|
||||||
|
matchPool.remove(this.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始匹配的逻辑部分
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 将 a 配对成功的消息传回客户端
|
||||||
|
JSONObject respA = new JSONObject();
|
||||||
|
respA.put("event","start-matching");
|
||||||
|
respA.put("opponent_username",b.getUsername());
|
||||||
|
respA.put("opponent_photo",b.getPhoto());
|
||||||
|
// 获取 a 的链接,并通过 sendMessage 将消息传给前端
|
||||||
|
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());
|
||||||
|
users.get(b.getId()).sendMessage(respB.toJSONString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消匹配的逻辑部分
|
||||||
|
private void stopMatching() {
|
||||||
|
System.out.println("stop matching");
|
||||||
|
matchPool.remove(this.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ public class RegisterServiceImpl implements RegisterService {
|
||||||
// 对密码进行加密
|
// 对密码进行加密
|
||||||
String encodedPassword = new BCryptPasswordEncoder().encode(password);
|
String encodedPassword = new BCryptPasswordEncoder().encode(password);
|
||||||
// 默认头像
|
// 默认头像
|
||||||
String photo = "https://cdn.acwing.com/media/user/profile/photo/253652_lg_e3d8435b66.jpg";
|
String photo = "https://typoraflykhan.oss-cn-beijing.aliyuncs.com/202302251824054.png";
|
||||||
// id 是数据库自增,这里生成新用户只需要将 id 参数写为 null 即可
|
// id 是数据库自增,这里生成新用户只需要将 id 参数写为 null 即可
|
||||||
User user = new User(null, username, encodedPassword, photo);
|
User user = new User(null, username, encodedPassword, photo);
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
create table record
|
||||||
|
(
|
||||||
|
id int auto_increment
|
||||||
|
primary key,
|
||||||
|
a_id int null,
|
||||||
|
a_sx int null,
|
||||||
|
a_sy int null,
|
||||||
|
b_id int null,
|
||||||
|
b_sx int null,
|
||||||
|
b_sy int null,
|
||||||
|
a_steps varchar(1000) null,
|
||||||
|
b_steps varchar(1000) null,
|
||||||
|
map varchar(1000) null,
|
||||||
|
loser varchar(10) null,
|
||||||
|
createtime datetime null
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
// 定义匹配页面
|
||||||
|
<template>
|
||||||
|
<div class="matchground">
|
||||||
|
<div class="row">
|
||||||
|
<!-- 左边 6 份,右边 6 份 -->
|
||||||
|
<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="opponent-photo">
|
||||||
|
<img :src="$store.state.pk.opponent_photo" alt="">
|
||||||
|
</div>
|
||||||
|
<div class="opponent-username">{{ $store.state.pk.opponent_username }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12" style="text-align:center">
|
||||||
|
<button type="button" class="btn btn-warning btn-lg" v-on:click="click_match_btn">{{ match_btn_info
|
||||||
|
}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup: () => {
|
||||||
|
const store = useStore();
|
||||||
|
let match_btn_info = ref("开始匹配")
|
||||||
|
|
||||||
|
const click_match_btn = () => {
|
||||||
|
if (match_btn_info.value === "开始匹配") {
|
||||||
|
match_btn_info.value = "取消";
|
||||||
|
// 向后端发请求,使用 JSON.stringify() 将一个 JSON 封装成一字符串
|
||||||
|
store.state.pk.socket.send(JSON.stringify({
|
||||||
|
// 传一个 event 域
|
||||||
|
event:"start-matching",
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
match_btn_info.value = "开始匹配";
|
||||||
|
store.state.pk.socket.send(JSON.stringify({
|
||||||
|
event:"stop-matching",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
match_btn_info,
|
||||||
|
click_match_btn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.matchground {
|
||||||
|
/* 60% 浏览器宽度, 70% 浏览器高度 */
|
||||||
|
width: 60vw;
|
||||||
|
height: 70vh;
|
||||||
|
/* 背景色: rgba 加上透明通道 */
|
||||||
|
background: rgba(70, 70, 70, 0.5);
|
||||||
|
/* 距上边距40px,左右居中 */
|
||||||
|
margin: 40px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-photo {
|
||||||
|
/* 居中 */
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 10vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-photo>img {
|
||||||
|
/* 头像圆形 */
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-username {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
/* 字体加粗 */
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
margin-top: 3vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-photo {
|
||||||
|
/* 居中 */
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 10vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-photo>img {
|
||||||
|
/* 头像圆形 */
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-username {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
/* 字体加粗 */
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
margin-top: 3vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 15vh;
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -99,9 +99,9 @@ export default {
|
||||||
let route_name = computed(() => route.name);
|
let route_name = computed(() => route.name);
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
console.log("退出前:" + store.state.user.token);
|
// console.log("退出前:" + store.state.user.token);
|
||||||
store.dispatch("logout");
|
store.dispatch("logout");
|
||||||
console.log("退出后:" + store.state.user.token);
|
// console.log("退出后:" + store.state.user.token);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
// 全局挂载 store , 后面写的 vue 页面都可以通过 $store 来调用
|
||||||
import store from './store'
|
import store from './store'
|
||||||
|
|
||||||
createApp(App).use(store).use(router).mount('#app')
|
createApp(App).use(store).use(router).mount('#app')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createStore } from "vuex";
|
import { createStore } from "vuex";
|
||||||
import ModuleUser from "./user";
|
import ModuleUser from "./user";
|
||||||
import ModuleBot from './bot';
|
import ModuleBot from './bot';
|
||||||
|
import ModulePk from './pk';
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
state: {},
|
state: {},
|
||||||
|
@ -10,5 +11,6 @@ export default createStore({
|
||||||
modules: {
|
modules: {
|
||||||
user: ModuleUser,
|
user: ModuleUser,
|
||||||
bot: ModuleBot,
|
bot: ModuleBot,
|
||||||
|
pk: ModulePk,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
export default {
|
||||||
|
state: {
|
||||||
|
// 当前状态:用于判断时匹配中还是已经匹配完成: matching 表示匹配中(匹配界面), playing 表示匹配完成(对战界面)
|
||||||
|
status: "matching",
|
||||||
|
// socket 信息
|
||||||
|
socket: null,
|
||||||
|
// 对手信息
|
||||||
|
opponent_username: "",
|
||||||
|
opponent_photo: "",
|
||||||
|
},
|
||||||
|
getters: {},
|
||||||
|
mutations: {
|
||||||
|
// 更新 socket 信息
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
module: {},
|
||||||
|
};
|
|
@ -1,14 +1,84 @@
|
||||||
<template>
|
<template>
|
||||||
<PlayGround>对战</PlayGround>
|
<PlayGround v-if="$store.state.pk.status === 'playing'" />
|
||||||
|
<MatchGround v-else-if="$store.state.pk.status === 'matching'" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlayGround from "../../components/PlayGround.vue";
|
import PlayGround from "../../components/PlayGround.vue";
|
||||||
|
import MatchGround from "../../components/MatchGround.vue"
|
||||||
|
// onMounted 用于组件被挂载时, onUnmounted 用于组件被解除挂载时
|
||||||
|
import { onMounted, onUnmounted } from "vue";
|
||||||
|
// 取出全局变量
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PlayGround,
|
PlayGround,
|
||||||
|
MatchGround
|
||||||
},
|
},
|
||||||
|
// 创建前后端连接:当当前页面组件被加载时,建立连接,故使用 onMounted 函数
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
// 定义后端连接的 url, 使用 ws 协议
|
||||||
|
const socketUrl = `ws://127.0.0.1:3000/websocket/${store.state.user.token}/`;
|
||||||
|
|
||||||
|
let socket = null;
|
||||||
|
// 当前组件被挂载的时候,需要创建后端连接,并且需要将连接信息存储到 store 全局变量里
|
||||||
|
onMounted(() => {
|
||||||
|
// 定义默认匹配页面对手信息(匹配等待过程中使用)
|
||||||
|
store.commit("updateOpponent", {
|
||||||
|
username: "等待对手",
|
||||||
|
photo: "https://typoraflykhan.oss-cn-beijing.aliyuncs.com/202302251825860.png",
|
||||||
|
})
|
||||||
|
|
||||||
|
// 新建 WebSocket
|
||||||
|
socket = new WebSocket(socketUrl);
|
||||||
|
store.commit("updateSocket", socket);
|
||||||
|
|
||||||
|
// 调用 socket 函数: onopen 对应后端的 WebSocketServer 的 onOpen 方法
|
||||||
|
socket.onopen = () => {
|
||||||
|
console.log("frontend: connected!");
|
||||||
|
// 将 socket 存到全局变量里面
|
||||||
|
store.commit("updateSocket", socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = (msg) => {
|
||||||
|
const data = JSON.parse(msg.data);
|
||||||
|
// console.log(data);
|
||||||
|
// data.event 在后端定义
|
||||||
|
if (data.event === "start-matching") {
|
||||||
|
// 匹配成功,更新对手信息
|
||||||
|
store.commit("updateOpponent", {
|
||||||
|
username: data.opponent_username,
|
||||||
|
photo: data.opponent_photo,
|
||||||
|
})
|
||||||
|
// 匹配成功,延迟两秒后:更改匹配状态 matching -> playing
|
||||||
|
setTimeout(() => {
|
||||||
|
store.commit("updateStatus", "playing");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log("frontend: disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 离开 pk 页面时,调用 onUnmounted函数
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 断开连接
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
// 离开 pk 页面时,将匹配状态改回 matching
|
||||||
|
store.commit("updateStatus", "matching");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue