This commit is contained in:
2025-10-13 18:34:48 +08:00
commit 37865d041f
116 changed files with 31168 additions and 0 deletions

25
common/asr.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "aip-cpp-sdk/speech.h"
#include "logger.hpp"
namespace bite_im{
class ASRClient {
public:
using ptr = std::shared_ptr<ASRClient>;
ASRClient(const std::string &app_id,
const std::string &api_key,
const std::string &secret_key):
_client(app_id, api_key, secret_key) {}
std::string recognize(const std::string &speech_data, std::string &err){
Json::Value result = _client.recognize(speech_data, "pcm", 16000, aip::null);
if (result["err_no"].asInt() != 0) {
LOG_ERROR("语音识别失败:{}", result["err_msg"].asString());
err = result["err_msg"].asString();
return std::string();
}
return result["result"][0].asString();
}
private:
aip::Speech _client;
};
}

151
common/channel.hpp Normal file
View File

@ -0,0 +1,151 @@
#pragma once
#include <brpc/channel.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include "logger.hpp"
namespace bite_im{
//1. 封装单个服务的信道管理类:
class ServiceChannel {
public:
using ptr = std::shared_ptr<ServiceChannel>;
using ChannelPtr = std::shared_ptr<brpc::Channel>;
ServiceChannel(const std::string &name):
_service_name(name), _index(0){}
//服务上线了一个节点则调用append新增信道
void append(const std::string &host) {
auto channel = std::make_shared<brpc::Channel>();
brpc::ChannelOptions options;
options.connect_timeout_ms = -1;
options.timeout_ms = -1;
options.max_retry = 3;
options.protocol = "baidu_std";
int ret = channel->Init(host.c_str(), &options);
if (ret == -1) {
LOG_ERROR("初始化{}-{}信道失败!", _service_name, host);
return;
}
std::unique_lock<std::mutex> lock(_mutex);
_hosts.insert(std::make_pair(host, channel));
_channels.push_back(channel);
}
//服务下线了一个节点则调用remove释放信道
void remove(const std::string &host) {
std::unique_lock<std::mutex> lock(_mutex);
auto it = _hosts.find(host);
if (it == _hosts.end()) {
LOG_WARN("{}-{}节点删除信道时,没有找到信道信息!", _service_name, host);
return;
}
for (auto vit = _channels.begin(); vit != _channels.end(); ++vit) {
if (*vit == it->second) {
_channels.erase(vit);
break;
}
}
_hosts.erase(it);
}
//通过RR轮转策略获取一个Channel用于发起对应服务的Rpc调用
ChannelPtr choose() {
std::unique_lock<std::mutex> lock(_mutex);
if (_channels.size() == 0) {
LOG_ERROR("当前没有能够提供 {} 服务的节点!", _service_name);
return ChannelPtr();
}
int32_t idx = _index++ % _channels.size();
return _channels[idx];
}
private:
std::mutex _mutex;
int32_t _index; //当前轮转下标计数器
std::string _service_name;//服务名称
std::vector<ChannelPtr> _channels; //当前服务对应的信道集合
std::unordered_map<std::string, ChannelPtr> _hosts; //主机地址与信道映射关系
};
//总体的服务信道管理类
class ServiceManager {
public:
using ptr = std::shared_ptr<ServiceManager>;
ServiceManager() {}
//获取指定服务的节点信道
ServiceChannel::ChannelPtr choose(const std::string &service_name) {
std::unique_lock<std::mutex> lock(_mutex);
auto sit = _services.find(service_name);
if (sit == _services.end()) {
LOG_ERROR("当前没有能够提供 {} 服务的节点!", service_name);
return ServiceChannel::ChannelPtr();
}
return sit->second->choose();
}
//先声明,我关注哪些服务的上下线,不关心的就不需要管理了
void declared(const std::string &service_name) {
std::unique_lock<std::mutex> lock(_mutex);
_follow_services.insert(service_name);
}
//服务上线时调用的回调接口,将服务节点管理起来
void onServiceOnline(const std::string &service_instance, const std::string &host) {
std::string service_name = getServiceName(service_instance);
ServiceChannel::ptr service;
{
std::unique_lock<std::mutex> lock(_mutex);
auto fit = _follow_services.find(service_name);
if (fit == _follow_services.end()) {
LOG_DEBUG("{}-{} 服务上线了,但是当前并不关心!", service_name, host);
return;
}
//先获取管理对象,没有则创建,有则添加节点
auto sit = _services.find(service_name);
if (sit == _services.end()) {
service = std::make_shared<ServiceChannel>(service_name);
_services.insert(std::make_pair(service_name, service));
}else {
service = sit->second;
}
}
if (!service) {
LOG_ERROR("新增 {} 服务管理节点失败!", service_name);
return ;
}
service->append(host);
LOG_DEBUG("{}-{} 服务上线新节点,进行添加管理!", service_name, host);
}
//服务下线时调用的回调接口,从服务信道管理中,删除指定节点信道
void onServiceOffline(const std::string &service_instance, const std::string &host) {
std::string service_name = getServiceName(service_instance);
ServiceChannel::ptr service;
{
std::unique_lock<std::mutex> lock(_mutex);
auto fit = _follow_services.find(service_name);
if (fit == _follow_services.end()) {
LOG_DEBUG("{}-{} 服务下线了,但是当前并不关心!", service_name, host);
return;
}
//先获取管理对象,没有则创建,有则添加节点
auto sit = _services.find(service_name);
if (sit == _services.end()) {
LOG_WARN("删除{}服务节点时,没有找到管理对象", service_name);
return;
}
service = sit->second;
}
service->remove(host);
LOG_DEBUG("{}-{} 服务下线节点,进行删除管理!", service_name, host);
}
private:
std::string getServiceName(const std::string &service_instance) {
auto pos = service_instance.find_last_of('/');
if (pos == std::string::npos) return service_instance;
return service_instance.substr(0, pos);
}
private:
std::mutex _mutex;
std::unordered_set<std::string> _follow_services;
std::unordered_map<std::string, ServiceChannel::ptr> _services;
};
}

164
common/data_es.hpp Normal file
View File

@ -0,0 +1,164 @@
#include "icsearch.hpp"
#include "user.hxx"
#include "message.hxx"
namespace bite_im {
class ESClientFactory {
public:
static std::shared_ptr<elasticlient::Client> create(const std::vector<std::string> host_list) {
return std::make_shared<elasticlient::Client>(host_list);
}
};
class ESUser {
public:
using ptr = std::shared_ptr<ESUser>;
ESUser(const std::shared_ptr<elasticlient::Client> &client):
_es_client(client){}
bool createIndex() {
bool ret = ESIndex(_es_client, "user")
.append("user_id", "keyword", "standard", true)
.append("nickname")
.append("phone", "keyword", "standard", true)
.append("description", "text", "standard", false)
.append("avatar_id", "keyword", "standard", false)
.create();
if (ret == false) {
LOG_INFO("用户信息索引创建失败!");
return false;
}
LOG_INFO("用户信息索引创建成功!");
return true;
}
bool appendData(const std::string &uid,
const std::string &phone,
const std::string &nickname,
const std::string &description,
const std::string &avatar_id) {
bool ret = ESInsert(_es_client, "user")
.append("user_id", uid)
.append("nickname", nickname)
.append("phone", phone)
.append("description", description)
.append("avatar_id", avatar_id)
.insert(uid);
if (ret == false) {
LOG_ERROR("用户数据插入/更新失败!");
return false;
}
LOG_INFO("用户数据新增/更新成功!");
return true;
}
std::vector<User> search(const std::string &key, const std::vector<std::string> &uid_list) {
std::vector<User> res;
Json::Value json_user = ESSearch(_es_client, "user")
.append_should_match("phone.keyword", key)
.append_should_match("user_id.keyword", key)
.append_should_match("nickname", key)
.append_must_not_terms("user_id.keyword", uid_list)
.search();
if (json_user.isArray() == false) {
LOG_ERROR("用户搜索结果为空,或者结果不是数组类型");
return res;
}
int sz = json_user.size();
LOG_DEBUG("检索结果条目数量:{}", sz);
for (int i = 0; i < sz; i++) {
User user;
user.user_id(json_user[i]["_source"]["user_id"].asString());
user.nickname(json_user[i]["_source"]["nickname"].asString());
user.description(json_user[i]["_source"]["description"].asString());
user.phone(json_user[i]["_source"]["phone"].asString());
user.avatar_id(json_user[i]["_source"]["avatar_id"].asString());
res.push_back(user);
}
return res;
}
private:
// const std::string _uid_key = "user_id";
// const std::string _desc_key = "user_id";
// const std::string _phone_key = "user_id";
// const std::string _name_key = "user_id";
// const std::string _avatar_key = "user_id";
std::shared_ptr<elasticlient::Client> _es_client;
};
class ESMessage {
public:
using ptr = std::shared_ptr<ESMessage>;
ESMessage(const std::shared_ptr<elasticlient::Client> &es_client):
_es_client(es_client){}
bool createIndex() {
bool ret = ESIndex(_es_client, "message")
.append("user_id", "keyword", "standard", false)
.append("message_id", "keyword", "standard", false)
.append("create_time", "long", "standard", false)
.append("chat_session_id", "keyword", "standard", true)
.append("content")
.create();
if (ret == false) {
LOG_INFO("消息信息索引创建失败!");
return false;
}
LOG_INFO("消息信息索引创建成功!");
return true;
}
bool appendData(const std::string &user_id,
const std::string &message_id,
const long create_time,
const std::string &chat_session_id,
const std::string &content) {
bool ret = ESInsert(_es_client, "message")
.append("message_id", message_id)
.append("create_time", create_time)
.append("user_id", user_id)
.append("chat_session_id", chat_session_id)
.append("content", content)
.insert(message_id);
if (ret == false) {
LOG_ERROR("消息数据插入/更新失败!");
return false;
}
LOG_INFO("消息数据新增/更新成功!");
return true;
}
bool remove(const std::string &mid) {
bool ret = ESRemove(_es_client, "message").remove(mid);
if (ret == false) {
LOG_ERROR("消息数据删除失败!");
return false;
}
LOG_INFO("消息数据删除成功!");
return true;
}
std::vector<bite_im::Message> search(const std::string &key, const std::string &ssid) {
std::vector<bite_im::Message> res;
Json::Value json_user = ESSearch(_es_client, "message")
.append_must_term("chat_session_id.keyword", ssid)
.append_must_match("content", key)
.search();
if (json_user.isArray() == false) {
LOG_ERROR("用户搜索结果为空,或者结果不是数组类型");
return res;
}
int sz = json_user.size();
LOG_DEBUG("检索结果条目数量:{}", sz);
for (int i = 0; i < sz; i++) {
bite_im::Message message;
message.user_id(json_user[i]["_source"]["user_id"].asString());
message.message_id(json_user[i]["_source"]["message_id"].asString());
boost::posix_time::ptime ctime(boost::posix_time::from_time_t(
json_user[i]["_source"]["create_time"].asInt64()));
message.create_time(ctime);
message.session_id(json_user[i]["_source"]["chat_session_id"].asString());
message.content(json_user[i]["_source"]["content"].asString());
res.push_back(message);
}
return res;
}
private:
std::shared_ptr<elasticlient::Client> _es_client;
};
}

75
common/data_redis.hpp Normal file
View File

@ -0,0 +1,75 @@
#include <sw/redis++/redis.h>
#include <iostream>
namespace bite_im {
class RedisClientFactory {
public:
static std::shared_ptr<sw::redis::Redis> create(
const std::string &host,
int port,
int db,
bool keep_alive) {
sw::redis::ConnectionOptions opts;
opts.host = host;
opts.port = port;
opts.db = db;
opts.keep_alive = keep_alive;
auto res = std::make_shared<sw::redis::Redis>(opts);
return res;
}
};
class Session {
public:
using ptr = std::shared_ptr<Session>;
Session(const std::shared_ptr<sw::redis::Redis> &redis_client):
_redis_client(redis_client){}
void append(const std::string &ssid, const std::string &uid) {
_redis_client->set(ssid, uid);
}
void remove(const std::string &ssid) {
_redis_client->del(ssid);
}
sw::redis::OptionalString uid(const std::string &ssid) {
return _redis_client->get(ssid);
}
private:
std::shared_ptr<sw::redis::Redis> _redis_client;
};
class Status {
public:
using ptr = std::shared_ptr<Status>;
Status(const std::shared_ptr<sw::redis::Redis> &redis_client):
_redis_client(redis_client){}
void append(const std::string &uid) {
_redis_client->set(uid, "");
}
void remove(const std::string &uid) {
_redis_client->del(uid);
}
bool exists(const std::string &uid) {
auto res = _redis_client->get(uid);
if (res) return true;
return false;
}
private:
std::shared_ptr<sw::redis::Redis> _redis_client;
};
class Codes {
public:
using ptr = std::shared_ptr<Codes>;
Codes(const std::shared_ptr<sw::redis::Redis> &redis_client):
_redis_client(redis_client){}
void append(const std::string &cid, const std::string &code,
const std::chrono::milliseconds &t = std::chrono::milliseconds(300000)) {
_redis_client->set(cid, code, t);
}
void remove(const std::string &cid) {
_redis_client->del(cid);
}
sw::redis::OptionalString code(const std::string &cid) {
return _redis_client->get(cid);
}
private:
std::shared_ptr<sw::redis::Redis> _redis_client;
};
}

46
common/dms.hpp Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <cstdlib>
#include <iostream>
#include <memory>
#include <alibabacloud/core/AlibabaCloud.h>
#include <alibabacloud/core/CommonRequest.h>
#include <alibabacloud/core/CommonClient.h>
#include <alibabacloud/core/CommonResponse.h>
#include "logger.hpp"
namespace bite_im{
class DMSClient {
public:
using ptr = std::shared_ptr<DMSClient>;
DMSClient(const std::string &access_key_id,
const std::string &access_key_secret) {
AlibabaCloud::InitializeSdk();
AlibabaCloud::ClientConfiguration configuration( "cn-chengdu" );
configuration.setConnectTimeout(1500);
configuration.setReadTimeout(4000);
AlibabaCloud::Credentials credential(access_key_id, access_key_secret);
_client = std::make_unique<AlibabaCloud::CommonClient>(credential, configuration);
}
~DMSClient() { AlibabaCloud::ShutdownSdk(); }
bool send(const std::string &phone, const std::string &code) {
AlibabaCloud::CommonRequest request(AlibabaCloud::CommonRequest::RequestPattern::RpcPattern);
request.setHttpMethod(AlibabaCloud::HttpRequest::Method::Post);
request.setDomain("dysmsapi.aliyuncs.com");
request.setVersion("2017-05-25");
request.setQueryParameter("Action", "SendSms");
request.setQueryParameter("SignName", "bitejiuyeke");
request.setQueryParameter("TemplateCode", "SMS_465324787");
request.setQueryParameter("PhoneNumbers", phone);
std::string param_code = "{\"code\":\"" + code + "\"}";
request.setQueryParameter("TemplateParam", param_code);
auto response = _client->commonResponse(request);
if (!response.isSuccess()) {
LOG_ERROR("短信验证码请求失败:{}", response.error().errorMessage());
return false;
}
return true;
}
private:
std::unique_ptr<AlibabaCloud::CommonClient> _client;
};
}

83
common/etcd.hpp Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <functional>
#include "logger.hpp"
namespace bite_im{
//服务注册客户端类
class Registry {
public:
using ptr = std::shared_ptr<Registry>;
Registry(const std::string &host):
_client(std::make_shared<etcd::Client>(host)) ,
_keep_alive(_client->leasekeepalive(3).get()),
_lease_id(_keep_alive->Lease()){}
~Registry() { _keep_alive->Cancel(); }
bool registry(const std::string &key, const std::string &val) {
auto resp = _client->put(key, val, _lease_id).get();
if (resp.is_ok() == false) {
LOG_ERROR("注册数据失败:{}", resp.error_message());
return false;
}
return true;
}
private:
std::shared_ptr<etcd::Client> _client;
std::shared_ptr<etcd::KeepAlive> _keep_alive;
uint64_t _lease_id;
};
//服务发现客户端类
class Discovery {
public:
using ptr = std::shared_ptr<Discovery>;
using NotifyCallback = std::function<void(std::string, std::string)>;
Discovery(const std::string &host,
const std::string &basedir,
const NotifyCallback &put_cb,
const NotifyCallback &del_cb):
_client(std::make_shared<etcd::Client>(host)) ,
_put_cb(put_cb), _del_cb(del_cb){
//先进行服务发现,先获取到当前已有的数据
auto resp = _client->ls(basedir).get();
if (resp.is_ok() == false) {
LOG_ERROR("获取服务信息数据失败:{}", resp.error_message());
}
int sz = resp.keys().size();
for (int i = 0; i < sz; ++i) {
if (_put_cb) _put_cb(resp.key(i), resp.value(i).as_string());
}
//然后进行事件监控,监控数据发生的改变并调用回调进行处理
_watcher = std::make_shared<etcd::Watcher>(*_client.get(), basedir,
std::bind(&Discovery::callback, this, std::placeholders::_1), true);
}
~Discovery() {
_watcher->Cancel();
}
private:
void callback(const etcd::Response &resp) {
if (resp.is_ok() == false) {
LOG_ERROR("收到一个错误的事件通知: {}", resp.error_message());
return;
}
for (auto const& ev : resp.events()) {
if (ev.event_type() == etcd::Event::EventType::PUT) {
if (_put_cb) _put_cb(ev.kv().key(), ev.kv().as_string());
LOG_DEBUG("新增服务:{}-{}", ev.kv().key(), ev.kv().as_string());
}else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
if (_del_cb) _del_cb(ev.prev_kv().key(), ev.prev_kv().as_string());
LOG_DEBUG("下线服务:{}-{}", ev.prev_kv().key(), ev.prev_kv().as_string());
}
}
}
private:
NotifyCallback _put_cb;
NotifyCallback _del_cb;
std::shared_ptr<etcd::Client> _client;
std::shared_ptr<etcd::Watcher> _watcher;
};
}

10508
common/httplib.h Normal file

File diff suppressed because it is too large Load Diff

255
common/icsearch.hpp Normal file
View File

@ -0,0 +1,255 @@
#pragma once
#include <elasticlient/client.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include <iostream>
#include <memory>
#include "logger.hpp"
namespace bite_im{
bool Serialize(const Json::Value &val, std::string &dst)
{
//先定义Json::StreamWriter 工厂类 Json::StreamWriterBuilder
Json::StreamWriterBuilder swb;
swb.settings_["emitUTF8"] = true;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
//通过Json::StreamWriter中的write接口进行序列化
std::stringstream ss;
int ret = sw->write(val, &ss);
if (ret != 0) {
std::cout << "Json反序列化失败\n";
return false;
}
dst = ss.str();
return true;
}
bool UnSerialize(const std::string &src, Json::Value &val)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
bool ret = cr->parse(src.c_str(), src.c_str() + src.size(), &val, &err);
if (ret == false) {
std::cout << "json反序列化失败: " << err << std::endl;
return false;
}
return true;
}
class ESIndex {
public:
ESIndex(std::shared_ptr<elasticlient::Client> &client,
const std::string &name,
const std::string &type = "_doc"):
_name(name), _type(type), _client(client) {
Json::Value analysis;
Json::Value analyzer;
Json::Value ik;
Json::Value tokenizer;
tokenizer["tokenizer"] = "ik_max_word";
ik["ik"] = tokenizer;
analyzer["analyzer"] = ik;
analysis["analysis"] = analyzer;
_index["settings"] = analysis;
}
ESIndex& append(const std::string &key,
const std::string &type = "text",
const std::string &analyzer = "ik_max_word",
bool enabled = true) {
Json::Value fields;
fields["type"] = type;
fields["analyzer"] = analyzer;
if (enabled == false ) fields["enabled"] = enabled;
_properties[key] = fields;
return *this;
}
bool create(const std::string &index_id = "default_index_id") {
Json::Value mappings;
mappings["dynamic"] = true;
mappings["properties"] = _properties;
_index["mappings"] = mappings;
std::string body;
bool ret = Serialize(_index, body);
if (ret == false) {
LOG_ERROR("索引序列化失败!");
return false;
}
// LOG_DEBUG("{}", body);
//2. 发起搜索请求
try {
auto rsp = _client->index(_name, _type, index_id, body);
if (rsp.status_code < 200 || rsp.status_code >= 300) {
LOG_ERROR("创建ES索引 {} 失败,响应状态码异常: {}", _name, rsp.status_code);
return false;
}
} catch(std::exception &e) {
LOG_ERROR("创建ES索引 {} 失败: {}", _name, e.what());
return false;
}
return true;
}
private:
std::string _name;
std::string _type;
Json::Value _properties;
Json::Value _index;
std::shared_ptr<elasticlient::Client> _client;
};
class ESInsert {
public:
ESInsert(std::shared_ptr<elasticlient::Client> &client,
const std::string &name,
const std::string &type = "_doc"):
_name(name), _type(type), _client(client)
{}
template<typename T>
ESInsert &append(const std::string &key, const T &val){
_item[key] = val;
return *this;
}
bool insert(const std::string id = "") {
std::string body;
bool ret = Serialize(_item, body);
if (ret == false) {
LOG_ERROR("索引序列化失败!");
return false;
}
LOG_DEBUG("{}", body);
//2. 发起搜索请求
try {
auto rsp = _client->index(_name, _type, id, body);
if (rsp.status_code < 200 || rsp.status_code >= 300) {
LOG_ERROR("新增数据 {} 失败,响应状态码异常: {}", body, rsp.status_code);
return false;
}
} catch(std::exception &e) {
LOG_ERROR("新增数据 {} 失败: {}", body, e.what());
return false;
}
return true;
}
private:
std::string _name;
std::string _type;
Json::Value _item;
std::shared_ptr<elasticlient::Client> _client;
};
class ESRemove {
public:
ESRemove(std::shared_ptr<elasticlient::Client> &client,
const std::string &name,
const std::string &type = "_doc"):
_name(name), _type(type), _client(client){}
bool remove(const std::string &id) {
try {
auto rsp = _client->remove(_name, _type, id);
if (rsp.status_code < 200 || rsp.status_code >= 300) {
LOG_ERROR("删除数据 {} 失败,响应状态码异常: {}", id, rsp.status_code);
return false;
}
} catch(std::exception &e) {
LOG_ERROR("删除数据 {} 失败: {}", id, e.what());
return false;
}
return true;
}
private:
std::string _name;
std::string _type;
std::shared_ptr<elasticlient::Client> _client;
};
class ESSearch {
public:
ESSearch(std::shared_ptr<elasticlient::Client> &client,
const std::string &name,
const std::string &type = "_doc"):
_name(name), _type(type), _client(client){}
ESSearch& append_must_not_terms(const std::string &key, const std::vector<std::string> &vals) {
Json::Value fields;
for (const auto& val : vals){
fields[key].append(val);
}
Json::Value terms;
terms["terms"] = fields;
_must_not.append(terms);
return *this;
}
ESSearch& append_should_match(const std::string &key, const std::string &val) {
Json::Value field;
field[key] = val;
Json::Value match;
match["match"] = field;
_should.append(match);
return *this;
}
ESSearch& append_must_term(const std::string &key, const std::string &val) {
Json::Value field;
field[key] = val;
Json::Value term;
term["term"] = field;
_must.append(term);
return *this;
}
ESSearch& append_must_match(const std::string &key, const std::string &val){
Json::Value field;
field[key] = val;
Json::Value match;
match["match"] = field;
_must.append(match);
return *this;
}
Json::Value search(){
Json::Value cond;
if (_must_not.empty() == false) cond["must_not"] = _must_not;
if (_should.empty() == false) cond["should"] = _should;
if (_must.empty() == false) cond["must"] = _must;
Json::Value query;
query["bool"] = cond;
Json::Value root;
root["query"] = query;
std::string body;
bool ret = Serialize(root, body);
if (ret == false) {
LOG_ERROR("索引序列化失败!");
return Json::Value();
}
LOG_DEBUG("{}", body);
//2. 发起搜索请求
cpr::Response rsp;
try {
rsp = _client->search(_name, _type, body);
if (rsp.status_code < 200 || rsp.status_code >= 300) {
LOG_ERROR("检索数据 {} 失败,响应状态码异常: {}", body, rsp.status_code);
return Json::Value();
}
} catch(std::exception &e) {
LOG_ERROR("检索数据 {} 失败: {}", body, e.what());
return Json::Value();
}
//3. 需要对响应正文进行反序列化
LOG_DEBUG("检索响应正文: [{}]", rsp.text);
Json::Value json_res;
ret = UnSerialize(rsp.text, json_res);
if (ret == false) {
LOG_ERROR("检索数据 {} 结果反序列化失败", rsp.text);
return Json::Value();
}
return json_res["hits"]["hits"];
}
private:
std::string _name;
std::string _type;
Json::Value _must_not;
Json::Value _should;
Json::Value _must;
std::shared_ptr<elasticlient::Client> _client;
};
}

34
common/logger.hpp Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>
#include <iostream>
// mode - 运行模式: true-发布模式; false调试模式
namespace bite_im{
std::shared_ptr<spdlog::logger> g_default_logger;
void init_logger(bool mode, const std::string &file, int32_t level)
{
if (mode == false) {
//如果是调试模式,则创建标准输出日志器,输出等级为最低
g_default_logger = spdlog::stdout_color_mt("default-logger");
g_default_logger->set_level(spdlog::level::level_enum::trace);
g_default_logger->flush_on(spdlog::level::level_enum::trace);
}else {
//否则是发布模式,则创建文件输出日志器,输出等级根据参数而定
g_default_logger = spdlog::basic_logger_mt("default-logger", file);
g_default_logger->set_level((spdlog::level::level_enum)level);
g_default_logger->flush_on((spdlog::level::level_enum)level);
}
g_default_logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v");
}
#define LOG_TRACE(format, ...) bite_im::g_default_logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(format, ...) bite_im::g_default_logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(format, ...) bite_im::g_default_logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(format, ...) bite_im::g_default_logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) bite_im::g_default_logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_FATAL(format, ...) bite_im::g_default_logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
}

30
common/mysql.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <memory> // std::auto_ptr
#include <cstdlib> // std::exit
#include <iostream>
#include <odb/database.hxx>
#include <odb/mysql/database.hxx>
#include "logger.hpp"
// 用户注册, 用户登录, 验证码获取, 手机号注册,手机号登录, 获取用户信息, 用户信息修改
// 用信息新增, 通过昵称获取用户信息,通过手机号获取用户信息, 通过用户ID获取用户信息 通过多个用户ID获取多个用户信息信息修改
namespace bite_im {
class ODBFactory {
public:
static std::shared_ptr<odb::core::database> create(
const std::string &user,
const std::string &pswd,
const std::string &host,
const std::string &db,
const std::string &cset,
int port,
int conn_pool_count) {
std::unique_ptr<odb::mysql::connection_pool_factory> cpf(
new odb::mysql::connection_pool_factory(conn_pool_count, 0));
auto res = std::make_shared<odb::mysql::database>(user, pswd,
db, host, port, "", cset, 0, std::move(cpf));
return res;
}
};
}

71
common/mysql_apply.hpp Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include "mysql.hpp"
#include "friend_apply.hxx"
#include "friend_apply-odb.hxx"
namespace bite_im {
class FriendApplyTable {
public:
using ptr = std::shared_ptr<FriendApplyTable>;
FriendApplyTable(const std::shared_ptr<odb::core::database> &db) : _db(db){}
bool insert(FriendApply &ev) {
try {
odb::transaction trans(_db->begin());
_db->persist(ev);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增好友申请事件失败 {}-{}:{}", ev.user_id(), ev.peer_id(), e.what());
return false;
}
return true;
}
bool exists(const std::string &uid, const std::string &pid) {
bool flag = false;
try {
typedef odb::query<FriendApply> query;
typedef odb::result<FriendApply> result;
odb::transaction trans(_db->begin());
result r(_db->query<FriendApply>(query::user_id == uid && query::peer_id == pid));
LOG_DEBUG("{} - {} 好友事件数量:{}", uid, pid, r.size());
flag = !r.empty();
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取好友申请事件失败:{}-{}-{}", uid, pid, e.what());
}
return flag;
}
bool remove(const std::string &uid, const std::string &pid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<FriendApply> query;
typedef odb::result<FriendApply> result;
_db->erase_query<FriendApply>(query::user_id == uid && query::peer_id == pid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除好友申请事件失败 {}-{}:{}", uid, pid, e.what());
return false;
}
return true;
}
//获取当前指定用户的 所有好友申请者ID
std::vector<std::string> applyUsers(const std::string &uid){
std::vector<std::string> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<FriendApply> query;
typedef odb::result<FriendApply> result;
//当前的uid是被申请者的用户ID
result r(_db->query<FriendApply>(query::peer_id == uid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(i->user_id());
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过用户{}的好友申请者失败:{}", uid, e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}

View File

@ -0,0 +1,118 @@
#pragma once
#include "mysql.hpp"
#include "chat_session.hxx"
#include "chat_session-odb.hxx"
#include "mysql_chat_session_member.hpp"
namespace bite_im {
class ChatSessionTable {
public:
using ptr = std::shared_ptr<ChatSessionTable>;
ChatSessionTable(const std::shared_ptr<odb::core::database> &db):_db(db){}
bool insert(ChatSession &cs) {
try {
odb::transaction trans(_db->begin());
_db->persist(cs);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增会话失败 {}:{}", cs.chat_session_name(), e.what());
return false;
}
return true;
}
bool remove(const std::string &ssid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSession> query;
typedef odb::result<ChatSession> result;
_db->erase_query<ChatSession>(query::chat_session_id == ssid);
typedef odb::query<ChatSessionMember> mquery;
_db->erase_query<ChatSessionMember>(mquery::session_id == ssid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除会话失败 {}:{}", ssid, e.what());
return false;
}
return true;
}
bool remove(const std::string &uid, const std::string &pid) {
//单聊会话的删除,-- 根据单聊会话的两个成员
try {
odb::transaction trans(_db->begin());
typedef odb::query<SingleChatSession> query;
typedef odb::result<SingleChatSession> result;
auto res = _db->query_one<SingleChatSession>(
query::csm1::user_id == uid &&
query::csm2::user_id == pid &&
query::css::chat_session_type == ChatSessionType::SINGLE);
std::string cssid = res->chat_session_id;
typedef odb::query<ChatSession> cquery;
_db->erase_query<ChatSession>(cquery::chat_session_id == cssid);
typedef odb::query<ChatSessionMember> mquery;
_db->erase_query<ChatSessionMember>(mquery::session_id == cssid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除会话失败 {}-{}:{}", uid, pid, e.what());
return false;
}
return true;
}
std::shared_ptr<ChatSession> select(const std::string &ssid) {
std::shared_ptr<ChatSession> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSession> query;
typedef odb::result<ChatSession> result;
res.reset(_db->query_one<ChatSession>(query::chat_session_id == ssid));
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过会话ID获取会话信息失败 {}:{}", ssid, e.what());
}
return res;
}
std::vector<SingleChatSession> singleChatSession(const std::string &uid) {
std::vector<SingleChatSession> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<SingleChatSession> query;
typedef odb::result<SingleChatSession> result;
//当前的uid是被申请者的用户ID
result r(_db->query<SingleChatSession>(
query::css::chat_session_type == ChatSessionType::SINGLE &&
query::csm1::user_id == uid &&
query::csm2::user_id != query::csm1::user_id));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取用户 {} 的单聊会话失败:{}", uid, e.what());
}
return res;
}
std::vector<GroupChatSession> groupChatSession(const std::string &uid) {
std::vector<GroupChatSession> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<GroupChatSession> query;
typedef odb::result<GroupChatSession> result;
//当前的uid是被申请者的用户ID
result r(_db->query<GroupChatSession>(
query::css::chat_session_type == ChatSessionType::GROUP &&
query::csm::user_id == uid ));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取用户 {} 的群聊会话失败:{}", uid, e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}

View File

@ -0,0 +1,87 @@
#pragma once
#include "mysql.hpp"
#include "chat_session_member.hxx"
#include "chat_session_member-odb.hxx"
namespace bite_im {
class ChatSessionMemeberTable {
public:
using ptr = std::shared_ptr<ChatSessionMemeberTable>;
ChatSessionMemeberTable(const std::shared_ptr<odb::core::database> &db):_db(db){}
//单个会话成员的新增 --- ssid & uid
bool append(ChatSessionMember &csm) {
try {
odb::transaction trans(_db->begin());
_db->persist(csm);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增单会话成员失败 {}-{}:{}",
csm.session_id(), csm.user_id(), e.what());
return false;
}
return true;
}
bool append(std::vector<ChatSessionMember> &csm_lists) {
try {
odb::transaction trans(_db->begin());
for (auto &csm : csm_lists) {
_db->persist(csm);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增多会话成员失败 {}-{}:{}",
csm_lists[0].session_id(), csm_lists.size(), e.what());
return false;
}
return true;
}
//删除指定会话中的指定成员 -- ssid & uid
bool remove(ChatSessionMember &csm) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSessionMember> query;
typedef odb::result<ChatSessionMember> result;
_db->erase_query<ChatSessionMember>(query::session_id == csm.session_id() &&
query::user_id == csm.user_id());
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除单会话成员失败 {}-{}:{}",
csm.session_id(), csm.user_id(), e.what());
return false;
}
return true;
}
//删除会话的所有成员信息
bool remove(const std::string &ssid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSessionMember> query;
typedef odb::result<ChatSessionMember> result;
_db->erase_query<ChatSessionMember>(query::session_id == ssid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除会话所有成员失败 {}:{}", ssid, e.what());
return false;
}
return true;
}
std::vector<std::string> members(const std::string &ssid) {
std::vector<std::string> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSessionMember> query;
typedef odb::result<ChatSessionMember> result;
result r(_db->query<ChatSessionMember>(query::session_id == ssid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(i->user_id());
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取会话成员失败:{}-{}", ssid, e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}

86
common/mysql_message.hpp Normal file
View File

@ -0,0 +1,86 @@
#include "mysql.hpp"
#include "message.hxx"
#include "message-odb.hxx"
namespace bite_im {
class MessageTable {
public:
using ptr = std::shared_ptr<MessageTable>;
MessageTable(const std::shared_ptr<odb::core::database> &db): _db(db){}
~MessageTable(){}
bool insert(Message &msg) {
try {
odb::transaction trans(_db->begin());
_db->persist(msg);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增消息失败 {}:{}", msg.message_id(),e.what());
return false;
}
return true;
}
bool remove(const std::string &ssid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<Message> query;
typedef odb::result<Message> result;
_db->erase_query<Message>(query::session_id == ssid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除会话所有消息失败 {}:{}", ssid, e.what());
return false;
}
return true;
}
std::vector<Message> recent(const std::string &ssid, int count) {
std::vector<Message> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<Message> query;
typedef odb::result<Message> result;
//本次查询是以ssid作为过滤条件然后进行以时间字段进行逆序通过limit
// session_id='xx' order by create_time desc limit count;
std::stringstream cond;
cond << "session_id='" << ssid << "' ";
cond << "order by create_time desc limit " << count;
result r(_db->query<Message>(cond.str()));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
std::reverse(res.begin(), res.end());
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取最近消息失败:{}-{}-{}", ssid, count, e.what());
}
return res;
}
std::vector<Message> range(const std::string &ssid,
boost::posix_time::ptime &stime,
boost::posix_time::ptime &etime) {
std::vector<Message> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<Message> query;
typedef odb::result<Message> result;
//获取指定会话指定时间段的信息
result r(_db->query<Message>(query::session_id == ssid &&
query::create_time >= stime &&
query::create_time <= etime));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取区间消息失败:{}-{}:{}-{}", ssid,
boost::posix_time::to_simple_string(stime),
boost::posix_time::to_simple_string(etime), e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}

78
common/mysql_relation.hpp Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include "mysql.hpp"
#include "relation.hxx"
#include "relation-odb.hxx"
namespace bite_im {
class RelationTable {
public:
using ptr = std::shared_ptr<RelationTable>;
RelationTable(const std::shared_ptr<odb::core::database> &db) : _db(db){}
//新增关系信息
bool insert(const std::string &uid, const std::string &pid) {
//{1,2} {2,1}
try {
Relation r1(uid, pid);
Relation r2(pid, uid);
odb::transaction trans(_db->begin());
_db->persist(r1);
_db->persist(r2);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增用户好友关系信息失败 {}-{}:{}", uid, pid, e.what());
return false;
}
return true;
}
//移除关系信息
bool remove(const std::string &uid, const std::string &pid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<Relation> query;
typedef odb::result<Relation> result;
_db->erase_query<Relation>(query::user_id == uid && query::peer_id == pid);
_db->erase_query<Relation>(query::user_id == pid && query::peer_id == uid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除好友关系信息失败 {}-{}:{}", uid, pid, e.what());
return false;
}
return true;
}
//判断关系是否存在
bool exists(const std::string &uid, const std::string &pid) {
typedef odb::query<Relation> query;
typedef odb::result<Relation> result;
result r;
bool flag = false;
try {
odb::transaction trans(_db->begin());
r = _db->query<Relation>(query::user_id == uid && query::peer_id == pid);
flag = !r.empty();
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取用户好友关系失败:{}-{}-{}", uid, pid, e.what());
}
return flag;
}
//获取指定用户的好友ID
std::vector<std::string> friends(const std::string &uid) {
std::vector<std::string> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<Relation> query;
typedef odb::result<Relation> result;
result r(_db->query<Relation>(query::user_id == uid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(i->peer_id());
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过用户-{}的所有好友ID失败:{}", uid, e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}

102
common/mysql_user.hpp Normal file
View File

@ -0,0 +1,102 @@
#include "mysql.hpp"
#include "user.hxx"
#include "user-odb.hxx"
namespace bite_im {
class UserTable {
public:
using ptr = std::shared_ptr<UserTable>;
UserTable(const std::shared_ptr<odb::core::database> &db):_db(db){}
bool insert(const std::shared_ptr<User> &user) {
try {
odb::transaction trans(_db->begin());
_db->persist(*user);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增用户失败 {}:{}", user->nickname(),e.what());
return false;
}
return true;
}
bool update(const std::shared_ptr<User> &user) {
try {
odb::transaction trans(_db->begin());
_db->update(*user);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("更新用户失败 {}:{}", user->nickname(), e.what());
return false;
}
return true;
}
std::shared_ptr<User> select_by_nickname(const std::string &nickname) {
std::shared_ptr<User> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<User> query;
typedef odb::result<User> result;
res.reset(_db->query_one<User>(query::nickname == nickname));
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过昵称查询用户失败 {}:{}", nickname, e.what());
}
return res;
}
std::shared_ptr<User> select_by_phone(const std::string &phone) {
std::shared_ptr<User> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<User> query;
typedef odb::result<User> result;
res.reset(_db->query_one<User>(query::phone == phone));
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过手机号查询用户失败 {}:{}", phone, e.what());
}
return res;
}
std::shared_ptr<User> select_by_id(const std::string &user_id) {
std::shared_ptr<User> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<User> query;
typedef odb::result<User> result;
res.reset(_db->query_one<User>(query::user_id == user_id));
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过用户ID查询用户失败 {}:{}", user_id, e.what());
}
return res;
}
std::vector<User> select_multi_users(const std::vector<std::string> &id_list) {
// select * from user where id in ('id1', 'id2', ...)
if (id_list.empty()) {
return std::vector<User>();
}
std::vector<User> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<User> query;
typedef odb::result<User> result;
std::stringstream ss;
ss << "user_id in (";
for (const auto &id : id_list) {
ss << "'" << id << "',";
}
std::string condition = ss.str();
condition.pop_back();
condition += ")";
result r(_db->query<User>(condition));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过用户ID批量查询用户失败:{}", e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}

106
common/rabbitmq.hpp Normal file
View File

@ -0,0 +1,106 @@
#pragma once
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>
#include <iostream>
#include <functional>
#include "logger.hpp"
namespace bite_im{
class MQClient {
public:
using MessageCallback = std::function<void(const char*, size_t)>;
using ptr = std::shared_ptr<MQClient>;
MQClient(const std::string &user,
const std::string passwd,
const std::string host) {
_loop = EV_DEFAULT;
_handler = std::make_unique<AMQP::LibEvHandler>(_loop);
//amqp://root:123456@127.0.0.1:5672/
std::string url = "amqp://" + user + ":" + passwd + "@" + host + "/";
AMQP::Address address(url);
_connection = std::make_unique<AMQP::TcpConnection>(_handler.get(), address);
_channel = std::make_unique<AMQP::TcpChannel>(_connection.get());
_loop_thread = std::thread([this]() {
ev_run(_loop, 0);
});
}
~MQClient() {
ev_async_init(&_async_watcher, watcher_callback);
ev_async_start(_loop, &_async_watcher);
ev_async_send(_loop, &_async_watcher);
_loop_thread.join();
_loop = nullptr;
}
void declareComponents(const std::string &exchange,
const std::string &queue,
const std::string &routing_key = "routing_key",
AMQP::ExchangeType echange_type = AMQP::ExchangeType::direct) {
_channel->declareExchange(exchange, echange_type)
.onError([](const char *message) {
LOG_ERROR("声明交换机失败:{}", message);
exit(0);
})
.onSuccess([exchange](){
LOG_ERROR("{} 交换机创建成功!", exchange);
});
_channel->declareQueue(queue)
.onError([](const char *message) {
LOG_ERROR("声明队列失败:{}", message);
exit(0);
})
.onSuccess([queue](){
LOG_ERROR("{} 队列创建成功!", queue);
});
//6. 针对交换机和队列进行绑定
_channel->bindQueue(exchange, queue, routing_key)
.onError([exchange, queue](const char *message) {
LOG_ERROR("{} - {} 绑定失败:", exchange, queue);
exit(0);
})
.onSuccess([exchange, queue, routing_key](){
LOG_ERROR("{} - {} - {} 绑定成功!", exchange, queue, routing_key);
});
}
bool publish(const std::string &exchange,
const std::string &msg,
const std::string &routing_key = "routing_key") {
LOG_DEBUG("向交换机 {}-{} 发布消息!", exchange, routing_key);
bool ret = _channel->publish(exchange, routing_key, msg);
if (ret == false) {
LOG_ERROR("{} 发布消息失败:", exchange);
return false;
}
return true;
}
void consume(const std::string &queue, const MessageCallback &cb) {
LOG_DEBUG("开始订阅 {} 队列消息!", queue);
_channel->consume(queue, "consume-tag") //返回值 DeferredConsumer
.onReceived([this, cb](const AMQP::Message &message,
uint64_t deliveryTag,
bool redelivered) {
cb(message.body(), message.bodySize());
_channel->ack(deliveryTag);
})
.onError([queue](const char *message){
LOG_ERROR("订阅 {} 队列消息失败: {}", queue, message);
exit(0);
});
}
private:
static void watcher_callback(struct ev_loop *loop, ev_async *watcher, int32_t revents) {
ev_break(loop, EVBREAK_ALL);
}
private:
struct ev_async _async_watcher;
struct ev_loop *_loop;
std::unique_ptr<AMQP::LibEvHandler> _handler;
std::unique_ptr<AMQP::TcpConnection> _connection;
std::unique_ptr<AMQP::TcpChannel> _channel;
std::thread _loop_thread;
};
}

262
common/sendemail.hpp Normal file
View File

@ -0,0 +1,262 @@
#pragma once
#include <iostream>
#include <string>
#include <chrono>
#include <random>
#include <ctime>
#include <cstring> // for memset
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/buffer.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
class SendEmail
{
// 请确保在链接器设置中加入 libssl.lib 与 libcrypto.lib或通过 vcpkg 自动集成)
public:
using ptr = std::shared_ptr<SendEmail>;
// 辅助函数:通过 SSL 发送数据,并检查返回值
bool sendSSL(SSL* ssl, const std::string& msg, const char* label)
{
int bytesSent = SSL_write(ssl, msg.c_str(), static_cast<int>(msg.length()));
if (bytesSent <= 0) {
std::cerr << "Failed to send " << label << " message via SSL." << std::endl;
ERR_print_errors_fp(stderr);
return false;
}
return true;
}
std::string base64_encode(const std::string& input)
{
BIO* bmem = BIO_new(BIO_s_mem());
BIO* b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
b64 = BIO_push(b64, bmem);
BIO_write(b64, input.c_str(), static_cast<int>(input.length()));
BIO_flush(b64);
BUF_MEM* bptr;
BIO_get_mem_ptr(b64, &bptr);
std::string result(bptr->data, bptr->length);
BIO_free_all(b64);
return result;
}
// 辅助函数:通过 SSL 接收数据,并打印输出
bool recvSSL(SSL* ssl, char* buff, int buffSize, const char* label)
{
int bytesReceived = SSL_read(ssl, buff, buffSize);
if (bytesReceived <= 0) {
std::cerr << "Failed to receive " << label << " message via SSL." << std::endl;
ERR_print_errors_fp(stderr);
return false;
}
buff[bytesReceived] = '\0';
std::cout << label << " response: " << buff << std::endl;
return true;
}
/// <summary>
/// 发送邮件到指定邮箱
/// </summary>
/// <param name="收件人邮箱"></param>
/// <returns>成功 = true失败 = false</returns>
bool SEND_email(const std::string& email)
{
// 1. DNS解析 smtp.qq.com使用端口 465
struct addrinfo hints = {};
hints.ai_family = AF_INET; // IPv4
hints.ai_socktype = SOCK_STREAM; // TCP
struct addrinfo* addrResult = nullptr;
int ret = getaddrinfo("smtp.qq.com", "465", &hints, &addrResult);
if (ret != 0 || addrResult == nullptr) {
std::cerr << "DNS resolution failed: " << gai_strerror(ret) << std::endl;
return false;
}
// 2. 建立 TCP 连接
int sock = socket(addrResult->ai_family, addrResult->ai_socktype, addrResult->ai_protocol);
if (sock == -1) {
std::cerr << "Socket creation failed!" << std::endl;
freeaddrinfo(addrResult);
return false;
}
if (connect(sock, addrResult->ai_addr, addrResult->ai_addrlen) != 0) {
std::cerr << "Connection failed!" << std::endl;
close(sock);
freeaddrinfo(addrResult);
return false;
}
freeaddrinfo(addrResult);
// 3. 初始化 OpenSSL
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
const SSL_METHOD* method = TLS_client_method();
SSL_CTX* sslCtx = SSL_CTX_new(method);
if (!sslCtx) {
std::cerr << "Unable to create SSL context." << std::endl;
close(sock);
return false;
}
SSL* ssl = SSL_new(sslCtx);
if (!ssl) {
std::cerr << "Unable to create SSL structure." << std::endl;
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 绑定 socket 到 SSL
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
std::cerr << "SSL_connect failed." << std::endl;
ERR_print_errors_fp(stderr);
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 4. 读取服务器欢迎信息
char buff[2048] = { 0 };
if (!recvSSL(ssl, buff, sizeof(buff) - 1, "Server")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 5. 发送 EHLO 命令
std::string sendMsg = "EHLO localhost\r\n";
if (!sendSSL(ssl, sendMsg, "EHLO") || !recvSSL(ssl, buff, sizeof(buff) - 1, "EHLO")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 6. 认证流程AUTH LOGIN
sendMsg = "AUTH LOGIN\r\n";
if (!sendSSL(ssl, sendMsg, "AUTH") || !recvSSL(ssl, buff, sizeof(buff) - 1, "AUTH")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 7. 发送用户名Base64编码后的
std::string username = base64_encode("zxiao_xin@qq.com") + "\r\n";
if (!sendSSL(ssl, username, "Username") || !recvSSL(ssl, buff, sizeof(buff) - 1, "Username")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 8. 发送密码Base64编码后的
std::string password = base64_encode("ydfslvabdryvejai") + "\r\n";
if (!sendSSL(ssl, password, "Password") || !recvSSL(ssl, buff, sizeof(buff) - 1, "Password")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 9. 设置发件人
sendMsg = "MAIL FROM:<zxiao_xin@qq.com>\r\n";
if (!sendSSL(ssl, sendMsg, "MAIL FROM") || !recvSSL(ssl, buff, sizeof(buff) - 1, "MAIL FROM")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 10. 设置收件人
sendMsg = "RCPT TO:<" + email + ">\r\n";
if (!sendSSL(ssl, sendMsg, "RCPT TO") || !recvSSL(ssl, buff, sizeof(buff) - 1, "RCPT TO")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 检查回复是否为成功代码
std::string s = buff;
s = s.substr(0, 3);
if (s != "250") {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 11. 命令 DATA
sendMsg = "DATA\r\n";
if (!sendSSL(ssl, sendMsg, "DATA") || !recvSSL(ssl, buff, sizeof(buff) - 1, "DATA")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 12. 生成验证码
std::default_random_engine engine(static_cast<unsigned int>(std::time(nullptr)));
std::uniform_int_distribution<int> distribution(10000, 99999);
std::string verificationCode = std::to_string(distribution(engine));
_verifyCode = verificationCode;
// 13. 构造邮件头和正文
sendMsg =
"From: \"Mysterious系统\" <zxiao_xin@qq.com>\r\n"
"Reply-To: \"请勿回复\" <no-reply@qq.com>\r\n"
"To: <" + email + ">\r\n"
"Subject: Mysterious验证码\r\n"
"Content-Type: text/plain; charset=UTF-8\r\n"
"\r\n"
"这是您的验证码: " + verificationCode + "\r\n"
"用于身份验证,请勿泄露。如非本人操作,请忽略此邮件。\r\n"
"\r\n.\r\n";
if (!sendSSL(ssl, sendMsg, "Email DATA") || !recvSSL(ssl, buff, sizeof(buff) - 1, "DATA send")) {
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return false;
}
// 14. 命令 QUIT
sendMsg = "QUIT\r\n";
sendSSL(ssl, sendMsg, "QUIT");
recvSSL(ssl, buff, sizeof(buff) - 1, "QUIT");
// 15. 清理资源
SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(sslCtx);
close(sock);
return true;
}
std::string getVerifyCode() {
return _verifyCode;
}
private:
// std::string _email;
std::string _verifyCode;
};

86
common/utils.hpp Normal file
View File

@ -0,0 +1,86 @@
//实现项目中一些公共的工具类接口
//1. 生成一个唯一ID的接口
//2. 文件的读写操作接口
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <atomic>
#include <random>
#include <iomanip>
#include "logger.hpp"
namespace bite_im {
std::string uuid() {
//生成一个由16位随机字符组成的字符串作为唯一ID
// 1. 生成6个0~255之间的随机数字(1字节-转换为16进制字符)--生成12位16进制字符
std::random_device rd;//实例化设备随机数对象-用于生成设备随机数
std::mt19937 generator(rd());//以设备随机数为种子,实例化伪随机数对象
std::uniform_int_distribution<int> distribution(0,255); //限定数据范围
std::stringstream ss;
for (int i = 0; i < 6; i++) {
if (i == 2) ss << "-";
ss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator);
}
ss << "-";
// 2. 通过一个静态变量生成一个2字节的编号数字--生成4位16进制数字字符
static std::atomic<short> idx(0);
short tmp = idx.fetch_add(1);
ss << std::setw(4) << std::setfill('0') << std::hex << tmp;
return ss.str();
}
std::string vcode() {
std::random_device rd;//实例化设备随机数对象-用于生成设备随机数
std::mt19937 generator(rd());//以设备随机数为种子,实例化伪随机数对象
std::uniform_int_distribution<int> distribution(0,9); //限定数据范围
std::stringstream ss;
for (int i = 0; i < 4; i++) {
ss << distribution(generator);
}
return ss.str();
}
bool readFile(const std::string &filename, std::string &body){
//实现读取一个文件的所有数据放入body中
std::ifstream ifs(filename, std::ios::binary | std::ios::in);
if (ifs.is_open() == false) {
LOG_ERROR("打开文件 {} 失败!", filename);
return false;
}
ifs.seekg(0, std::ios::end);//跳转到文件末尾
size_t flen = ifs.tellg(); //获取当前偏移量-- 文件大小
ifs.seekg(0, std::ios::beg);//跳转到文件起始
body.resize(flen);
ifs.read(&body[0], flen);
if (ifs.good() == false) {
LOG_ERROR("读取文件 {} 数据失败!", filename);
ifs.close();
return false;
}
ifs.close();
return true;
}
bool writeFile(const std::string &filename, const std::string &body){
//实现将body中的数据写入filename对应的文件中
std::ofstream ofs(filename, std::ios::out | std::ios::binary | std::ios::trunc);
if (ofs.is_open() == false) {
LOG_ERROR("打开文件 {} 失败!", filename);
return false;
}
ofs.write(body.c_str(), body.size());
if (ofs.good() == false) {
LOG_ERROR("读取文件 {} 数据失败!", filename);
ofs.close();
return false;
}
ofs.close();
return true;
}
}