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.pswp.cn/news/160147.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/160147.shtml
英文地址,請注明出處:http://en.pswp.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 …