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

基于muduo库的图床云共享存储项目(二)

基于muduo库的图床云共享存储项目(二)

  • 文件传输和接口设计
    • HTTP服务构建
  • HTTP API设计
    • /api/reg注册接口
      • 代码实现
    • /api/login登录接口
      • 代码实现
  • 阶段性功能验证

在上一节当中,我们主要介绍了图床云共享存储项目的架构以及一些依赖的组件,接下来我们就需要来手把手实现对应的后端代码了。

文件传输和接口设计

HTTP服务构建

图床项目主要是http请求,所以首先构建http应用,在这儿我们是基于 muduo 构建http server,代码如下:

#include <iostream>
#include "muduo/net/TcpServer.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/net/EventLoop.h"
#include "muduo/base/Logging.h"using namespace muduo;
using namespace muduo::net;class HttpServer {public://构造函数 loop主线程的EventLoop, addr封装ip,port, name服务名字,num_event_loops多少个subReactorHttpServer(EventLoop *loop, const InetAddress &addr, const std::string &name,  int num_event_loops): loop_(loop), server_(loop, addr, name){// 各类回调函数的设置server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, std::placeholders::_1));}void start() {server_.start();}private:// TcpServer回调的设置void onConnection(const TcpConnectionPtr &conn) {LOG_INFO << "onConnectio: " << conn.get();}// 业务相关的处理void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time) {LOG_INFO << "onMessage: " << conn.get();}void onWriteComplete(const TcpConnectionPtr& conn) {LOG_INFO <<  "onWriteComplete " << conn.get();}private:TcpServer server_; // 用于每个连接的回调函数 接收新连接 收发数据EventLoop *loop_ = nullptr; // 主线程的EventLoop
};int main()
{std::cout << "hello tucuang!!! tc_http_src2\n";uint16_t http_bind_port = 8081; // 端口号const char *http_bind_ip = "0.0.0.0"; // ip地址int32_t num_event_loops = 4; // subRecator的数量EventLoop loop; // 主循环的loopInetAddress addr(http_bind_ip, http_bind_port);LOG_INFO << "port: " << http_bind_port;HttpServer server(&loop, addr, "HttpServer", num_event_loops);server.start();loop.loop();return 0;
}

基于api fox 来进行测试,我们可以看见,端口是可以正常来进行连接的:

在这里插入图片描述
接下来我们写一段测试代码,对 http 请求进行处理并且回发数据,看看是否是正常的:

#include <iostream>
#include "muduo/net/TcpServer.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/net/EventLoop.h"
#include "muduo/base/Logging.h"using namespace muduo;
using namespace muduo::net;class HttpServer {public://构造函数 loop主线程的EventLoop, addr封装ip,port, name服务名字,num_event_loops多少个subReactorHttpServer(EventLoop *loop, const InetAddress &addr, const std::string &name,  int num_event_loops): loop_(loop), server_(loop, addr, name){// 各类回调函数的设置server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, std::placeholders::_1));server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));server_.setWriteCompleteCallback(std::bind(&HttpServer::onWriteComplete, this, std::placeholders::_1));// subReactor对应线程数量设置server_.setThreadNum(num_event_loops);}void start() {server_.start();}private:// TcpServer回调的设置void onConnection(const TcpConnectionPtr &conn) {LOG_INFO << "onConnectio: " << conn.get();}// 业务相关的处理void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time) {LOG_INFO << "onMessage: " << conn.get();// 获取buf的数据const char* in_buf = buf->peek();LOG_INFO << "get msg: " << in_buf;char *resp_content = new char[256];string str_json = "{\"code\": 0}"; uint32_t len_json = str_json.size();//暂时先放这里#define HTTP_RESPONSE_REQ                                                     \"HTTP/1.1 200 OK\r\n"                                                      \"Connection:close\r\n"                                                     \"Content-Length:%d\r\n"                                                    \"Content-Type:application/json;charset=utf-8\r\n\r\n%s"snprintf(resp_content, 256, HTTP_RESPONSE_REQ, len_json, str_json.c_str()); 	conn->send(resp_content);conn->shutdown();}void onWriteComplete(const TcpConnectionPtr& conn) {LOG_INFO <<  "onWriteComplete " << conn.get();}private:TcpServer server_; // 用于每个连接的回调函数 接收新连接 收发数据EventLoop *loop_ = nullptr; // 主线程的EventLoop
};int main()
{std::cout << "hello tucuang!!! tc_http_src2\n";uint16_t http_bind_port = 8081; // 端口号const char *http_bind_ip = "0.0.0.0"; // ip地址int32_t num_event_loops = 4; // subRecator的数量EventLoop loop; // 主循环的loopInetAddress addr(http_bind_ip, http_bind_port);LOG_INFO << "port: " << http_bind_port;HttpServer server(&loop, addr, "HttpServer", num_event_loops);server.start();loop.loop();return 0;
}

测试结果如下,可以看见正常回发数据:
在这里插入图片描述
接下来就是对 httpconnection 类进行封装,他需要跟我们 tcpconnection 实现互联,包括对应的收到数据,解析数据,回发数据,都是通过 httpconnection 类来进行实现的:

http_conn.h

#ifndef __HTTP_CONN_H__
#define __HTTP_CONN_H__
#include "http_parser_wrapper.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/net/Buffer.h"using namespace muduo;
using namespace muduo::net;
using namespace std;class CHttpConn : public std::enable_shared_from_this<CHttpConn>
{
public:// 构造函数CHttpConn(TcpConnectionPtr tcp_con);// 析构函数virtual ~CHttpConn();// 业务处理void OnRead(Buffer *buf);private:TcpConnectionPtr tcp_conn_; // 对应的tcp connectionuint32_t uuid_;             // 生成唯一idCHttpParserWrapper http_parser; // http协议解析对象
};using CHttpConnPtr = std::shared_ptr<CHttpConn>;#endif

http_conn.c

#include "http_conn.h"
#include "muduo/base/Logging.h" CHttpConn::CHttpConn(TcpConnectionPtr tcp_conn): tcp_conn_(tcp_conn)
{// 构造对应的uuiduuid_ = std::any_cast<uint32_t>(tcp_conn_->getContext());LOG_INFO << "构造CHttpConn uuid: "<< uuid_ ;
}void CHttpConn::OnRead(Buffer *buf) {// 获取buf的数据const char* in_buf = buf->peek();// LOG_INFO << "get msg: " << in_buf;// 获取对应的数据长度int32_t length = buf->readableBytes();// 对url进行解析http_parser.ParseHttpContent(in_buf, length);string url = http_parser.GetUrlString();string content = http_parser.GetBodyContentString();LOG_INFO << "url: " << url << ", content: " << content;if (http_parser.IsReadAll()) {char *resp_content = new char[256];string str_json = "{\"code\": 0}"; uint32_t len_json = str_json.size();//暂时先放这里#define HTTP_RESPONSE_REQ                                                     \"HTTP/1.1 200 OK\r\n"                                                      \"Connection:close\r\n"                                                     \"Content-Length:%d\r\n"                                                    \"Content-Type:application/json;charset=utf-8\r\n\r\n%s"snprintf(resp_content, 256, HTTP_RESPONSE_REQ, len_json, str_json.c_str()); 	tcp_conn_->send(resp_content);}
}CHttpConn::~CHttpConn() {LOG_INFO << "析构CHttpConn uuid: "<< uuid_ ;
}

注意:

  • 每一个连接都会对应一个 http 请求,肯定会存在多个连接的场景,我们就需要建立对应的 http 连接的映射关系,因为后期我们是需要进行识别的,当前采用的就是生成一个唯一的 uuid 的方法,当建立一个连接以后,对应的 uuid 就++,然后去识别对应不同的 http 连接;
  • OnRead函数其实就是收到数据以后进行解析,然后回发数据,我们只是现在将其封装在了 httpconnectio 类中进行处理,最终还是在主函数的 onMessage 中进行调用即可。

main.cc

#include <iostream>
#include "muduo/net/TcpServer.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/net/EventLoop.h"
#include "muduo/base/Logging.h"
#include "http_parser.h"
#include "http_parser_wrapper.h"
#include "http_conn.h"using namespace muduo;
using namespace muduo::net;// 用于保存对应的http请求的映射关系
std::map<uint32_t, CHttpConnPtr> s_http_map;class HttpServer {
public://构造函数 loop主线程的EventLoop, addr封装ip,port, name服务名字,num_event_loops多少个subReactorHttpServer(EventLoop *loop, const InetAddress &addr, const std::string &name,  int num_event_loops): loop_(loop), server_(loop, addr, name){// 各类回调函数的设置server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, std::placeholders::_1));server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));server_.setWriteCompleteCallback(std::bind(&HttpServer::onWriteComplete, this, std::placeholders::_1));// subReactor对应线程数量设置server_.setThreadNum(num_event_loops);}void start() {server_.start();}private:// TcpServer回调的设置void onConnection(const TcpConnectionPtr &conn) {if (conn->connected()){uint32_t uuid = conn_uuid_generator_++;// 设置tcp的唯一标识conn->setContext(uuid);CHttpConnPtr http_conn = std::make_shared<CHttpConn>(conn);// 建立对应映射关系s_http_map.insert({uuid, http_conn});LOG_INFO << "onConnection new conn: " << conn.get();} else {// 删除对应映射关系uint32_t uuid = std::any_cast<uint32_t>(conn->getContext());s_http_map.erase(uuid);LOG_INFO << "onConnection dis conn: " << conn.get();}}// 业务相关的处理void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time) {LOG_INFO << "onMessage: " << conn.get();uint32_t uuid = std::any_cast<uint32_t>(conn->getContext());CHttpConnPtr &http_conn = s_http_map[uuid];//处理 相关业务http_conn->OnRead(buf);  // 直接在io线程处理}void onWriteComplete(const TcpConnectionPtr& conn) {LOG_INFO <<  "onWriteComplete " << conn.get();}private:TcpServer server_; // 用于每个连接的回调函数 接收新连接 收发数据EventLoop *loop_ = nullptr; // 主线程的EventLoopstd::atomic<uint32_t> conn_uuid_generator_ = 0;  // 这里是用于表示唯一http请求,不会一直保持链接
};int main()
{std::cout << "hello tucuang!!! tc_http_src2\n";uint16_t http_bind_port = 8081; // 端口号const char *http_bind_ip = "0.0.0.0"; // ip地址int32_t num_event_loops = 4; // subRecator的数量EventLoop loop; // 主循环的loopInetAddress addr(http_bind_ip, http_bind_port);LOG_INFO << "port: " << http_bind_port;HttpServer server(&loop, addr, "HttpServer", num_event_loops);server.start();loop.loop();return 0;
}
  • onMessage 就是相应的业务处理,收到数据,回发数据,onConnection 其实就是 http 请求发过来,就需要来建立连接,然后构建对应的 http 请求的映射关系,表示当前已经建立一个新的连接了,后续就是对业务进行处理了;
  • onMessage 和 onConnection 都需要设置成对应的回调函数,在 HttpServer 构造函数初始化的过程就设置,然后一旦有 http 连接发过来就会立马调用回调函数建立连接,然后对对应的请求进行相应的处理工作。

HTTP API设计

HTTP API设计大多数是基于JSON格式,这是有原因的。JSON构建起来很简单,因为大多数语言都内置了JSON支持。

JSON/HTTP对于基础设施项目的API来说是一个很好的选择,我们项目也是采用 HTTP + JSON的方式通过客户端向服务器请求数据。

主要的接口:

  • /api/reg 注册
  • /api/login 登录
  • /api/myfiles 用户文件列表
  • /api/md5 文件秒传检测
  • /api/upload 上传文件
  • /api/sharefiles 共享文件
  • /api/dealfile 分享/删除文件
  • /api/dealsharefile 处理分享文件相关
  • /api/sharepic 图片分享相关

本文主要是针对于注册以及登录的接口实现,其它接口在后续的文章当中也会进行实现。

/api/reg注册接口

注册是一个简单的HTTP接口,根据用户输入的注册信息,创建一个新的用户。

请求URL

URLhttp://192.168.1.6:8081/api/reg
请求方式POST
HTTP 版本1.1
Content-Typeapplication/json

请求参数

请求参数要区分必填字段和可选字段, 如果是必填字段,服务端没有检测该字段的时候 直接返回失败。

参数名含义规则说明是否必须缺省值
email邮箱必须符合email规范可选
firstPwd密码md5加密后的值必填
nickName用户昵称不能超过32个字符必填
phone手机号码不能超过16个字符可选
userName用户名称不能超过32个字符必填

应答参数

名称含义规则说明
code结果值0:成功
1:失败
2:用户存在

代码实现

api_register.h

#ifndef _API_REGISTER_H_
#define _API_REGISTER_H_
#include <iostream>// 用于用户登录信息的注册
int ApiRegisterUser(std::string &post_data, std::string &resp_json);#endif

api_register.cc

#include "api_register.h"
#include "muduo/base/Logging.h" // Logger日志头文件
#include <jsoncpp/json/json.h>// 封装对应结果的序列化接口
int encdoeRegisterJson(int code, std::string& str_json) {Json::Value root;root["code"] = code;Json::FastWriter writer;str_json = writer.write(root);return 0;
}// 对json进行反序列化
int decodeRegisterJson(const std::string &str_json, std::string &user_name,std::string &nick_name, std::string &pwd, std::string &phone,std::string &email)
{bool res;Json::Value root;Json::Reader jsonReader;res = jsonReader.parse(str_json, root);    if (!res) {LOG_ERROR << "parse reg json failed ";return -1;}// 用户名if (root["userName"].isNull()) {LOG_ERROR << "userName null";return -1;}user_name = root["userName"].asString();// 昵称if (root["nickName"].isNull()) {LOG_ERROR << "nickName null";return -1;}nick_name = root["nickName"].asString();//密码if (root["firstPwd"].isNull()) {LOG_ERROR << "firstPwd null";return -1;}pwd = root["firstPwd"].asString();//电话  非必须if (root["phone"].isNull()) {LOG_WARN << "phone null";} else {phone = root["phone"].asString();}//邮箱 非必须if (root["email"].isNull()) {LOG_WARN << "email null";} else {email = root["email"].asString();}return 0;
}int registerUser(std::string &user_name, std::string &nick_name, std::string &pwd,std::string &phone, std::string &email) {int ret = 0;// 还没有处理,先直接返回0return ret;
}// 用户注册接口
int ApiRegisterUser(std::string &post_data, std::string &resp_json) {int ret = 0;// 关于用户注册信息的一些变量std::string user_name;std::string nick_name;std::string pwd;std::string phone;std::string email;LOG_INFO << "post_data: " << post_data << "\n";// 判断当前传输的数据是否为空if (post_data.empty()) {LOG_ERROR << "post_data is empty!!!";// 序列化,返回对应的结果给客户端// code = 1encdoeRegisterJson(1, resp_json);return -1;}// 进行数据的反序列化ret = decodeRegisterJson(post_data, user_name, nick_name, pwd, phone, email);if(ret < 0) {encdoeRegisterJson(1, resp_json);return -1;}// 数据反序列化完成以后就进行账号的注册// 首先会在数据库中进行查找,找到了就是已经存在,未找到才继续进行注册ret = registerUser(user_name, nick_name, pwd, phone, email);encdoeRegisterJson(ret, resp_json);return 0;
}

api_register.cc 文件主要实现的就是对于用户注册的序列化以及反序列化:

  • 对于用户的注册信息,我们需要实现的功能就是如果当前用户存在,就在数据库中进行查找,如果不存在,就进行注册;
  • 对于客户端发送过来的数据,我们首先就要进行反序列化,转化为服务端的处理规则的数据,然后在进行对应的处理,最终为客户端返回对应的结果即可。

/api/login登录接口

登录,根据用户输入的登录信息,登录进入到后台系统。

请求URL

URLhttp://192.168.1.6:8081/api/reg
请求方式POST
HTTP 版本1.1
Content-Typeapplication/json

请求参数

参数名含义规则说明是否必须缺省值
pwd密码md5加密后的值必填
user用户名称不能超过32个字符必填

应答参数

名称含义规则说明
code结果值0: 成功
1: 失败
token令牌每次登录后,生成的token不一样,后续其他接口请求时,需要带上token,用来校验请求的合法性

代码实现

api_login.h

#ifndef _API_LOGIN_H_
#define _API_LOGIN_H_
#include <iostream> 
int ApiUserLogin(std::string &post_data, std::string &resp_json);#endif // ! _API_LOGIN_H_

api_login.cc

#include "api_register.h"
#include "muduo/base/Logging.h" // Logger日志头文件
#include <jsoncpp/json/json.h>// 封装登录结果的json
int encodeLoginJson(int code, std::string &token, std::string &str_json) {Json::Value root;root["code"] = code;if (code == 0) {root["token"] = token; // 正常返回的时候才写入token}Json::FastWriter writer;str_json = writer.write(root);return 0;
}// 解析登录信息
int decodeLoginJson(const std::string &str_json, std::string &user_name,std::string &pwd) {bool res;Json::Value root;Json::Reader jsonReader;res = jsonReader.parse(str_json, root);if (!res) {LOG_ERROR << "parse login json failed ";return -1;}// 用户名if (root["user"].isNull()) {LOG_ERROR << "user null";return -1;}user_name = root["user"].asString();//密码if (root["pwd"].isNull()) {LOG_ERROR << "pwd null";return -1;}pwd = root["pwd"].asString();return 0;
}// 验证账号密码是否匹配
int verifyUserPassword(std::string &user_name, std::string &pwd) {int ret = 0;// 这里暂时不做处理,因为这里还没有涉及数据库return ret;
}// 生成token信息
int setToken(std::string &user_name, std::string &token) {int ret = 0;token = "1234";//更新到redisreturn ret;
}// 登录接口
int ApiUserLogin(std::string &post_data, std::string &resp_json) {// 登录所用的用户名和密码std::string user_name;std::string pwd;// token跟后续的操作有关,这儿需要返回一个生成的唯一tokenstd::string token;if (post_data.empty()) {LOG_INFO << "post_data is empty!!!";encodeLoginJson(1, token, resp_json);return -1;}// 解析对应的json文件if (decodeLoginJson(post_data, user_name, pwd) < 0) {LOG_ERROR << "decodeRegisterJson failed";encodeLoginJson(1, token, resp_json);return -1;}// 验证账号和密码是否匹配if (verifyUserPassword(user_name, pwd) < 0) {LOG_ERROR << "verifyUserPassword failed";encodeLoginJson(1, token, resp_json);return -1;}// 生成token信息if (setToken(user_name, token) < 0) {LOG_ERROR << "setToken failed";encodeLoginJson(1, token, resp_json);return -1;}// 封装登录结果encodeLoginJson(0, token, resp_json);return 0;}

api_login.cc 文件主要实现的就是对于用户的序列化以及反序列化,跟 api_register.cc 文件的逻辑其实大差不差,只是各自的功能不一样:

  • 因为当前还没有引入数据库操作,所以这儿检查密码与用户名的并没有实现,只是直接返回 code = 0 成功的这样一个操作;
  • 每次登录后,会生成一个 token,生成的 token不一样,后续其他接口请求时,需要带上token,用来校验请求的合法性,token 是要存储在 Redis 当中的,这儿我们目前也没有进行实现,所以成功默认返回一个值即可。

当前功能已经实现,对应的数据就需要在 http server 当中进行处理,所以我们就需要添加对应的用户注册以及登录的处理接口:

http_conn.cc

#include "http_conn.h"
#include "muduo/base/Logging.h" 
#include "api/api_login.h"
#include "api/api_register.h"#define HTTP_RESPONSE_JSON_MAX 4096
#define HTTP_RESPONSE_JSON                                                     \"HTTP/1.1 200 OK\r\n"                                                      \"Connection:close\r\n"                                                     \"Content-Length:%d\r\n"                                                    \"Content-Type:application/json;charset=utf-8\r\n\r\n%s"#define HTTP_RESPONSE_HTML                                                    \"HTTP/1.1 200 OK\r\n"                                                      \"Connection:close\r\n"                                                     \"Content-Length:%d\r\n"                                                    \"Content-Type:text/html;charset=utf-8\r\n\r\n%s"#define HTTP_RESPONSE_BAD_REQ                                                     \"HTTP/1.1 400 Bad\r\n"                                                      \"Connection:close\r\n"                                                     \"Content-Length:%d\r\n"                                                    \"Content-Type:application/json;charset=utf-8\r\n\r\n%s"#define HTTP_RESPONSE_REQ                                                     \"HTTP/1.1 404 OK\r\n"                                                      \"Connection:close\r\n"                                                     \"Content-Length:%d\r\n"                                                    \"Content-Type:application/json;charset=utf-8\r\n\r\n%s"CHttpConn::CHttpConn(TcpConnectionPtr tcp_conn): tcp_conn_(tcp_conn)
{// 构造对应的uuiduuid_ = std::any_cast<uint32_t>(tcp_conn_->getContext());LOG_INFO << "构造CHttpConn uuid: "<< uuid_ ;
}void CHttpConn::OnRead(Buffer *buf) {// 获取buf的数据const char* in_buf = buf->peek();// LOG_INFO << "get msg: " << in_buf;// 获取对应的数据长度int32_t length = buf->readableBytes();// 对url进行解析http_parser.ParseHttpContent(in_buf, length);if (http_parser.IsReadAll()) {string url = http_parser.GetUrlString();string content = http_parser.GetBodyContentString();LOG_INFO << "url: " << url << ", content: " << content;if (strncmp(url.c_str(), "/api/reg", 8) == 0) {_HandleRegisterRequest(content);} else if (strncmp(url.c_str(), "/api/login", 10) == 0) {_HandleLoginRequest(content);} else {char *resp_content = new char[256];string str_json = "{\"code\": 0}"; uint32_t len_json = str_json.size();snprintf(resp_content, 256, HTTP_RESPONSE_REQ, len_json, str_json.c_str()); 	tcp_conn_->send(resp_content);}}
}// 账号注册处理
int CHttpConn::_HandleRegisterRequest(std::string &post_data) {string resp_json;// 调用注册的进行处理int ret = ApiRegisterUser(post_data, resp_json);// 封装http_bodychar *http_body = new char[HTTP_RESPONSE_JSON_MAX];uint32_t ulen = resp_json.length();snprintf(http_body, HTTP_RESPONSE_JSON_MAX, HTTP_RESPONSE_JSON, ulen,resp_json.c_str()); 	tcp_conn_->send(http_body);delete[] http_body;LOG_INFO << "    uuid: "<< uuid_;return 0;
}int CHttpConn::_HandleLoginRequest(std::string &post_data)
{string str_json;// 调用登录的接口进行处理int ret = ApiUserLogin(post_data, str_json);// 封装返回内容char *szContent = new char[HTTP_RESPONSE_JSON_MAX];uint32_t ulen = str_json.length();snprintf(szContent, HTTP_RESPONSE_JSON_MAX, HTTP_RESPONSE_JSON, ulen, str_json.c_str()); 	tcp_conn_->send(szContent);delete [] szContent;LOG_INFO << "    uuid: "<< uuid_; return 0;
}CHttpConn::~CHttpConn() {LOG_INFO << "析构CHttpConn uuid: "<< uuid_ ;
}

当我们读取到对应的 http 请求以后就会进行解析,通过对应的 url 进行判断,如果是注册就调用对应的注册处理函数,如果是登录就调用对应的登录处理函数,依次进行处理就好了。

阶段性功能验证

当前我们已经实现了注册以及登录的接口,我们先来进行验证一下:

在这里插入图片描述

可以看见,对应的功能是可以正常的实现的,本篇文章对于项目的介绍就到这儿,后续会继续进行更新。

http://www.dtcms.com/a/353862.html

相关文章:

  • STM32 之串口WIFI应用--基于RTOS的环境
  • AlphaFold 2 本地部署与安装教程(Linux)
  • ICCV 2025 | 清华IEDA提出GUAVA,单图创建可驱动的上半身3D化身!实时、高效,还能捕捉细腻的面部表情和手势。
  • 【51单片机】【protues仿真】基于51单片机篮球计时计分器数码管系统
  • 什么是代理ip?代理ip的运作机制
  • C++ 中 ::(作用域解析运算符)的用途
  • 大小鼠糖水偏爱实验系统 糖水偏好实验系统 小鼠糖水偏好实验系统 大鼠糖水偏好实验系统
  • 【半导体制造流程概述】
  • 优化IDEA卡顿的问题
  • 使用CCProxy搭建http/https代理服务器
  • AWS OpenSearch 可观测最佳实践
  • Maya绑定:人物绑定详细案例
  • 数据结构之 【红黑树的简介与插入问题的实现】
  • 数值分析离散积分近似求值
  • 【数据分析】微生物群落网络构建与模块划分的比较研究:SparCC、Spearman-RAW与Spearman-CLR方法的性能评估
  • Shell编程-随机密码生成
  • volitale伪共享问题及解决方案
  • SoC如何实现线程安全?
  • 【进阶篇第五弹】《详解存储过程》从0掌握MySQL中的存储过程以及存储函数
  • TypeScript:Interface接口
  • 如何启动一个分支网络改造试点?三步走
  • 【链表 - LeetCode】25. K 个一组翻转链表
  • 干眼症护理学注意事项
  • linux下的网络编程(2)
  • 技术分析 | Parasoft C/C++test如何突破单元测试的隔离难题
  • 亚马逊关键词策略全解析:类型、工具与多账号运营优化指南
  • AT_abc406_f [ABC406F] Compare Tree Weights
  • Windows/Linux 环境下 Jmeter 性能测试的安装与使用
  • 基于SpringBoot的宠物领养服务系统【2026最新】
  • MySQL 面试题系列(五)