mirror of
https://gitee.com/Zhaoxin59/my-chat_-server.git
synced 2026-02-14 01:21:50 +08:00
update
This commit is contained in:
25
common/asr.hpp
Normal file
25
common/asr.hpp
Normal 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
151
common/channel.hpp
Normal 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
164
common/data_es.hpp
Normal 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
75
common/data_redis.hpp
Normal 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
46
common/dms.hpp
Normal 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
83
common/etcd.hpp
Normal 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
10508
common/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
255
common/icsearch.hpp
Normal file
255
common/icsearch.hpp
Normal 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
34
common/logger.hpp
Normal 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
30
common/mysql.hpp
Normal 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
71
common/mysql_apply.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
118
common/mysql_chat_session.hpp
Normal file
118
common/mysql_chat_session.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
87
common/mysql_chat_session_member.hpp
Normal file
87
common/mysql_chat_session_member.hpp
Normal 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
86
common/mysql_message.hpp
Normal 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
78
common/mysql_relation.hpp
Normal 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
102
common/mysql_user.hpp
Normal 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
106
common/rabbitmq.hpp
Normal 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
262
common/sendemail.hpp
Normal 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
86
common/utils.hpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user