当前位置: 首页 > news >正文

2.9.C++项目:网络版五子棋对战之业务处理模块的设计

文章目录

  • 一、意义
  • 二、功能
  • 三、管理
    • (一)客户端请求
    • (二)websocket
  • 四、框架
  • 五、完整代码

在这里插入图片描述

一、意义

将所有的模块整合在一起,通过网络通信获取到客户端的请求,提供不同的业务处理。
服务器模块,是对当前所实现的所有模块的⼀个整合,并进行服务器搭建的一个模块,最终封装实现出⼀个gobang_server的服务器模块类,向外提供搭建五⼦棋对战服务器的接口。通过实例化的对象可以简便的完成服务器的搭建。

二、功能

  1. 搭建websocket服务器,实现网络通信。
  2. 针对各种不同的请求进行不同的业务处理。

三、管理

(一)客户端请求

1. 客户端从服务器获取一个注册页面 - 静态页面请求
2. 客户端给服务器发送一个注册请求(提交用户名以及密码)—— 动态功能请求
3. 客户端从服务器获取一个登陆页面 - 静态页面请求
4. 客户端给服务器发送一个登录请求(提交用户名以及密码) —— 动态功能请求
5. 客户端从服务器获取一个游戏大厅页面 - 静态页面请求
6. 客户端给服务器发送了一个获取个人信息的请求(展现个人信息)—— 动态功能请求

(二)websocket

  1. 客户端给服务器发送了一个切换websocket协议通信的请求(建立游戏大厅长连接)
  2. 客户端给服务器发送对战匹配请求
  3. 客户端给服务器发送停止匹配请求
  4. 对战匹配成功,客户端从服务器获取一个游戏房间页面 - 静态页面请求
  5. 客户端给服务器发送了一个切换websocket协议通信的请求(建立游戏房间长连接)
  6. 客户端给服务器发送下棋请求
  7. 客户端给服务器发送聊天请求
  8. 游戏结束,返回游戏大厅,客户端给服务器发送一个获取游戏大厅页面的请求 -> 5

四、框架

1. 静态资源页面,在后台服务器上是个html/css/js 文件
静态资源请求的处理,其实就是将文件中的内容发给客户端
1. 注册页面请求
请求:GET /register.htm HTTP/1.1
响应:
HTTP/1.1 200 OK 
Content - Length : xxx
Content - Type : text/html
register.html 文件中的内容数据2. 登陆页面请求
请求:GET /login.htm HTTP/1.13. 大厅页面请求请求:GET /game_hall.htm HTTP/1.14. 房间页面请求
请求:GET /game_room.htm HTTP/1.1动态功能请求:
post /reg HTTP/1.1#ifndef __M_SRV_H__
#define __M_SRV_H__
#include "db.hpp"
#include "matcher.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include "util.hpp"#define WWWROOT "./wwwroot/"class gobang_server {private:std::string _web_root;//静态资源根目录 ./wwwroot/      /register.html ->  ./wwwroot/register.htmlwsserver_t _wssrv;user_table _ut;online_manager _om;room_manager _rm;matcher _mm;session_manager _sm;private:void wsclose_callback(websocketpp::connection_hdl hdl);void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg);void wsopen_callback(websocketpp::connection_hdl hdl);void http_callback(websocketpp::connection_hdl hdl);public:/*进行成员初始化,以及服务器回调函数的设置*/gobang_server(const std::string &host,const std::string &user,const std::string &pass,const std::string &dbname,uint16_t port = 3306,const std::string &wwwroot = WWWROOT):_web_root(wwwroot), _ut(host, user, pass, dbname, port),_rm(&_ut, &_om), _sm(&_wssrv), _mm(&_rm, &_ut, &_om) {_wssrv.set_access_channels(websocketpp::log::alevel::none);_wssrv.init_asio();_wssrv.set_reuse_addr(true);_wssrv.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));_wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));_wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));_wssrv.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));}/*启动服务器*/void start(int port) {_wssrv.listen(port);_wssrv.start_accept();_wssrv.run();}
};

五、完整代码

// 服务器的整合实现:
// 1. 网络通信接口的设计
//     收到一个什么格式的数据,代表什么样的请求,应该给予什么样的业务处理及响应。
// 2. 开始搭建服务器
//     1. 搭建websocket服务器,实现网络通信。
//     2. 针对各种不同的请求进行不同的业务处理。
// 客户端存在的请求:
// 1. 客户端从服务器获取一个注册页面 - 静态页面请求
// 2. 客户端给服务器发送一个注册请求(提交用户名以及密码)—— 动态功能请求
// 3. 客户端从服务器获取一个登陆页面 - 静态页面请求
// 4. 客户端给服务器发送一个登录请求(提交用户名以及密码) —— 动态功能请求
// 5. 客户端从服务器获取一个游戏大厅页面 - 静态页面请求
// 6. 客户端给服务器发送了一个获取个人信息的请求(展现个人信息)—— 动态功能请求// websocket
// 7. 客户端给服务器发送了一个切换websocket协议通信的请求(建立游戏大厅长连接)
// 8. 客户端给服务器发送对战匹配请求
// 9. 客户端给服务器发送停止匹配请求 
// 10. 对战匹配成功,客户端从服务器获取一个游戏房间页面 - 静态页面请求
// 11. 客户端给服务器发送了一个切换websocket协议通信的请求(建立游戏房间长连接)
// 12. 客户端给服务器发送下棋请求
// 13. 客户端给服务器发送聊天请求
// 14. 游戏结束,返回游戏大厅,客户端给服务器发送一个获取游戏大厅页面的请求 -> 5// 1. 静态资源页面,在后台服务器上是个html/css/js 文件
// 静态资源请求的处理,其实就是将文件中的内容发给客户端
// 1. 注册页面请求
// 请求:GET /register.htm HTTP/1.1
// 响应:
// HTTP/1.1 200 OK 
// Content - Length : xxx
// Content - Type : text/html
// register.html 文件中的内容数据// 2. 登陆页面请求
// 请求:GET /login.htm HTTP/1.1// 3. 大厅页面请求// 请求:GET /game_hall.htm HTTP/1.1// 4. 房间页面请求
// 请求:GET /game_room.htm HTTP/1.1// 动态功能请求:
// post /reg HTTP/1.1#ifndef __M_SRV_H__
#define __M_SRV_H__
#include "db.hpp"
#include "matcher.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include "util.hpp"#define WWWROOT "./wwwroot/"
class gobang_server{private:std::string _web_root;//静态资源根目录 ./wwwroot/      /register.html ->  ./wwwroot/register.htmlwsserver_t _wssrv;user_table _ut;online_manager _om;room_manager _rm;matcher _mm;session_manager _sm;private:void file_handler(wsserver_t::connection_ptr &conn) {//静态资源请求的处理//1. 获取到请求uri-资源路径,了解客户端请求的页面文件名称websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();//2. 组合出文件的实际路径   相对根目录 + uristd::string realpath = _web_root + uri;//3. 如果请求的是个目录,增加一个后缀  login.html,    /  ->  /login.htmlif (realpath.back() == '/') {realpath += "login.html";}//4. 读取文件内容Json::Value resp_json;std::string body;bool ret = file_util::read(realpath, body);//  1. 文件不存在,读取文件内容失败,返回404if (ret == false) {body += "<html>";body += "<head>";body += "<meta charset='UTF-8'/>";body += "</head>";body += "<body>";body += "<h1> Not Found </h1>";body += "</body>";conn->set_status(websocketpp::http::status_code::not_found);conn->set_body(body);return;}//5. 设置响应正文conn->set_body(body);conn->set_status(websocketpp::http::status_code::ok);}void http_resp(wsserver_t::connection_ptr &conn, bool result, websocketpp::http::status_code::value code, const std::string &reason) {Json::Value resp_json;resp_json["result"] = result;resp_json["reason"] = reason;std::string resp_body;json_util::serialize(resp_json, resp_body);conn->set_status(code);conn->set_body(resp_body);conn->append_header("Content-Type", "application/json");return;}void reg(wsserver_t::connection_ptr &conn) {//用户注册功能请求的处理websocketpp::http::parser::request req = conn->get_request();//1. 获取到请求正文std::string req_body = conn->get_request_body();//2. 对正文进行json反序列化,得到用户名和密码Json::Value login_info;bool ret = json_util::unserialize(req_body, login_info);if (ret == false) {DLOG("反序列化注册信息失败");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");}//3. 进行数据库的用户新增操作if (login_info["username"].isNull() || login_info["password"].isNull()) {DLOG("用户名密码不完整");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");}ret = _ut.insert(login_info);if (ret == false) {DLOG("向数据库插入数据失败");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名已经被占用!");}//  如果成功了,则返回200return http_resp(conn, true, websocketpp::http::status_code::ok, "注册用户成功");}void login(wsserver_t::connection_ptr &conn) {//用户登录功能请求的处理//1. 获取请求正文,并进行json反序列化,得到用户名和密码std::string req_body = conn->get_request_body();Json::Value login_info;bool ret = json_util::unserialize(req_body, login_info);if (ret == false) {DLOG("反序列化登录信息失败");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");}//2. 校验正文完整性,进行数据库的用户信息验证if (login_info["username"].isNull() || login_info["password"].isNull()) {DLOG("用户名密码不完整");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");}ret = _ut.login(login_info);if (ret == false) {//  1. 如果验证失败,则返回400DLOG("用户名密码错误");return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名密码错误");}//3. 如果验证成功,给客户端创建sessionuint64_t uid = login_info["id"].asUInt64();session_ptr ssp = _sm.create_session(uid, LOGIN);if (ssp.get() == nullptr) {DLOG("创建会话失败");return http_resp(conn, false, websocketpp::http::status_code::internal_server_error , "创建会话失败");}_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);//4. 设置响应头部:Set-Cookie,将sessionid通过cookie返回std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());conn->append_header("Set-Cookie", cookie_ssid);return http_resp(conn, true, websocketpp::http::status_code::ok , "登录成功");}bool get_cookie_val(const std::string &cookie_str, const std::string &key,  std::string &val) {// Cookie: SSID=XXX; path=/; //1. 以 ; 作为间隔,对字符串进行分割,得到各个单个的cookie信息std::string sep = "; ";std::vector<std::string> cookie_arr;string_util::split(cookie_str, sep, cookie_arr);for (auto str : cookie_arr) {//2. 对单个cookie字符串,以 = 为间隔进行分割,得到key和valstd::vector<std::string> tmp_arr;string_util::split(str, "=", tmp_arr);if (tmp_arr.size() != 2) { continue; }if (tmp_arr[0] == key) {val = tmp_arr[1];return true;}}return false;}void info(wsserver_t::connection_ptr &conn) {//用户信息获取功能请求的处理Json::Value err_resp;// 1. 获取请求信息中的Cookie,从Cookie中获取ssidstd::string cookie_str = conn->get_request_header("Cookie");if (cookie_str.empty()) {//如果没有cookie,返回错误:没有cookie信息,让客户端重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到cookie信息,请重新登录");}// 1.5. 从cookie中取出ssidstd::string ssid_str;bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);if (ret == false) {//cookie中没有ssid,返回错误:没有ssid信息,让客户端重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到ssid信息,请重新登录");}// 2. 在session管理中查找对应的会话信息session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));if (ssp.get() == nullptr) {//没有找到session,则认为登录已经过期,需要重新登录return http_resp(conn, true, websocketpp::http::status_code::bad_request, "登录过期,请重新登录");}// 3. 从数据库中取出用户信息,进行序列化发送给客户端uint64_t uid = ssp->get_user();Json::Value user_info;ret = _ut.select_by_id(uid, user_info);if (ret == false) {//获取用户信息失败,返回错误:找不到用户信息return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到用户信息,请重新登录");}std::string body;json_util::serialize(user_info, body);conn->set_body(body);conn->append_header("Content-Type", "application/json");conn->set_status(websocketpp::http::status_code::ok);// 4. 刷新session的过期时间_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);}void http_callback(websocketpp::connection_hdl hdl) {wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string method = req.get_method();std::string uri = req.get_uri();if (method == "POST" && uri == "/reg") {return reg(conn);}else if (method == "POST" && uri == "/login") {return login(conn);}else if (method == "GET" && uri == "/info") {return info(conn);}else {return file_handler(conn);}}void ws_resp(wsserver_t::connection_ptr conn, Json::Value &resp) {std::string body;json_util::serialize(resp, body);conn->send(body);}session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn) {Json::Value err_resp;// 1. 获取请求信息中的Cookie,从Cookie中获取ssidstd::string cookie_str = conn->get_request_header("Cookie");if (cookie_str.empty()) {//如果没有cookie,返回错误:没有cookie信息,让客户端重新登录err_resp["optype"] = "hall_ready";err_resp["reason"] = "没有找到cookie信息,需要重新登录";err_resp["result"] = false;ws_resp(conn, err_resp);return session_ptr();}// 1.5. 从cookie中取出ssidstd::string ssid_str;bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);if (ret == false) {//cookie中没有ssid,返回错误:没有ssid信息,让客户端重新登录err_resp["optype"] = "hall_ready";err_resp["reason"] = "没有找到SSID信息,需要重新登录";err_resp["result"] = false;ws_resp(conn, err_resp);return session_ptr();}// 2. 在session管理中查找对应的会话信息session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));if (ssp.get() == nullptr) {//没有找到session,则认为登录已经过期,需要重新登录err_resp["optype"] = "hall_ready";err_resp["reason"] = "没有找到session信息,需要重新登录";err_resp["result"] = false;ws_resp(conn, err_resp);return session_ptr();}return ssp;}void wsopen_game_hall(wsserver_t::connection_ptr conn) {//游戏大厅长连接建立成功Json::Value resp_json;//1. 登录验证--判断当前客户端是否已经成功登录session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//2. 判断当前客户端是否是重复登录if (_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user())) {resp_json["optype"] = "hall_ready";resp_json["reason"] = "玩家重复登录!";resp_json["result"] = false;return ws_resp(conn, resp_json);}//3. 将当前客户端以及连接加入到游戏大厅_om.enter_game_hall(ssp->get_user(), conn);//4. 给客户端响应游戏大厅连接建立成功resp_json["optype"] = "hall_ready";resp_json["result"] = true;ws_resp(conn, resp_json);//5. 记得将session设置为永久存在_sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);}void wsopen_game_room(wsserver_t::connection_ptr conn) {Json::Value resp_json;//1. 获取当前客户端的sessionsession_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//2. 当前用户是否已经在在线用户管理的游戏房间或者游戏大厅中---在线用户管理if (_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user())) {resp_json["optype"] = "room_ready";resp_json["reason"] = "玩家重复登录!";resp_json["result"] = false;return ws_resp(conn, resp_json);}//3. 判断当前用户是否已经创建好了房间 --- 房间管理room_ptr rp = _rm.get_room_by_uid(ssp->get_user());if (rp.get() == nullptr) {resp_json["optype"] = "room_ready";resp_json["reason"] = "没有找到玩家的房间信息";resp_json["result"] = false;return ws_resp(conn, resp_json);}//4. 将当前用户添加到在线用户管理的游戏房间中_om.enter_game_room(ssp->get_user(), conn);//5. 将session重新设置为永久存在_sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);//6. 回复房间准备完毕resp_json["optype"] = "room_ready";resp_json["result"] = true;resp_json["room_id"] = (Json::UInt64)rp->id();resp_json["uid"] = (Json::UInt64)ssp->get_user();resp_json["white_id"] = (Json::UInt64)rp->get_white_user();resp_json["black_id"] = (Json::UInt64)rp->get_black_user();return ws_resp(conn, resp_json);}void wsopen_callback(websocketpp::connection_hdl hdl) {//websocket长连接建立成功之后的处理函数wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsopen_game_hall(conn);}else if (uri == "/room") {//建立了游戏房间的长连接return wsopen_game_room(conn);}}void wsclose_game_hall(wsserver_t::connection_ptr conn) {//游戏大厅长连接断开的处理//1. 登录验证--判断当前客户端是否已经成功登录session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//1. 将玩家从游戏大厅中移除_om.exit_game_hall(ssp->get_user());//2. 将session恢复生命周期的管理,设置定时销毁_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);}void wsclose_game_room(wsserver_t::connection_ptr conn) {//获取会话信息,识别客户端session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//1. 将玩家从在线用户管理中移除_om.exit_game_room(ssp->get_user());//2. 将session回复生命周期的管理,设置定时销毁_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);//3. 将玩家从游戏房间中移除,房间中所有用户退出了就会销毁房间_rm.remove_room_user(ssp->get_user());}void wsclose_callback(websocketpp::connection_hdl hdl) {//websocket连接断开前的处理wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsclose_game_hall(conn);}else if (uri == "/room") {//建立了游戏房间的长连接return wsclose_game_room(conn);}}void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {Json::Value resp_json;std::string resp_body;//1. 身份验证,当前客户端到底是哪个玩家session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {return;}//2. 获取请求信息std::string req_body = msg->get_payload();Json::Value req_json;bool ret = json_util::unserialize(req_body, req_json);if (ret == false) {resp_json["result"] = false;resp_json["reason"] = "请求信息解析失败";return ws_resp(conn, resp_json);}//3. 对于请求进行处理:if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start"){//  开始对战匹配:通过匹配模块,将用户添加到匹配队列中_mm.add(ssp->get_user());resp_json["optype"] = "match_start";resp_json["result"] = true;return ws_resp(conn, resp_json);}else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop") {//  停止对战匹配:通过匹配模块,将用户从匹配队列中移除_mm.del(ssp->get_user());resp_json["optype"] = "match_stop";resp_json["result"] = true;return ws_resp(conn, resp_json);}resp_json["optype"] = "unknow";resp_json["reason"] = "请求类型未知";resp_json["result"] = false;return ws_resp(conn, resp_json);}void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg) {Json::Value resp_json;//1. 获取客户端session,识别客户端身份session_ptr ssp = get_session_by_cookie(conn);if (ssp.get() == nullptr) {DLOG("房间-没有找到会话信息");return;}//2. 获取客户端房间信息room_ptr rp = _rm.get_room_by_uid(ssp->get_user());if (rp.get() == nullptr) {resp_json["optype"] = "unknow";resp_json["reason"] = "没有找到玩家的房间信息";resp_json["result"] = false;DLOG("房间-没有找到玩家房间信息");return ws_resp(conn, resp_json);}//3. 对消息进行反序列化Json::Value req_json;std::string req_body = msg->get_payload();bool ret = json_util::unserialize(req_body, req_json);if (ret == false) {resp_json["optype"] = "unknow";resp_json["reason"] = "请求解析失败";resp_json["result"] = false;DLOG("房间-反序列化请求失败");return ws_resp(conn, resp_json);}DLOG("房间:收到房间请求,开始处理....");//4. 通过房间模块进行消息请求的处理return rp->handle_request(req_json);}void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg) {//websocket长连接通信处理wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);websocketpp::http::parser::request req = conn->get_request();std::string uri = req.get_uri();if (uri == "/hall") {//建立了游戏大厅的长连接return wsmsg_game_hall(conn, msg);}else if (uri == "/room") {//建立了游戏房间的长连接return wsmsg_game_room(conn, msg);}}public:/*进行成员初始化,以及服务器回调函数的设置*/gobang_server(const std::string &host,const std::string &user,const std::string &pass,const std::string &dbname,uint16_t port = 3306,const std::string &wwwroot = WWWROOT):_web_root(wwwroot), _ut(host, user, pass, dbname, port),_rm(&_ut, &_om), _sm(&_wssrv), _mm(&_rm, &_ut, &_om) {_wssrv.set_access_channels(websocketpp::log::alevel::none);_wssrv.init_asio();_wssrv.set_reuse_addr(true);_wssrv.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));_wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));_wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));_wssrv.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));}/*启动服务器*/void start(int port) {_wssrv.listen(port);_wssrv.start_accept();_wssrv.run();}
};
#endif

相关文章:

2.9.C++项目:网络版五子棋对战之业务处理模块的设计

文章目录 一、意义二、功能三、管理&#xff08;一&#xff09;客户端请求&#xff08;二&#xff09;websocket 四、框架五、完整代码 一、意义 将所有的模块整合在一起&#xff0c;通过网络通信获取到客户端的请求&#xff0c;提供不同的业务处理。 服务器模块&#xff0c;是…...

springboot actuator 常用接口

概述 微服务作为一项在云中部署应用和服务的新技术是当下比较热门话题&#xff0c;而微服务的特点决定了功能模块的部署是分布式的&#xff0c;运行在不同的机器上相互通过服务调用进行交互&#xff0c;业务流会经过多个微服务的处理和传递&#xff0c;在这种框架下&#xff0…...

知识点滴 - Email地址不区分大小写

电子邮件地址本身对字符大小写不敏感。这意味着实际的电子邮件地址&#xff0c;如 "exampleemail.com"&#xff0c;并不区分字母的大小写。无论你输入的是大写字母还是小写字母&#xff0c;它仍然会到达同一个电子邮件账户。例如&#xff0c;如果您的电子邮件地址是 …...

同一个页面同一区域两个el-table在v-if下样式重叠问题

&#x1f349;正常情况下在radio切换时两个表格的样式应如下 &#x1f349;实际上用v-if显示时会出现以下问题&#xff08;本该属于时间段相同模块的表格却出现在时间段自定义的表格中&#xff09; &#x1f349;解决方案&#xff1a; &#x1f343;一、将v-if替换成v-show(…...

ExoPlayer架构详解与源码分析(6)——MediaPeriod

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…...

【开题报告】基于Spring Boot的课程在线预约系统的设计与实现

1.引言 随着互联网的发展&#xff0c;线上教育和课程培训变得越来越普遍。然而&#xff0c;很多学生在选择课程时面临一些困扰&#xff0c;例如如何找到适合自己的课程&#xff0c;如何与老师进行预约等。因此&#xff0c;设计一个基于Spring Boot的课程在线预约系统具有重要的…...

React Hooks还有哪些常用的用法?

除了之前提到的 useState、useEffect、useContext、useRef、useMemo 和 useCallback,还有一些其他常用的 React Hooks,它们提供了额外的功能和灵活性。以下是其中一些常见的 React Hooks: 1:useReducer:用于在函数组件中管理复杂的状态逻辑,类似于 Redux 的 reducer。 …...

基于Java的学生学籍管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…...

Java内部类、匿名内部类、嵌套类详解

CONTENTS 1. 创建内部类2. 内部类到外部类的连接3. 在内部类中生成外部类对象的引用4. 匿名内部类5. 嵌套类6. 接口中的类 1. 创建内部类 创建内部类的方式就是把类定义放在一个包围它的类之中&#xff1a; package com.yyj;public class Parcel1 {class Contests {private i…...

【兔子王赠书第3期】《案例学Python(进阶篇)》

文章目录 前言推荐图书本书特色本书目录本书样章本书读者对象粉丝福利丨评论免费赠书尾声 前言 随着人工智能和大数据的蓬勃发展&#xff0c;Python将会得到越来越多开发者的喜爱和应用。因为Python语法简单&#xff0c;学习速度快&#xff0c;大家可以用更短的时间掌握这门语…...

【C刷题】day6

一、选择题 1、以下叙述中正确的是&#xff08; &#xff09; A: 只能在循环体内和switch语句体内使用break语句 B: 当break出现在循环体中的switch语句体内时&#xff0c;其作用是跳出该switch语句体&#xff0c;并中止循环体的执行 C: continue语句的作用是&#xff1a;在…...

MySQL精髓:如何使用ALL一次找到最大值

题目来自LeetCode 题目 表&#xff1a;Project -------------------- | Column Name | Type | -------------------- | project_id | int | | employee_id | int | -------------------- (project_id, employee_id) 是该表的主键(具有唯一值的列的组合)。 employee_id 是该表…...

安全设备

一.防火墙 5层应用层 防火墙 4层 udp tcp 协议 华为 厂商 华为 h3 1.区域划分 Dmz 停火区 Untrust 不安全区域 Trust 安全区域 防火墙 默认禁止所有 二.Waf Web 应用防火墙 放到web前面 产品 雷池 绿盟 软件 安…...

基于Java的足球赛会管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…...

如何确定Apache Kafka的大小和规模

调整或扩展Kafka以获得最佳成本和性能的第一步是了解数据流平台如何使用资源。这里给一些实用的建议。 实现Apache Kafka的团队&#xff0c;或者扩展他们对强大的开源分布式事件流平台的使用&#xff0c;通常需要帮助理解如何根据他们的需求正确地调整和扩展Kafka资源。这可能…...

项目总结-新增商品-Pagehelper插件分页查询

&#xff08;1&#xff09;新增商品 工具类&#xff1a; /** * Title: FileUtils.java * Package com.qfedu.common.utils * Description: TODO(用一句话描述该文件做什么) * author Feri * date 2018年5月29日 * version V1.0 */ package com.gdsdxy.common.u…...

java基础篇-环境变量

java基础 编程学习的关键点、重点1.环境变量设置待续 编程学习的关键点、重点 输入输出 Java语言、C语言、Python语言、甚至SQL语言&#xff0c;都需要实战、做大量输入输出等 1.环境变量设置 1.下载jdk安装 jdk官网下载直达链接&#xff1a;https://www.oracle.com/java/te…...

API自动化测试:如何构建高效的测试流程!

一、引言 在当前的软件开发环境中&#xff0c;API&#xff08;Application Programming Interface&#xff09;扮演了极为重要的角色&#xff0c;连接着应用的各个部分。对API进行自动化测试能够提高测试效率&#xff0c;降低错误&#xff0c;确保软件产品的质量。本文将通过实…...

MySQL8锁的问题

关键字 mysql 8、lock 问题描述 项目上反馈&#xff0c;一个简单的提交操作需要 40 秒。 抓取 SQL 发现 update gl_credit_bill set verifystate2 where id2761279790403840 执行耗时近40秒解决问题思路 手动执行 SQL&#xff0c;发现非常快&#xff0c;基本排除数据库本身…...

进阶JAVA篇-深入了解 Stream 流对象的创建与中间方法、终结方法

目录 1.0 Stream 流的说明 2.0 Stream 流对象的创建 2.1 对于 Collection 系列集合创建 Stream 流对象的方式 2.2 对于 Map 系列集合创建 Stream 流对象的方式 2.3 对于数组创建 Stream 流对象的方式 3.0 Stream 流的中间方法 3.1 Stream 流的 filter() 中间方法 3.2 Stream 流…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...