SpringBoot学习笔记-实现微服务:匹配系统(上)

笔记内容转载自 AcWing 的 SpringBoot 框架课讲义,课程链接:AcWing SpringBoot 框架课。

CONTENTS

  • 1. 配置WebSocket
  • 2. 前后端WebSocket通信
    • 2.1 WS通信的建立
    • 2.2 加入JWT验证
  • 3. 前后端匹配业务
    • 3.1 实现前端页面
    • 3.2 实现前后端交互逻辑
    • 3.3 同步游戏地图

我们的游戏之后是两名玩家对战,因此需要实现联机功能,在这之前还需要实现一个匹配系统,能够匹配分数相近的玩家进行对战。

想要进行匹配就至少要有两个客户端,当两个客户端都向服务器发送匹配请求后并不会马上得到返回结果,一般会等待一段时间,这个时间是未知的,因此这个匹配是一个异步的过程,对于这种异步的过程或者是计算量比较大的过程我们都会用一个额外的服务来操作。

那么这个额外的用于匹配的服务可以称为 Matching System,这是另外一个程序(进程),当后端服务器接收到前端的请求后就会将请求发送给 Matching System,这个匹配系统维护了一堆用户的集合,它会不断地去匹配分数最接近的用户,当匹配成功一组用户后就会将结果返回给后端服务器,再由后端将匹配结果立即返回给对应的前端。这种服务就被称为微服务,可以用 Spring Cloud 实现。

用以前的 HTTP 请求很难达到这种效果,之前我们是在客户端向后端发送请求,且后端在短时间内就会返回结果,HTTP 请求只能满足这种一问一答式的服务。而我们现在需要实现的效果是客户端发送请求后不知道经过多长时间后端才会返回结果,对于这种情况需要使用 WebSocket 协议(WS),该协议不仅支持客户端向服务器发送请求,也支持服务器向客户端发送请求。

在前端向服务器发送请求后,服务器会维护好一个 WS 链接,这个链接其实就是一个 WebSocketServer 类的实例,所有和这个链接相关的信息都会存到这个类中。

1. 配置WebSocket

我们之前每次刷新网页就会随机生成游戏地图,该过程是在浏览器本地执行的,当我们要实现匹配功能时,地图就不能由两名玩家各自的客户端生成,否则就基本不可能完全一样了。

当匹配成功后应该由服务器端创建一个 Game 任务,将游戏放到该任务下执行,统一生成地图,且判断移动或者输赢等逻辑之后也应该移到后端来执行。

生成好地图后服务器就将地图传给两名玩家的前端,然后等待玩家的键盘输入或者是 Bot 代码的输入,Bot 代码的输入也属于一个微服务。

首先我们先在 pom.xml 文件中添加以下依赖:

  • spring-boot-starter-websocket
  • fastjson

接着在 config 包下创建 WebSocketConfig 配置类:

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 {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

然后我们创建一个 consumer 包,在其中创建 WebSocketServer 类:

package com.kob.backend.consumer;import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;@Component
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {@OnOpenpublic void onOpen(Session session, @PathParam("token") String token) {// 建立链接}@OnClosepublic void onClose() {// 关闭链接}@OnMessagepublic void onMessage(String message, Session session) {// 从Client接收消息}@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}
}

之前我们配置的 Spring Security 设置了屏蔽除了授权之外的其他所有链接,因此我们需要在 SecurityConfig 类中放行一下 WebSocket 的链接:

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;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {  // AuthenticationManager用于处理身份验证return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {  // 配置HttpSecurityhttp.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/user/account/login/", "/user/account/register/").permitAll()  // 需要公开的链接在这边写即可.antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/websocket/**");}
}

如果是使用新版的配置而不是使用 WebSecurityConfigurerAdapter 可以按以下方式配置:

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.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityCustomizer;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/user/account/login/", "/user/account/register/").permitAll().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic WebSecurityCustomizer webSecurityCustomizer(){return (web) -> web.ignoring().antMatchers("/websocket/**");}
}

2. 前后端WebSocket通信

2.1 WS通信的建立

WebSocket 不属于单例模式(同一个时间每个类只能有一个实例,我们每建一个 WS 链接都会新创建一个实例),不是标准的 Spring 中的组件,因此在注入 Mapper 时不能用 @Autowired 直接注入,一般是将 @Autowired 写在一个 set() 方法上,Spring 会根据方法的参数类型从 IoC 容器中找到该类型的 Bean 对象注入到方法的行参中,并且自动反射调用该方法。

我们先假设前端传过来的是用户 ID 而不是 JWT 令牌:

package com.kob.backend.consumer;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.concurrent.ConcurrentHashMap;@Component
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {// ConcurrentHashMap是一个线程安全的哈希表,用于将用户ID映射到WS实例private static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();private User user;private Session session = null;private static UserMapper userMapper;@Autowiredpublic void setUserMapper(UserMapper userMapper) {WebSocketServer.userMapper = userMapper;}@OnOpenpublic void onOpen(Session session, @PathParam("token") String token) {this.session = session;System.out.println("Connected!");Integer userId = Integer.parseInt(token);this.user = userMapper.selectById(userId);users.put(userId, this);}@OnClosepublic void onClose() {System.out.println("Disconnected!");if (this.user != null) {users.remove(this.user.getId());}}@OnMessagepublic void onMessage(String message, Session session) {System.out.println("Receive message!");}@OnErrorpublic 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();}}}
}

然后我们先在前端的 PKIndexView 组件中调试,当组件被挂载完成后发出请求建立 WS 链接,当被卸载后关闭 WS 链接:

<template><PlayGround />
</template><script>
import PlayGround from "@/components/PlayGround.vue";
import { onMounted, onUnmounted } from "vue";
import { useStore } from "vuex";export default {components: {PlayGround,},setup() {const store = useStore();let socket = null;let socket_url = `ws://localhost:3000/websocket/${store.state.user.id}/`;onMounted(() => {socket = new WebSocket(socket_url);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);  // Spring传过来的数据是放在消息的data中console.log(data);};socket.onclose = () => {  // 关闭链接后会执行console.log("Disconnected!");};});onUnmounted(() => {socket.close();  // 如果不断开链接每次切换页面都会创建新链接,就会导致有很多冗余链接});},
};
</script><style scoped></style>

现在我们在对战页面每次刷新后都可以在浏览器控制台或后端控制台中看到 WS 的输出信息。

接下来我们要将 WebSocket 存到前端的 store 中,在 store 目录下创建 pk.js 用来存储和对战页面相关的全局变量:

export default {state: {status: "matching",  // 当前状态,matching表示正在匹配,playing表示正在对战socket: null,  // 前端和后端建立的链接opponent_username: "",  // 对手的用户名opponent_photo: "",  // 对手的头像},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;},},actions: {},modules: {},
};

同时要在 store/index.js 中引入进来:

import { createStore } from "vuex";
import ModuleUser from "./user";
import ModulePk from "./pk";export default createStore({state: {},getters: {},mutations: {},actions: {},modules: {user: ModuleUser,pk: ModulePk,},
});

2.2 加入JWT验证

现在我们直接使用用户的 ID 建立 WS 链接,这是不安全的,因为前端可以自行修改这个 ID,因此就需要加入 JWT 验证。

WebSocket 中没有 Session 的概念,因此我们在验证的时候前端就不用将信息放到表头里了,直接放到链接中就行:

...<script>
...export default {...setup() {...let socket_url = `ws://localhost:3000/websocket/${store.state.user.jwt_token}/`;...},
};
</script>...

验证的逻辑可以参考之前的 JwtAuthenticationTokenFilter,我们可以把这个验证的模块单独写到一个文件中,在 consumer 包下创建 utils 包,然后创建一个 JwtAuthentication 类:

package com.kob.backend.consumer.utils;import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;public class JwtAuthentication {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;}
}

然后就可以在 WebSocketServer 中解析 JWT 令牌:

package com.kob.backend.consumer;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.concurrent.ConcurrentHashMap;@Component
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {...@OnOpenpublic void onOpen(Session session, @PathParam("token") String token) throws IOException {this.session = session;System.out.println("Connected!");Integer userId = JwtAuthentication.getUserId(token);this.user = userMapper.selectById(userId);if (user != null) {users.put(userId, this);} else {this.session.close();}}...
}

3. 前后端匹配业务

3.1 实现前端页面

我们需要实现一个前端的匹配页面,并能够切换匹配和对战页面,可以根据之前在 store 中存储的 status 状态来动态展示页面。首先在 components 目录下创建 MatchGround.vue 组件,其中需要展示玩家自己的头像和用户名以及对手的头像和用户名,当点击开始匹配按钮时向 WS 链接发送开始匹配的消息,点击取消按钮时发送取消匹配的消息:

<template><div class="matchground"><div class="row"><div class="col-md-6" style="text-align: center;"><div class="photo"><img class="img-fluid" :src="$store.state.user.photo"></div><div class="username">{{ $store.state.user.username }}</div></div><div class="col-md-6" style="text-align: center;"><div class="photo"><img class="img-fluid" :src="$store.state.pk.opponent_photo"></div><div class="username">{{ $store.state.pk.opponent_username }}</div></div><div class="col-md-12 text-center" style="margin-top: 14vh;"><button @click="click_match_btn" type="button" class="btn btn-info btn-lg">{{ 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({  // 将json封装成字符串发送给后端,后端会在onMessage()中接到请求event: "start_match",  // 表示开始匹配}));} else {match_btn_info.value = "开始匹配";store.state.pk.socket.send(JSON.stringify({event: "stop_match",  // 表示停止匹配}));}};return {match_btn_info,click_match_btn,};},
};
</script><style scoped>
div.matchground {width: 60vw;height: 70vh;margin: 40px auto;border-radius: 10px;background-color: rgba(50, 50, 50, 0.5);
}img {width: 35%;border-radius: 50%;margin: 14vh 0 1vh 0;
}.username {font-size: 24px;font-weight: bold;color: white;
}
</style>

3.2 实现前后端交互逻辑

当用户点击开始匹配按钮后,前端要向服务器发出一个请求,后端接收到请求后应该将该用户放入匹配池中,由于目前还没有实现微服务,因此我们先在 WebSocketServer 后端用一个 Set 维护正在匹配的玩家,当匹配池中满两名玩家就将其匹配在一起,然后将匹配结果返回给两名玩家的前端:

package com.kob.backend.consumer;import com.alibaba.fastjson2.JSONObject;
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是一个线程安全的哈希表,用于将用户ID映射到WS实例private static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();// CopyOnWriteArraySet也是线程安全的private static final CopyOnWriteArraySet<User> matchPool = new CopyOnWriteArraySet<>();  // 匹配池private User user;private Session session = null;private static UserMapper userMapper;@Autowiredpublic void setUserMapper(UserMapper userMapper) {WebSocketServer.userMapper = userMapper;}@OnOpenpublic void onOpen(Session session, @PathParam("token") String token) throws IOException {this.session = session;System.out.println("Connected!");Integer userId = JwtAuthentication.getUserId(token);this.user = userMapper.selectById(userId);if (user != null) {users.put(userId, this);} else {this.session.close();}}@OnClosepublic void onClose() {System.out.println("Disconnected!");if (this.user != null) {users.remove(this.user.getId());matchPool.remove(this.user);}}@OnMessagepublic void onMessage(String message, Session session) {  // 一般会把onMessage()当作路由System.out.println("Receive message!");JSONObject data = JSONObject.parseObject(message);String event = data.getString("event");  // 取出event的内容if ("start_match".equals(event)) {this.startMatching();} else if ("stop_match".equals(event)) {this.stopMatching();}}@OnErrorpublic 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();}}}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);JSONObject respA = new JSONObject();  // 发送给A的信息respA.put("event", "match_success");respA.put("opponent_username", b.getUsername());respA.put("opponent_photo", b.getPhoto());users.get(a.getId()).sendMessage(respA.toJSONString());  // A不一定是当前链接,因此要在users中获取JSONObject respB = new JSONObject();  // 发送给B的信息respB.put("event", "match_success");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);}
}

接着修改一下 PKIndexView,当接收到 WS 链接从后端发送过来的匹配成功消息后需要更新对手的头像和用户名:

...<script>
...export default {...setup() {...onMounted(() => {...socket.onmessage = (msg) => {  // 接收到后端消息时会执行const data = JSON.parse(msg.data);  // Spring传过来的数据是放在消息的data中console.log(data);if (data.event === "match_success") {  // 匹配成功store.commit("updateOpponent", {username: data.opponent_username,photo: data.opponent_photo,});setTimeout(() => {  // 3秒后再进入游戏地图界面store.commit("updateStatus", "playing");}, 3000);}};socket.onclose = () => {  // 关闭链接后会执行console.log("Disconnected!");store.commit("updateStatus", "matching");  // 进入游戏地图后玩家点击其他页面应该是默认退出游戏};...});...},
};
</script>...

测试的时候需要用两个浏览器,如果没有两个浏览器可以在 Edge 浏览器的右上角设置菜单中新建 InPrivate 窗口,这样就可以自己登录两个不同的账号进行匹配测试。

3.3 同步游戏地图

现在匹配成功后两名玩家进入游戏时看到的地图是不一样的,因为目前地图还都是在每名玩家本地的浏览器生成的,那么我们就需要将生成地图的逻辑放到服务器端。

先在后端的 consumer.utils 包下创建 Game 类,用来管理整个游戏流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/160147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

年底了,我劝大家真别轻易离职...

年底了&#xff0c;一些不满现状&#xff0c;被外界的“高薪”“好福利”吸引的人&#xff0c;一般就在这时候毅然决然地跳槽了。 在此展示一套学习笔记 / 面试手册&#xff0c;年后跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的软件测试…

银河麒麟V10-ARM架构-postgresql安装与部署指南

提示&#xff1a;本人长期接收外包任务。 前言 本文详细介绍应用源码进行pgsql的安装步骤&#xff0c;本文以postgresql-12.0为例。 一、下载并解压安装包 ☆下载地址&#xff1a;https://ftp.postgresql.org/pub/source/ 解压安装包&#xff0c;创建安装路径&#xff1a; …

shopee数据分析软件:了解市场趋势,分析竞争对手,优化运营策略

在当今数字化时代&#xff0c;数据已经成为了企业决策的重要依据。对于电商行业来说&#xff0c;数据更是至关重要。如果你想在电商领域中脱颖而出&#xff0c;那么你需要一款强大的数据分析工具来帮助你更好地了解市场、分析竞争对手、优化运营策略。而知虾数据软件就是这样一…

【python学习】中级篇-图形界面-内置库Tkinter,用于创建图形用户界面(GUI)

Tkinter是Python的一个内置库&#xff0c;用于创建图形用户界面(GUI)。 以下是一个简单的Tkinter用法示例&#xff1a; import tkinter as tkdef on_click():label.config(text"你好&#xff0c;" entry.get())# 创建主窗口 root tk.Tk() root.title("Tkinte…

【python】[subprocess库] 优雅的并发模板:并发,多进程管理与交互

需求 1> 创建多个进程&#xff0c;并发执行多个终端指令 2> 每个进程的进程号不同&#xff08;以供记录&#xff0c;并在异常退出时进行进程清理&#xff09; 3> 每个子进程的输出可被python变量记录 &#xff08;别问&#xff0c;就是想看&#xff09; 4> 这些子…

错题集(c语言)

一、 #include <stdio.h> int main() {int x, y;for (x 30, y 0; x > 10, y<10; x--, y)x / 2, y 2;printf("x%d,y%d\n", x, y);return 0; }思路&#xff1a; 第一次循环开始前&#xff1a;x30&#xff0c;y0&#xff0c;结束&#xff1a;x15&#…

js算法面试题(附答案)

js算法面试题十道 两数之和 题目&#xff1a;给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那两个整数&#xff0c;并返回他们的数组下标。 function twoSum(nums, target) {const map new Map();for (let i 0; i < nums.leng…

Java中如何使用雪花算法生成唯一ID

雪花算法&#xff08;Snowflake ID&#xff09;是 Twitter 开源的一种分布式 ID 生成算法&#xff0c;其目的是生成全局唯一的 ID。该算法的核心思想是将一个 64 位的二进制数字分成几个部分&#xff0c;每个部分表示不同的信息&#xff0c;例如数据中心ID、机器ID、序列号等。…

BUUCTF 梅花香之苦寒来 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 注意&#xff1a;得到的 flag 请包上 flag{} 提交 密文&#xff1a; 下载附件&#xff0c;解压得到一张.jpg图片。 解题思路&#xff1a; 1、用010 Editor看了一下&#xff0c;刚开始以为是修改宽高的题&#xff…

羊大师教你如何有效解决工作中的挑战与压力?

在现代社会&#xff0c;工作问题一直是许多人头疼的难题。无论是从工作压力到职业发展&#xff0c;工作问题不仅会影响个人的心理健康&#xff0c;还可能对整个工作团队的效率和和谐产生负面影响。因此&#xff0c;如何有效解决工作问题成为了每个职场人士都需要面对的挑战。 …

Web前端—移动Web第四天(vw适配方案、vw和vh的基本使用、综合案例-酷我音乐)

版本说明 当前版本号[20231122]。 版本修改说明20231122初版 目录 文章目录 版本说明目录移动 Web 第四天01-vw适配方案vw和vh基本使用vw布局vh布局混用问题 02-综合案例-酷我音乐准备工作头部布局头部内容搜索区域banner 区域标题公共样式排行榜内容推荐歌单布局推荐歌单内…

Cuda out of memory原因以及解决办法

Cuda out of memory原因以及解决办法 文章目录 Cuda out of memory原因以及解决办法batch_size设置过大 batch_size设置过大 最近在做对抗训练方面的实验&#xff0c;当batch_size设置为256的时候&#xff0c;出现cuda out of memory. 当将batch_size修改为128时&#xff0c;则…

mysql使用--连接查询

1.连接查询 如&#xff1a;SELECT * FROM t1, t2; 上述FROM语句将t1表&#xff0c;t2表连接。 假设t1表含n条记录&#xff0c;t2表含m条记录&#xff0c;则t1, t2得到的表将包含n*m条记录。 我们以一个混合连接&#xff0c;过滤的查询分析语句执行过程。 如&#xff1a;SELECT…

thinkphp文件夹生成zip压缩包

一、准备工作&#xff0c;使用phpinfo()查看有没有zip扩展 <?php echo phpinfo(); ?>Thinkphp使用PHP自带的ZipArchive压缩文件或文件夹 显示enabled 说明已经配置好 如果没有安装扩展的&#xff0c;请参照以下方法&#xff1a; 1、下载对应版本的扩展包&#xff1a…

Java操作excel之poi

1. 创建Excel 1.1 创建新Excel工作簿 引入poi依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</ar…

QTableView 和 QAbstractTableModel

1.自定义类继承QAbstractTableModel 头文件如下&#xff1a; #ifndef TESTMOUDLE_H #define TESTMOUDLE_H #include "StructTest.h" #include <QAbstractTableModel> class TestMoudle : public QAbstractTableModel { public: TestMoudle(QStringList&…

如何一次性解压多个文件

第一步&#xff1a;多选压缩包 第二步&#xff1a;右键解压即可 一句话&#xff0c;单个怎么解压&#xff0c;多个就怎么解压&#xff0c;只不过先选中 参考&#xff1a;如何一次性解压多个文件

智能安全帽作业记录仪赋能智慧工地人脸识别劳务实名制

需求背景 建筑工地是一个安全事故多发的场所。目前&#xff0c;工程建设规模不断扩大&#xff0c;工艺流程纷繁复杂&#xff0c;如何完善现场施工现场管理&#xff0c;控制事故发生频率&#xff0c;保障文明施工一直是施工企业、政府管理部门关注的焦点。尤其随着社会的不断进…

YARN,ZOOKEERPER--学习笔记

1&#xff0c;YARN组件 1.1YARN简介 YARN表示分布式资源调度&#xff0c;简单地说&#xff0c;就是&#xff1a;以分布式技术完成资源的合理分配&#xff0c;让MapReduce能高效完成计算任务。 YARN是Hadoop核心组件之一&#xff0c;用于提供分布式资源调度服务。 而在Hadoop …