2.9.C++项目:网络版五子棋对战之业务处理模块的设计
文章目录
- 一、意义
- 二、功能
- 三、管理
- (一)客户端请求
- (二)websocket
- 四、框架
- 五、完整代码

一、意义
将所有的模块整合在一起,通过网络通信获取到客户端的请求,提供不同的业务处理。
服务器模块,是对当前所实现的所有模块的⼀个整合,并进行服务器搭建的一个模块,最终封装实现出⼀个gobang_server的服务器模块类,向外提供搭建五⼦棋对战服务器的接口。通过实例化的对象可以简便的完成服务器的搭建。
二、功能
- 搭建websocket服务器,实现网络通信。
- 针对各种不同的请求进行不同的业务处理。
三、管理
(一)客户端请求
1. 客户端从服务器获取一个注册页面 - 静态页面请求
2. 客户端给服务器发送一个注册请求(提交用户名以及密码)—— 动态功能请求
3. 客户端从服务器获取一个登陆页面 - 静态页面请求
4. 客户端给服务器发送一个登录请求(提交用户名以及密码) —— 动态功能请求
5. 客户端从服务器获取一个游戏大厅页面 - 静态页面请求
6. 客户端给服务器发送了一个获取个人信息的请求(展现个人信息)—— 动态功能请求
(二)websocket
- 客户端给服务器发送了一个切换websocket协议通信的请求(建立游戏大厅长连接)
- 客户端给服务器发送对战匹配请求
- 客户端给服务器发送停止匹配请求
- 对战匹配成功,客户端从服务器获取一个游戏房间页面 - 静态页面请求
- 客户端给服务器发送了一个切换websocket协议通信的请求(建立游戏房间长连接)
- 客户端给服务器发送下棋请求
- 客户端给服务器发送聊天请求
- 游戏结束,返回游戏大厅,客户端给服务器发送一个获取游戏大厅页面的请求 -> 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++项目:网络版五子棋对战之业务处理模块的设计
文章目录 一、意义二、功能三、管理(一)客户端请求(二)websocket 四、框架五、完整代码 一、意义 将所有的模块整合在一起,通过网络通信获取到客户端的请求,提供不同的业务处理。 服务器模块,是…...

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

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

同一个页面同一区域两个el-table在v-if下样式重叠问题
🍉正常情况下在radio切换时两个表格的样式应如下 🍉实际上用v-if显示时会出现以下问题(本该属于时间段相同模块的表格却出现在时间段自定义的表格中) 🍉解决方案: 🍃一、将v-if替换成v-show(…...

ExoPlayer架构详解与源码分析(6)——MediaPeriod
系列文章目录 ExoPlayer架构详解与源码分析(1)——前言 ExoPlayer架构详解与源码分析(2)——Player ExoPlayer架构详解与源码分析(3)——Timeline ExoPlayer架构详解与源码分析(4)—…...
【开题报告】基于Spring Boot的课程在线预约系统的设计与实现
1.引言 随着互联网的发展,线上教育和课程培训变得越来越普遍。然而,很多学生在选择课程时面临一些困扰,例如如何找到适合自己的课程,如何与老师进行预约等。因此,设计一个基于Spring Boot的课程在线预约系统具有重要的…...
React Hooks还有哪些常用的用法?
除了之前提到的 useState、useEffect、useContext、useRef、useMemo 和 useCallback,还有一些其他常用的 React Hooks,它们提供了额外的功能和灵活性。以下是其中一些常见的 React Hooks: 1:useReducer:用于在函数组件中管理复杂的状态逻辑,类似于 Redux 的 reducer。 …...

基于Java的学生学籍管理系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding) 代码参考数据库参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…...
Java内部类、匿名内部类、嵌套类详解
CONTENTS 1. 创建内部类2. 内部类到外部类的连接3. 在内部类中生成外部类对象的引用4. 匿名内部类5. 嵌套类6. 接口中的类 1. 创建内部类 创建内部类的方式就是把类定义放在一个包围它的类之中: package com.yyj;public class Parcel1 {class Contests {private i…...

【兔子王赠书第3期】《案例学Python(进阶篇)》
文章目录 前言推荐图书本书特色本书目录本书样章本书读者对象粉丝福利丨评论免费赠书尾声 前言 随着人工智能和大数据的蓬勃发展,Python将会得到越来越多开发者的喜爱和应用。因为Python语法简单,学习速度快,大家可以用更短的时间掌握这门语…...

【C刷题】day6
一、选择题 1、以下叙述中正确的是( ) A: 只能在循环体内和switch语句体内使用break语句 B: 当break出现在循环体中的switch语句体内时,其作用是跳出该switch语句体,并中止循环体的执行 C: continue语句的作用是:在…...
MySQL精髓:如何使用ALL一次找到最大值
题目来自LeetCode 题目 表: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+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding) 代码参考数据库参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…...
如何确定Apache Kafka的大小和规模
调整或扩展Kafka以获得最佳成本和性能的第一步是了解数据流平台如何使用资源。这里给一些实用的建议。 实现Apache Kafka的团队,或者扩展他们对强大的开源分布式事件流平台的使用,通常需要帮助理解如何根据他们的需求正确地调整和扩展Kafka资源。这可能…...

项目总结-新增商品-Pagehelper插件分页查询
(1)新增商品 工具类: /** * 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语言,都需要实战、做大量输入输出等 1.环境变量设置 1.下载jdk安装 jdk官网下载直达链接:https://www.oracle.com/java/te…...

API自动化测试:如何构建高效的测试流程!
一、引言 在当前的软件开发环境中,API(Application Programming Interface)扮演了极为重要的角色,连接着应用的各个部分。对API进行自动化测试能够提高测试效率,降低错误,确保软件产品的质量。本文将通过实…...
MySQL8锁的问题
关键字 mysql 8、lock 问题描述 项目上反馈,一个简单的提交操作需要 40 秒。 抓取 SQL 发现 update gl_credit_bill set verifystate2 where id2761279790403840 执行耗时近40秒解决问题思路 手动执行 SQL,发现非常快,基本排除数据库本身…...

进阶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 流…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...