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

173
.gitignore vendored Normal file
View File

@ -0,0 +1,173 @@
# ================================
# 构建系统和编译产物
# ================================
# CMake 构建目录
*/build/
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.cmake
CMakeOutput.log
CMakeRuleHashes.txt
cmake.check_cache
CMakeDirectoryInformation.cmake
CMakeScratch/
CMakeScripts/
*.cmake.*
pkgRedirects/
progress.marks
TargetDirectories.txt
# 编译产物
*.o
*.o.d
*.obj
*.exe
*.out
*.app
# 协议缓冲区生成的文件
proto/generated/
# ODB 生成的数据库文件
*-odb.cxx
*-odb.hxx
*-odb.ixx
*.sql
# ================================
# 可执行文件和二进制文件
# ================================
# 服务端可执行文件
*/file_server
*/file_client
*/friend_server
*/friend_client
*/gateway_server
*/message_server
*/speech_server
*/speech_client
*/transmite_server
*/transmite_client
*/trans_user_client
*/user_server
*/user_client
# 测试可执行文件
*/test/*/main
*/test/mysql_test/main
*/test/es_test/main
*/test/redis_test/main
# 其他二进制文件
*/nc
*/make_file_download
*/base_download_file1
*/file_download_file2
# ================================
# 运行时数据和日志
# ================================
# 中间件数据
middle/data/
middle/elasticsearch/
middle/etcd/
middle/logs/
middle/mysql/
middle/rabbitmq/
middle/redis/
# 文件服务数据
file/build/data/
# 语音服务数据
speech/build/16k.pcm
speech/test/16k.pcm
# 日志文件
*.log
*.log.*
log/
logs/
# ================================
# 依赖库文件
# ================================
# 本地依赖库
*/depends/
*.so
*.so.*
*.dylib
*.dll
# ================================
# 临时文件和缓存
# ================================
*.tmp
*.temp
.tmp/
.cache/
*.swp
*.swo
*~
# CMake 临时文件
*/CMakeFiles/*/CompilerIdC/a.out
*/CMakeFiles/*/CompilerIdCXX/a.out
*/CMakeFiles/*/CompilerIdC/tmp/
*/CMakeFiles/*/CompilerIdCXX/tmp/
# ================================
# 系统文件
# ================================
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
Desktop.ini
# ================================
# IDE 和编辑器文件
# ================================
# VS Code
.vscode/
*.code-workspace
# Vim
*.swp
*.swo
*~
# ================================
# 协议缓冲区源文件(保留 .proto忽略生成的 .pb.*
# ================================
# 注意:保留 .proto 文件,但忽略生成的文件
*.pb.cc
*.pb.h
# ================================
# 项目特定文件
# ================================
# 树状结构输出文件
tree.txt
# 下载的文件
base_download_file1
file_download_file2
# 测试目录中的构建文件
*/test/*/Makefile
*/test/*/*.o
*/test/*/*.o.d
# ================================
# Docker 相关(可选)
# ================================
# 如果您使用 Docker可以取消注释以下行
# .dockerignore
# !.dockerignore
# docker-compose.override.yml

10
CMakeLists.txt Executable file
View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.1.3)
project(all-test)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/message)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/user)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/file)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/speech)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/transmite)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/friend)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/gateway)
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR})

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;
}
}

11
conf/file_server.conf Normal file
View File

@ -0,0 +1,11 @@
-run_mode=true
-log_file=/im/logs/file.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-base_service=/service
-instance_name=/file_service/instance
-access_host=10.0.0.235:10002
-storage_path=/im/data/
-listen_port=10002
-rpc_timeout=-1
-rpc_threads=1

20
conf/friend_server.conf Normal file
View File

@ -0,0 +1,20 @@
-run_mode=true
-log_file=/im/logs/friend.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/friend_service/instance
-access_host=10.0.0.235:10006
-listen_port=10006
-rpc_timeout=-1
-rpc_threads=1
-base_service=/service
-user_service=/service/user_service
-message_service=/service/message_service
-es_host=http://10.0.0.235:9200/
-mysql_host=10.0.0.235
-mysql_user=root
-mysql_pswd=123456
-mysql_db=bite_im
-mysql_cset=utf8
-mysql_port=0
-mysql_pool_count=4

17
conf/gateway_server.conf Normal file
View File

@ -0,0 +1,17 @@
-run_mode=true
-log_file=/im/logs/gateway.log
-log_level=0
-http_listen_port=9000
-websocket_listen_port=9001
-registry_host=http://10.0.0.235:2379
-base_service=/service
-file_service=/service/file_service
-friend_service=/service/friend_service
-message_service=/service/message_service
-user_service=/service/user_service
-speech_service=/service/speech_service
-transmite_service=/service/transmite_service
-redis_host=10.0.0.235
-redis_port=6379
-redis_db=0
-redis_keep_alive=true

26
conf/message_server.conf Normal file
View File

@ -0,0 +1,26 @@
-run_mode=true
-log_file=/im/logs/message.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/message_service/instance
-access_host=10.0.0.235:10005
-listen_port=10005
-rpc_timeout=-1
-rpc_threads=1
-base_service=/service
-user_service=/service/user_service
-file_service=/service/file_service
-es_host=http://10.0.0.235:9200/
-mysql_host=10.0.0.235
-mysql_user=root
-mysql_pswd=123456
-mysql_db=bite_im
-mysql_cset=utf8
-mysql_port=0
-mysql_pool_count=4
-mq_user=root
-mq_pswd=123456
-mq_host=10.0.0.235:5672
-mq_msg_exchange=msg_exchange
-mq_msg_queue=msg_queue
-mq_msg_binding_key=msg_queue

13
conf/speech_server.conf Normal file
View File

@ -0,0 +1,13 @@
-run_mode=true
-log_file=/im/logs/speech.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/speech_service/instance
-access_host=10.0.0.235:10001
-base_service=/service
-listen_port=10001
-rpc_timeout=-1
-rpc_threads=1
-app_id=60694095
-api_key=PWn6zlsxym8VwpBW8Or4PPGe
-secret_key=Bl0mn74iyAkr3FzCo5TZV7lBq7NYoms9

View File

@ -0,0 +1,24 @@
-run_mode=true
-log_file=/im/logs/transmite.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/transmite_service/instance
-access_host=10.0.0.235:10004
-listen_port=10004
-rpc_timeout=-1
-rpc_threads=1
-base_service=/service
-user_service=/service/user_service
-mysql_host=10.0.0.235
-mysql_user=root
-mysql_pswd=123456
-mysql_db=bite_im
-mysql_cset=utf8
-mysql_port=0
-mysql_pool_count=4
-mq_user=root
-mq_pswd=123456
-mq_host=10.0.0.235:5672
-mq_msg_exchange=msg_exchange
-mq_msg_queue=msg_queue
-mq_msg_binding_key=msg_queue

25
conf/user_server.conf Normal file
View File

@ -0,0 +1,25 @@
-run_mode=true
-log_file=/im/logs/user.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/user_service/instance
-access_host=10.0.0.235:10003
-listen_port=10003
-rpc_timeout=-1
-rpc_threads=1
-base_service=/service
-file_service=/service/file_service
-es_host=http://10.0.0.235:9200/
-mysql_host=10.0.0.235
-mysql_user=root
-mysql_pswd=123456
-mysql_db=bite_im
-mysql_cset=utf8
-mysql_port=0
-mysql_pool_count=4
-redis_host=10.0.0.235
-redis_port=6379
-redis_db=0
-redis_keep_alive=true
-dms_key_id=LTAI5t6NF7vt499UeqYX6LB9
-dms_key_secret=5hx1qvpXHDKfQDk73aJs6j53Q8KcF2

34
depends.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
#传递两个参数:
# 1. 可执行程序的路径名
# 2. 目录名称 --- 将这个程序的依赖库拷贝到指定目录下
declare depends
get_depends() {
depends=$(ldd $1 | awk '{if (match($3,"/")){print $3}}')
#mkdir $2
cp -Lr $depends $2
}
get_depends ./gateway/build/gateway_server ./gateway/depends
get_depends ./file/build/file_server ./file/depends
get_depends ./friend/build/friend_server ./friend/depends
get_depends ./message/build/message_server ./message/depends
get_depends ./speech/build/speech_server ./speech/depends
get_depends ./transmite/build/transmite_server ./transmite/depends
get_depends ./user/build/user_server ./user/depends
cp /bin/nc ./gateway/
cp /bin/nc ./file/
cp /bin/nc ./friend/
cp /bin/nc ./message/
cp /bin/nc ./speech/
cp /bin/nc ./transmite/
cp /bin/nc ./user/
get_depends /bin/nc ./gateway/depends
get_depends /bin/nc ./file/depends
get_depends /bin/nc ./friend/depends
get_depends /bin/nc ./message/depends
get_depends /bin/nc ./speech/depends
get_depends /bin/nc ./user/depends
get_depends /bin/nc ./transmite/depends

221
docker-compose.yml Normal file
View File

@ -0,0 +1,221 @@
version: "3.8"
services:
etcd:
image: quay.io/coreos/etcd:v3.5.0
container_name: etcd-service
environment:
- ETCD_NAME=etcd-s1
- ETCD_DATA_DIR=/var/lib/etcd
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
- ./middle/data/etcd:/var/lib/etcd:rw
ports:
- 2379:2379
restart: always
mysql:
image: mysql:8.0.42
container_name: mysql-service
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
- ./sql:/docker-entrypoint-initdb.d/:rw
- ./middle/data/mysql:/var/lib/mysql:rw
ports:
- 3306:3306
restart: always
redis:
image: redis:7.0.15
container_name: redis-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
- ./middle/data/redis:/var/lib/redis:rw
ports:
- 6379:6379
restart: always
elasticsearch:
image: elasticsearch:7.17.21
container_name: elasticsearch-service
environment:
- "discovery.type=single-node"
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
- ./middle/data/elasticsearch:/data:rw
ports:
- 9200:9200
- 9300:9300
restart: always
rabbitmq:
image: rabbitmq:3.10.8
container_name: rabbitmq-service
environment:
RABBITMQ_DEFAULT_USER: root
RABBITMQ_DEFAULT_PASS: 123456
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
- ./middle/data/rabbitmq:/var/lib/rabbitmq:rw
ports:
- 5672:5672
restart: always
file_server:
build: ./file
#image: server-user_server
container_name: file_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/file_server.conf:/im/conf/file_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 10002:10002
restart: always
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379 -c "/im/bin/file_server -flagfile=/im/conf/file_server.conf"
depends_on:
- etcd
friend_server:
build: ./friend
#image: file-server:v1
container_name: friend_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/friend_server.conf:/im/conf/friend_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 10006:10006
restart: always
depends_on:
- etcd
- mysql
- elasticsearch
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379,3306,9200 -c "/im/bin/friend_server -flagfile=/im/conf/friend_server.conf"
gateway_server:
build: ./gateway
#image: file-server:v1
container_name: gateway_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/gateway_server.conf:/im/conf/gateway_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 9000:9000
- 9001:9001
restart: always
depends_on:
- etcd
- redis
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379,6379 -c "/im/bin/gateway_server -flagfile=/im/conf/gateway_server.conf"
message_server:
build: ./message
#image: file-server:v1
container_name: message_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/message_server.conf:/im/conf/message_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 10005:10005
restart: always
depends_on:
- etcd
- mysql
- elasticsearch
- rabbitmq
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379,3306,9200,5672 -c "/im/bin/message_server -flagfile=/im/conf/message_server.conf"
speech_server:
build: ./speech
#image: file-server:v1
container_name: speech_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/speech_server.conf:/im/conf/speech_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 10001:10001
restart: always
depends_on:
- etcd
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379 -c "/im/bin/speech_server -flagfile=/im/conf/speech_server.conf"
transmite_server:
build: ./transmite
#image: file-server:v1
container_name: transmite_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/transmite_server.conf:/im/conf/transmite_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 10004:10004
restart: always
depends_on:
- etcd
- mysql
- rabbitmq
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379,3306,5672 -c "/im/bin/transmite_server -flagfile=/im/conf/transmite_server.conf"
user_server:
build: ./user
#image: file-server:v1
container_name: user_server-service
volumes:
# 1. 希望容器内的程序能够访问宿主机上的文件
# 2. 希望容器内程序运行所产生的数据文件能落在宿主机上
# 挂载的信息: entrypoint.sh文件 数据目录(im/logs, im/data), 配置文件
- ./conf/user_server.conf:/im/conf/user_server.conf
- ./middle/data/logs:/im/logs:rw
- ./middle/data/data:/im/data:rw
- ./entrypoint.sh:/im/bin/entrypoint.sh
ports:
- 10003:10003
restart: always
depends_on:
- etcd
- mysql
- redis
- elasticsearch
entrypoint:
# 跟dockerfile中的cmd比较类似都是容器启动后的默认操作--替代dockerfile中的cmd
/im/bin/entrypoint.sh -h 10.0.0.235 -p 2379,3306,5672,9200 -c "/im/bin/user_server -flagfile=/im/conf/user_server.conf"

38
entrypoint.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
#./entrypoint.sh -h 127.0.0.1 -p 3306,2379,6379 -c '/im/bin/file_server -flagfile=./xx.conf'
# 1. 编写一个端口探测函数,端口连接不上则循环等待
# wait_for 127.0.0.1 3306
wait_for() {
while ! nc -z $1 $2
do
echo "$2 端口连接失败,休眠等待!";
sleep 1;
done
echo "$1:$2 检测成功!";
}
# 2. 对脚本运行参数进行解析获取到ipportcommand
declare ip
declare ports
declare command
while getopts "h:p:c:" arg
do
case $arg in
h)
ip=$OPTARG;;
p)
ports=$OPTARG;;
c)
command=$OPTARG;;
esac
done
# 3. 通过执行脚本进行端口检测
# ${port //,/ } 针对port中的内容以空格替换字符串中的, shell中数组--一种以空格间隔的字符串
for port in ${ports//,/ }
do
wait_for $ip $port
done
# 4. 执行command
# eval 对一个字符串进行二次检测,将其当作命令进行执行
eval $command

55
file/CMakeLists.txt Normal file
View File

@ -0,0 +1,55 @@
# 1. 添加cmake版本说明
cmake_minimum_required(VERSION 3.1.3)
# 2. 声明工程名称
project(file_server)
set(target "file_server")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 3. 检测并生成ODB框架代码
# 1. 添加所需的proto映射代码文件名称
set(proto_path ${CMAKE_CURRENT_SOURCE_DIR}/../proto)
set(proto_files file.proto base.proto)
# 2. 检测框架代码文件是否已经生成
set(proto_hxx "")
set(proto_cxx "")
set(proto_srcs "")
foreach(proto_file ${proto_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".proto" ".pb.cc" proto_cc ${proto_file})
string(REPLACE ".proto" ".pb.h" proto_hh ${proto_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${proto_cc})
add_custom_command(
PRE_BUILD
COMMAND protoc
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I ${proto_path} --experimental_allow_proto3_optional ${proto_path}/${proto_file}
DEPENDS ${proto_path}/${proto_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
COMMENT "生成Protobuf框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
)
endif()
list(APPEND proto_srcs ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc})
endforeach()
# 4. 获取源码目录下的所有源码文件
set(src_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files)
# 5. 声明目标及依赖
add_executable(${target} ${src_files} ${proto_srcs})
# 7. 设置需要连接的库
target_link_libraries(${target} -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
set(test_client "file_client")
set(test_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/test test_files)
add_executable(${test_client} ${test_files} ${proto_srcs})
target_link_libraries(${test_client} -lgtest -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
# 6. 设置头文件默认搜索路径
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third/include)
#8. 设置安装路径
INSTALL(TARGETS ${target} ${test_client} RUNTIME DESTINATION bin)

16
file/dockerfile Normal file
View File

@ -0,0 +1,16 @@
# 声明基础经镜像来源
FROM debian:12
# 声明工作目录
WORKDIR /im
RUN mkdir -p /im/logs &&\
mkdir -p /im/data &&\
mkdir -p /im/conf &&\
mkdir -p /im/bin
# 将可执行程序依赖,拷贝进镜像
COPY ./build/file_server /im/bin/
# 将可执行程序文件,拷贝进镜像
COPY ./depends /lib/x86_64-linux-gnu/
# 设置容器启动的默认操作 ---运行程序
CMD /im/bin/file_server -flagfile=/im/conf/file_server.conf

11
file/file_server.conf Normal file
View File

@ -0,0 +1,11 @@
-run_mode=true
-log_file=/im/logs/file.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-base_service=/service
-instance_name=/file_service/instance
-access_host=10.0.0.235:10002
-storage_path=/im/data/
-listen_port=10002
-rpc_timeout=-1
-rpc_threads=1

View File

@ -0,0 +1,33 @@
//按照流程完成服务器的搭建
//1. 参数解析
//2. 日志初始化
//3. 构造服务器对象,启动服务器
#include "file_server.hpp"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(registry_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(instance_name, "/file_service/instance", "当前实例名称");
DEFINE_string(access_host, "127.0.0.1:10002", "当前实例的外部访问地址");
DEFINE_string(storage_path, "./data/", "当前实例的外部访问地址");
DEFINE_int32(listen_port, 10002, "Rpc服务器监听端口");
DEFINE_int32(rpc_timeout, -1, "Rpc调用超时时间");
DEFINE_int32(rpc_threads, 1, "Rpc的IO线程数量");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
bite_im::FileServerBuilder fsb;
fsb.make_rpc_server(FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads, FLAGS_storage_path);
fsb.make_reg_object(FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host);
auto server = fsb.build();
server->start();
return 0;
}

190
file/source/file_server.hpp Normal file
View File

@ -0,0 +1,190 @@
//实现文件存储子服务
//1. 实现文件rpc服务类 --- 实现rpc调用的业务处理接口
//2. 实现文件存储子服务的服务器类
//3. 实现文件存储子服务类的构造者
#include <brpc/server.h>
#include <butil/logging.h>
#include "asr.hpp"
#include "etcd.hpp" // 服务注册模块封装
#include "logger.hpp" // 日志模块封装
#include "utils.hpp" // uuid生成、文件读写等工具函数
#include "base.pb.h"
#include "file.pb.h"
namespace bite_im{
class FileServiceImpl : public bite_im::FileService {
public:
FileServiceImpl(const std::string &storage_path):
_storage_path(storage_path){
umask(0);
mkdir(storage_path.c_str(), 0775);
if (_storage_path.back() != '/') _storage_path.push_back('/');
}
~FileServiceImpl(){}
void GetSingleFile(google::protobuf::RpcController* controller,
const ::bite_im::GetSingleFileReq* request,
::bite_im::GetSingleFileRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
response->set_request_id(request->request_id());
//1. 取出请求中的文件ID起始就是文件名
std::string fid = request->file_id();
std::string filename = _storage_path + fid;
//2. 将文件ID作为文件名读取文件数据
std::string body;
bool ret = readFile(filename, body);
if (ret == false) {
response->set_success(false);
response->set_errmsg("读取文件数据失败!");
LOG_ERROR("{} 读取文件数据失败!", request->request_id());
return;
}
//3. 组织响应
response->set_success(true);
response->mutable_file_data()->set_file_id(fid);
response->mutable_file_data()->set_file_content(body);
}
void GetMultiFile(google::protobuf::RpcController* controller,
const ::bite_im::GetMultiFileReq* request,
::bite_im::GetMultiFileRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
response->set_request_id(request->request_id());
// 循环取出请求中的文件ID读取文件数据进行填充
for (int i = 0; i < request->file_id_list_size(); i++) {
std::string fid = request->file_id_list(i);
std::string filename = _storage_path + fid;
std::string body;
bool ret = readFile(filename, body);
if (ret == false) {
response->set_success(false);
response->set_errmsg("读取文件数据失败!");
LOG_ERROR("{} 读取文件数据失败!", request->request_id());
return;
}
FileDownloadData data;
data.set_file_id(fid);
data.set_file_content(body);
response->mutable_file_data()->insert({fid, data});
}
response->set_success(true);
}
void PutSingleFile(google::protobuf::RpcController* controller,
const ::bite_im::PutSingleFileReq* request,
::bite_im::PutSingleFileRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
response->set_request_id(request->request_id());
//1. 为文件生成一个唯一uudi作为文件名 以及 文件ID
std::string fid = uuid();
std::string filename = _storage_path + fid;
//2. 取出请求中的文件数据,进行文件数据写入
bool ret = writeFile(filename, request->file_data().file_content());
if (ret == false) {
response->set_success(false);
response->set_errmsg("读取文件数据失败!");
LOG_ERROR("{} 写入文件数据失败!", request->request_id());
return;
}
//3. 组织响应
response->set_success(true);
response->mutable_file_info()->set_file_id(fid);
response->mutable_file_info()->set_file_size(request->file_data().file_size());
response->mutable_file_info()->set_file_name(request->file_data().file_name());
}
void PutMultiFile(google::protobuf::RpcController* controller,
const ::bite_im::PutMultiFileReq* request,
::bite_im::PutMultiFileRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
response->set_request_id(request->request_id());
for (int i = 0; i < request->file_data_size(); i++) {
std::string fid = uuid();
std::string filename = _storage_path + fid;
bool ret = writeFile(filename, request->file_data(i).file_content());
if (ret == false) {
response->set_success(false);
response->set_errmsg("读取文件数据失败!");
LOG_ERROR("{} 写入文件数据失败!", request->request_id());
return;
}
bite_im::FileMessageInfo *info = response->add_file_info();
info->set_file_id(fid);
info->set_file_size(request->file_data(i).file_size());
info->set_file_name(request->file_data(i).file_name());
}
response->set_success(true);
}
private:
std::string _storage_path;
};
class FileServer {
public:
using ptr = std::shared_ptr<FileServer>;
FileServer(const Registry::ptr &reg_client,
const std::shared_ptr<brpc::Server> &server):
_reg_client(reg_client),
_rpc_server(server){}
~FileServer(){}
//搭建RPC服务器并启动服务器
void start() {
_rpc_server->RunUntilAskedToQuit();
}
private:
Registry::ptr _reg_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
class FileServerBuilder {
public:
//用于构造服务注册客户端对象
void make_reg_object(const std::string &reg_host,
const std::string &service_name,
const std::string &access_host) {
_reg_client = std::make_shared<Registry>(reg_host);
_reg_client->registry(service_name, access_host);
}
//构造RPC服务器对象
void make_rpc_server(uint16_t port, int32_t timeout,
uint8_t num_threads, const std::string &path = "./data/") {
_rpc_server = std::make_shared<brpc::Server>();
FileServiceImpl *file_service = new FileServiceImpl(path);
int ret = _rpc_server->AddService(file_service,
brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
if (ret == -1) {
LOG_ERROR("添加Rpc服务失败");
abort();
}
brpc::ServerOptions options;
options.idle_timeout_sec = timeout;
options.num_threads = num_threads;
ret = _rpc_server->Start(port, &options);
if (ret == -1) {
LOG_ERROR("服务启动失败!");
abort();
}
}
FileServer::ptr build() {
if (!_reg_client) {
LOG_ERROR("还未初始化服务注册模块!");
abort();
}
if (!_rpc_server) {
LOG_ERROR("还未初始化RPC服务器模块");
abort();
}
FileServer::ptr server = std::make_shared<FileServer>(_reg_client, _rpc_server);
return server;
}
private:
Registry::ptr _reg_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
}

152
file/test/file_client.cc Normal file
View File

@ -0,0 +1,152 @@
//编写一个file客户端程序对文件存储子服务进行单元测试
// 1. 封装四个接口进行rpc调用实现对于四个业务接口的测试
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include <thread>
#include "etcd.hpp"
#include "channel.hpp"
#include "logger.hpp"
#include "file.pb.h"
#include "base.pb.h"
#include "utils.hpp"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(etcd_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(file_service, "/service/file_service", "服务监控根目录");
bite_im::ServiceChannel::ChannelPtr channel;
std::string single_file_id;
TEST(put_test, single_file) {
//1. 读取当前目录下的指定文件数据
std::string body;
ASSERT_TRUE(bite_im::readFile("./Makefile", body));
//2. 实例化rpc调用客户端对象发起rpc调用
bite_im::FileService_Stub stub(channel.get());
bite_im::PutSingleFileReq req;
req.set_request_id("1111");
req.mutable_file_data()->set_file_name("Makefile");
req.mutable_file_data()->set_file_size(body.size());
req.mutable_file_data()->set_file_content(body);
brpc::Controller *cntl = new brpc::Controller();
bite_im::PutSingleFileRsp *rsp = new bite_im::PutSingleFileRsp();
stub.PutSingleFile(cntl, &req, rsp, nullptr);
ASSERT_FALSE(cntl->Failed());
//3. 检测返回值中上传是否成功
ASSERT_TRUE(rsp->success());
ASSERT_EQ(rsp->file_info().file_size(), body.size());
ASSERT_EQ(rsp->file_info().file_name(), "Makefile");
single_file_id = rsp->file_info().file_id();
LOG_DEBUG("文件ID{}", rsp->file_info().file_id());
}
TEST(get_test, single_file) {
//先发起Rpc调用进行文件下载
bite_im::FileService_Stub stub(channel.get());
bite_im::GetSingleFileReq req;
bite_im::GetSingleFileRsp *rsp;
req.set_request_id("2222");
req.set_file_id(single_file_id);
brpc::Controller *cntl = new brpc::Controller();
rsp = new bite_im::GetSingleFileRsp();
stub.GetSingleFile(cntl, &req, rsp, nullptr);
ASSERT_FALSE(cntl->Failed());
ASSERT_TRUE(rsp->success());
//将文件数据,存储到文件中
ASSERT_EQ(single_file_id, rsp->file_data().file_id());
bite_im::writeFile("make_file_download", rsp->file_data().file_content());
}
std::vector<std::string> multi_file_id;
TEST(put_test, multi_file) {
//1. 读取当前目录下的指定文件数据
std::string body1;
ASSERT_TRUE(bite_im::readFile("./base.pb.h", body1));
std::string body2;
ASSERT_TRUE(bite_im::readFile("./file.pb.h", body2));
//2. 实例化rpc调用客户端对象发起rpc调用
bite_im::FileService_Stub stub(channel.get());
bite_im::PutMultiFileReq req;
req.set_request_id("3333");
auto file_data = req.add_file_data();
file_data->set_file_name("base.pb.h");
file_data->set_file_size(body1.size());
file_data->set_file_content(body1);
file_data = req.add_file_data();
file_data->set_file_name("file.pb.h");
file_data->set_file_size(body2.size());
file_data->set_file_content(body2);
brpc::Controller *cntl = new brpc::Controller();
bite_im::PutMultiFileRsp *rsp = new bite_im::PutMultiFileRsp();
stub.PutMultiFile(cntl, &req, rsp, nullptr);
ASSERT_FALSE(cntl->Failed());
//3. 检测返回值中上传是否成功
ASSERT_TRUE(rsp->success());
for (int i = 0; i < rsp->file_info_size(); i++){
multi_file_id.push_back(rsp->file_info(i).file_id());
LOG_DEBUG("文件ID{}", multi_file_id[i]);
}
}
TEST(get_test, multi_file) {
//先发起Rpc调用进行文件下载
bite_im::FileService_Stub stub(channel.get());
bite_im::GetMultiFileReq req;
bite_im::GetMultiFileRsp *rsp;
req.set_request_id("4444");
req.add_file_id_list(multi_file_id[0]);
req.add_file_id_list(multi_file_id[1]);
brpc::Controller *cntl = new brpc::Controller();
rsp = new bite_im::GetMultiFileRsp();
stub.GetMultiFile(cntl, &req, rsp, nullptr);
ASSERT_FALSE(cntl->Failed());
ASSERT_TRUE(rsp->success());
//将文件数据,存储到文件中
ASSERT_TRUE(rsp->file_data().find(multi_file_id[0]) != rsp->file_data().end());
ASSERT_TRUE(rsp->file_data().find(multi_file_id[1]) != rsp->file_data().end());
auto map = rsp->file_data();
auto file_data1 = map[multi_file_id[0]];
bite_im::writeFile("base_download_file1",file_data1.file_content());
auto file_data2 = map[multi_file_id[1]];
bite_im::writeFile("file_download_file2", file_data2.file_content());
}
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
//1. 先构造Rpc信道管理对象
auto sm = std::make_shared<bite_im::ServiceManager>();
sm->declared(FLAGS_file_service);
auto put_cb = std::bind(&bite_im::ServiceManager::onServiceOnline, sm.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&bite_im::ServiceManager::onServiceOffline, sm.get(), std::placeholders::_1, std::placeholders::_2);
//2. 构造服务发现对象
bite_im::Discovery::ptr dclient = std::make_shared<bite_im::Discovery>(FLAGS_etcd_host, FLAGS_base_service, put_cb, del_cb);
//3. 通过Rpc信道管理对象获取提供Echo服务的信道
channel = sm->choose(FLAGS_file_service);
if (!channel) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return -1;
}
return RUN_ALL_TESTS();
}

88
friend/CMakeLists.txt Normal file
View File

@ -0,0 +1,88 @@
# 1. 添加cmake版本说明
cmake_minimum_required(VERSION 3.1.3)
# 2. 声明工程名称
project(friend_server)
set(target "friend_server")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 3. 检测并生成ODB框架代码
# 1. 添加所需的proto映射代码文件名称
set(proto_path ${CMAKE_CURRENT_SOURCE_DIR}/../proto)
set(proto_files base.proto user.proto message.proto friend.proto)
# 2. 检测框架代码文件是否已经生成
set(proto_hxx "")
set(proto_cxx "")
set(proto_srcs "")
foreach(proto_file ${proto_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".proto" ".pb.cc" proto_cc ${proto_file})
string(REPLACE ".proto" ".pb.h" proto_hh ${proto_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${proto_cc})
add_custom_command(
PRE_BUILD
COMMAND protoc
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I ${proto_path} --experimental_allow_proto3_optional ${proto_path}/${proto_file}
DEPENDS ${proto_path}/${proto_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
COMMENT "生成Protobuf框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
)
endif()
list(APPEND proto_srcs ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc})
endforeach()
# 3. 检测并生成ODB框架代码
# 1. 添加所需的odb映射代码文件名称
set(odb_path ${CMAKE_CURRENT_SOURCE_DIR}/../odb)
set(odb_files chat_session_member.hxx chat_session.hxx friend_apply.hxx relation.hxx)
# 2. 检测框架代码文件是否已经生成
set(odb_hxx "")
set(odb_cxx "")
set(odb_srcs "")
foreach(odb_file ${odb_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".hxx" "-odb.hxx" odb_hxx ${odb_file})
string(REPLACE ".hxx" "-odb.cxx" odb_cxx ${odb_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${odb_cxx})
add_custom_command(
PRE_BUILD
COMMAND odb
ARGS -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time ${odb_path}/${odb_file}
DEPENDS ${odb_path}/${odb_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
COMMENT "生成ODB框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
)
endif()
# 4. 将所有生成的框架源码文件名称保存起来 student-odb.cxx classes-odb.cxx
list(APPEND odb_srcs ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx})
endforeach()
# 4. 获取源码目录下的所有源码文件
set(src_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files)
# 5. 声明目标及依赖
add_executable(${target} ${src_files} ${proto_srcs} ${odb_srcs})
# 7. 设置需要连接的库
target_link_libraries(${target} -lgflags
-lspdlog -lfmt -lbrpc -lssl -lcrypto
-lprotobuf -lleveldb -letcd-cpp-api
-lcpprest -lcurl -lodb-mysql -lodb -lodb-boost
/usr/lib/x86_64-linux-gnu/libjsoncpp.so.19
-lcpr -lelasticlient)
set(test_client "friend_client")
set(test_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/test test_files)
add_executable(${test_client} ${test_files} ${proto_srcs})
target_link_libraries(${test_client} -pthread -lgtest -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
# 6. 设置头文件默认搜索路径
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../odb)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third/include)
#8. 设置安装路径
INSTALL(TARGETS ${target} ${test_client} RUNTIME DESTINATION bin)

16
friend/dockerfile Normal file
View File

@ -0,0 +1,16 @@
# 声明基础经镜像来源
FROM debian:12
# 声明工作目录
WORKDIR /im
RUN mkdir -p /im/logs &&\
mkdir -p /im/data &&\
mkdir -p /im/conf &&\
mkdir -p /im/bin
# 将可执行程序依赖,拷贝进镜像
COPY ./build/friend_server /im/bin/
# 将可执行程序文件,拷贝进镜像
COPY ./depends /lib/x86_64-linux-gnu/
# 设置容器启动的默认操作 ---运行程序
CMD /im/bin/friend_server -flagfile=/im/conf/friend_server.conf

20
friend/friend_server.conf Normal file
View File

@ -0,0 +1,20 @@
-run_mode=true
-log_file=/im/logs/friend.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/friend_service/instance
-access_host=10.0.0.235:10006
-listen_port=10006
-rpc_timeout=-1
-rpc_threads=1
-base_service=/service
-user_service=/service/user_service
-message_service=/service/message_service
-es_host=http://10.0.0.235:9200/
-mysql_host=10.0.0.235
-mysql_user=root
-mysql_pswd=123456
-mysql_db=bite_im
-mysql_cset=utf8
-mysql_port=0
-mysql_pool_count=4

View File

@ -0,0 +1,49 @@
//主要实现语音识别子服务的服务器的搭建
#include "friend_server.hpp"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(registry_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(instance_name, "/friend_service/instance", "当前实例名称");
DEFINE_string(access_host, "127.0.0.1:10006", "当前实例的外部访问地址");
DEFINE_int32(listen_port, 10006, "Rpc服务器监听端口");
DEFINE_int32(rpc_timeout, -1, "Rpc调用超时时间");
DEFINE_int32(rpc_threads, 1, "Rpc的IO线程数量");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(user_service, "/service/user_service", "用户管理子服务名称");
DEFINE_string(message_service, "/service/message_service", "消息存储子服务名称");
DEFINE_string(es_host, "http://127.0.0.1:9200/", "ES搜索引擎服务器URL");
DEFINE_string(mysql_host, "127.0.0.1", "Mysql服务器访问地址");
DEFINE_string(mysql_user, "root", "Mysql服务器访问用户名");
DEFINE_string(mysql_pswd, "123456", "Mysql服务器访问密码");
DEFINE_string(mysql_db, "bite_im", "Mysql默认库名称");
DEFINE_string(mysql_cset, "utf8", "Mysql客户端字符集");
DEFINE_int32(mysql_port, 0, "Mysql服务器访问端口");
DEFINE_int32(mysql_pool_count, 4, "Mysql连接池最大连接数量");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
bite_im::FriendServerBuilder fsb;
fsb.make_es_object({FLAGS_es_host});
fsb.make_mysql_object(FLAGS_mysql_user, FLAGS_mysql_pswd, FLAGS_mysql_host,
FLAGS_mysql_db, FLAGS_mysql_cset, FLAGS_mysql_port, FLAGS_mysql_pool_count);
fsb.make_discovery_object(FLAGS_registry_host, FLAGS_base_service, FLAGS_user_service, FLAGS_message_service);
fsb.make_rpc_server(FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads);
fsb.make_registry_object(FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host);
auto server = fsb.build();
server->start();
return 0;
}

View File

@ -0,0 +1,637 @@
//实现语音识别子服务
#include <brpc/server.h>
#include <butil/logging.h>
#include "data_es.hpp" // es数据管理客户端封装
#include "mysql_chat_session_member.hpp" // mysql数据管理客户端封装
#include "mysql_chat_session.hpp" // mysql数据管理客户端封装
#include "mysql_relation.hpp" // mysql数据管理客户端封装
#include "mysql_apply.hpp" // mysql数据管理客户端封装
#include "etcd.hpp" // 服务注册模块封装
#include "logger.hpp" // 日志模块封装
#include "utils.hpp" // 基础工具接口
#include "channel.hpp" // 信道管理模块封装
#include "friend.pb.h" // protobuf框架代码
#include "base.pb.h" // protobuf框架代码
#include "user.pb.h" // protobuf框架代码
#include "message.pb.h" // protobuf框架代码
namespace bite_im{
class FriendServiceImpl : public bite_im::FriendService {
public:
FriendServiceImpl(
const std::shared_ptr<elasticlient::Client> &es_client,
const std::shared_ptr<odb::core::database> &mysql_client,
const ServiceManager::ptr &channel_manager,
const std::string &user_service_name,
const std::string &message_service_name) :
_es_user(std::make_shared<ESUser>(es_client)),
_mysql_apply(std::make_shared<FriendApplyTable>(mysql_client)),
_mysql_chat_session(std::make_shared<ChatSessionTable>(mysql_client)),
_mysql_chat_session_member(std::make_shared<ChatSessionMemeberTable>(mysql_client)),
_mysql_relation(std::make_shared<RelationTable>(mysql_client)),
_user_service_name(user_service_name),
_message_service_name(message_service_name),
_mm_channels(channel_manager){}
~FriendServiceImpl(){}
virtual void GetFriendList(::google::protobuf::RpcController* controller,
const ::bite_im::GetFriendListReq* request,
::bite_im::GetFriendListRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取请求中的关键要素用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
//2. 从数据库中查询获取用户的好友ID
auto friend_id_lists = _mysql_relation->friends(uid);
std::unordered_set<std::string> user_id_lists;
for (auto &id : friend_id_lists) {
user_id_lists.insert(id);
}
//3. 从用户子服务批量获取用户信息
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, user_id_lists, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto & user_it : user_list) {
auto user_info = response->add_friend_list();
user_info->CopyFrom(user_it.second);
}
}
virtual void FriendRemove(::google::protobuf::RpcController* controller,
const ::bite_im::FriendRemoveReq* request,
::bite_im::FriendRemoveRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取关键要素当前用户ID要删除的好友ID
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string pid = request->peer_id();
//2. 从好友关系表中删除好友关系信息
bool ret = _mysql_relation->remove(uid, pid);
if (ret == false) {
LOG_ERROR("{} - 从数据库删除好友信息失败!", rid);
return err_response(rid, "从数据库删除好友信息失败!");
}
//3. 从会话信息表中,删除对应的聊天会话 -- 同时删除会话成员表中的成员信息
ret = _mysql_chat_session->remove(uid, pid);
if (ret == false) {
LOG_ERROR("{}- 从数据库删除好友会话信息失败!", rid);
return err_response(rid, "从数据库删除好友会话信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
}
virtual void FriendAdd(::google::protobuf::RpcController* controller,
const ::bite_im::FriendAddReq* request,
::bite_im::FriendAddRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取请求中的关键要素申请人用户ID 被申请人用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string pid = request->respondent_id();
//2. 判断两人是否已经是好友
bool ret = _mysql_relation->exists(uid, pid);
if (ret == true) {
LOG_ERROR("{}- 申请好友失败-两者{}-{}已经是好友关系", rid, uid, pid);
return err_response(rid, "两者已经是好友关系!");
}
//3. 当前是否已经申请过好友
ret = _mysql_apply->exists(uid, pid);
if (ret == true) {
LOG_ERROR("{}- 申请好友失败-已经申请过对方好友!", rid, uid, pid);
return err_response(rid, "已经申请过对方好友!");
}
//4. 向好友申请表中,新增申请信息
std::string eid = uuid();
FriendApply ev(eid, uid, pid);
ret = _mysql_apply->insert(ev);
if (ret == false) {
LOG_ERROR("{} - 向数据库新增好友申请事件失败!", rid);
return err_response(rid, "向数据库新增好友申请事件失败!");
}
//3. 组织响应
response->set_request_id(rid);
response->set_success(true);
response->set_notify_event_id(eid);
}
virtual void FriendAddProcess(::google::protobuf::RpcController* controller,
const ::bite_im::FriendAddProcessReq* request,
::bite_im::FriendAddProcessRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取请求中的关键要素申请人用户ID被申请人用户ID处理结果事件ID
std::string rid = request->request_id();
std::string eid = request->notify_event_id();
std::string uid = request->user_id(); //被申请人
std::string pid = request->apply_user_id();//申请人
bool agree = request->agree();
//2. 判断有没有该申请事件
bool ret = _mysql_apply->exists(pid, uid);
if (ret == false) {
LOG_ERROR("{}- 没有找到{}-{}对应的好友申请事件!", rid, pid, uid);
return err_response(rid, "没有找到对应的好友申请事件!");
}
//3. 如果有: 可以处理; --- 删除申请事件--事件已经处理完毕
ret = _mysql_apply->remove(pid, uid);
if (ret == false) {
LOG_ERROR("{}- 从数据库删除申请事件 {}-{} 失败!", rid, pid, uid);
return err_response(rid, "从数据库删除申请事件失败!");
}
//4. 如果处理结果是同意:向数据库新增好友关系信息;新增单聊会话信息及会话成员
std::string cssid;
if (agree == true) {
ret = _mysql_relation->insert(uid, pid);
if (ret == false) {
LOG_ERROR("{}- 新增好友关系信息-{}-{}", rid, uid, pid);
return err_response(rid, "新增好友关系信息!");
}
cssid = uuid();
ChatSession cs(cssid, "", ChatSessionType::SINGLE);
ret = _mysql_chat_session->insert(cs);
if (ret == false) {
LOG_ERROR("{}- 新增单聊会话信息-{}", rid, cssid);
return err_response(rid, "新增单聊会话信息失败!");
}
ChatSessionMember csm1(cssid, uid);
ChatSessionMember csm2(cssid, pid);
std::vector<ChatSessionMember> mlist = {csm1, csm2};
ret = _mysql_chat_session_member->append(mlist);
if (ret == false) {
LOG_ERROR("{}- 没有找到{}-{}对应的好友申请事件!", rid, pid, uid);
return err_response(rid, "没有找到对应的好友申请事件!");
}
}
//5. 组织响应
response->set_request_id(rid);
response->set_success(true);
response->set_new_session_id(cssid);
}
virtual void FriendSearch(::google::protobuf::RpcController* controller,
const ::bite_im::FriendSearchReq* request,
::bite_im::FriendSearchRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取请求中的关键要素搜索关键字可能是用户ID可能是手机号可能是昵称的一部分
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string skey = request->search_key();
LOG_DEBUG("{} 好友搜索 {}", uid, skey);
//2. 根据用户ID获取用户的好友ID列表
auto friend_id_lists = _mysql_relation->friends(uid);
//3. 从ES搜索引擎进行用户信息搜索 --- 过滤掉当前的好友
std::unordered_set<std::string> user_id_lists;
friend_id_lists.push_back(uid);// 把自己也过滤掉
auto search_res = _es_user->search(skey, friend_id_lists);
for (auto &it : search_res) {
user_id_lists.insert(it.user_id());
}
//4. 根据获取到的用户ID 从用户子服务器进行批量用户信息获取
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, user_id_lists, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
//5. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto & user_it : user_list) {
auto user_info = response->add_user_info();
user_info->CopyFrom(user_it.second);
}
}
virtual void GetPendingFriendEventList(::google::protobuf::RpcController* controller,
const ::bite_im::GetPendingFriendEventListReq* request,
::bite_im::GetPendingFriendEventListRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取关键要素当前用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
//2. 从数据库获取待处理的申请事件信息 --- 申请人用户ID列表
auto res = _mysql_apply->applyUsers(uid);
std::unordered_set<std::string> user_id_lists;
for (auto &id : res) {
user_id_lists.insert(id);
}
//3. 批量获取申请人用户信息、
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, user_id_lists, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto & user_it : user_list) {
auto ev = response->add_event();
ev->mutable_sender()->CopyFrom(user_it.second);
}
}
virtual void GetChatSessionList(::google::protobuf::RpcController* controller,
const ::bite_im::GetChatSessionListReq* request,
::bite_im::GetChatSessionListRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//获取聊天会话的作用:一个用户登录成功后,能够展示自己的历史聊天信息
//1. 提取请求中的关键要素当前请求用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
//2. 从数据库中查询出用户的单聊会话列表
auto sf_list = _mysql_chat_session->singleChatSession(uid);
// 1. 从单聊会话列表中取出所有的好友ID从用户子服务获取用户信息
std::unordered_set<std::string> users_id_list;
for (const auto &f : sf_list) {
users_id_list.insert(f.friend_id);
}
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, users_id_list, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
// 2. 设置响应会话信息:会话名称就是好友名称;会话头像就是好友头像
//3. 从数据库中查询出用户的群聊会话列表
auto gc_list = _mysql_chat_session->groupChatSession(uid);
//4. 根据所有的会话ID从消息存储子服务获取会话最后一条消息
//5. 组织响应
for (const auto &f : sf_list) {
auto chat_session_info = response->add_chat_session_info_list();
chat_session_info->set_single_chat_friend_id(f.friend_id);
chat_session_info->set_chat_session_id(f.chat_session_id);
chat_session_info->set_chat_session_name(user_list[f.friend_id].nickname());
chat_session_info->set_avatar(user_list[f.friend_id].avatar());
MessageInfo msg;
ret = GetRecentMsg(rid, f.chat_session_id, msg);
if (ret == false) {continue;}
chat_session_info->mutable_prev_message()->CopyFrom(msg);
}
for (const auto &f : gc_list) {
auto chat_session_info = response->add_chat_session_info_list();
chat_session_info->set_chat_session_id(f.chat_session_id);
chat_session_info->set_chat_session_name(f.chat_session_name);
MessageInfo msg;
ret = GetRecentMsg(rid, f.chat_session_id, msg);
if (ret == false) { continue; }
chat_session_info->mutable_prev_message()->CopyFrom(msg);
}
response->set_request_id(rid);
response->set_success(true);
}
virtual void ChatSessionCreate(::google::protobuf::RpcController* controller,
const ::bite_im::ChatSessionCreateReq* request,
::bite_im::ChatSessionCreateRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//创建会话,其实针对的是用户要创建一个群聊会话
//1. 提取请求关键要素:会话名称,会话成员
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string cssname = request->chat_session_name();
//2. 生成会话ID向数据库添加会话信息添加会话成员信息
std::string cssid = uuid();
ChatSession cs(cssid, cssname, ChatSessionType::GROUP);
bool ret = _mysql_chat_session->insert(cs);
if (ret == false) {
LOG_ERROR("{} - 向数据库添加会话信息失败: {}", rid, cssname);
return err_response(rid, "向数据库添加会话信息失败!");
}
std::vector<ChatSessionMember> member_list;
for (int i = 0; i < request->member_id_list_size(); i++) {
ChatSessionMember csm(cssid, request->member_id_list(i));
member_list.push_back(csm);
}
ret = _mysql_chat_session_member->append(member_list);
if (ret == false) {
LOG_ERROR("{} - 向数据库添加会话成员信息失败: {}", rid, cssname);
return err_response(rid, "向数据库添加会话成员信息失败!");
}
//3. 组织响应---组织会话信息
response->set_request_id(rid);
response->set_success(true);
response->mutable_chat_session_info()->set_chat_session_id(cssid);
response->mutable_chat_session_info()->set_chat_session_name(cssname);
}
virtual void GetChatSessionMember(::google::protobuf::RpcController* controller,
const ::bite_im::GetChatSessionMemberReq* request,
::bite_im::GetChatSessionMemberRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//用于用户查看群聊成员信息的时候:进行成员信息展示
//1. 提取关键要素聊天会话ID
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string cssid = request->chat_session_id();
//2. 从数据库获取会话成员ID列表
auto member_id_lists = _mysql_chat_session_member->members(cssid);
std::unordered_set<std::string> uid_list;
for (const auto &id : member_id_lists) {
uid_list.insert(id);
}
//3. 从用户子服务批量获取用户信息
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, uid_list, user_list);
if (ret == false) {
LOG_ERROR("{} - 从用户子服务获取用户信息失败!", rid);
return err_response(rid, "从用户子服务获取用户信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto &uit : user_list) {
auto user_info = response->add_member_info_list();
user_info->CopyFrom(uit.second);
}
}
private:
bool GetRecentMsg(const std::string &rid,
const std::string &cssid, MessageInfo &msg) {
auto channel = _mm_channels->choose(_message_service_name);
if (!channel) {
LOG_ERROR("{} - 获取消息子服务信道失败!!", rid);
return false;
}
GetRecentMsgReq req;
GetRecentMsgRsp rsp;
req.set_request_id(rid);
req.set_chat_session_id(cssid);
req.set_msg_count(1);
brpc::Controller cntl;
bite_im::MsgStorageService_Stub stub(channel.get());
stub.GetRecentMsg(&cntl, &req, &rsp, nullptr);
if (cntl.Failed() == true) {
LOG_ERROR("{} - 消息存储子服务调用失败: {}", rid, cntl.ErrorText());
return false;
}
if ( rsp.success() == false) {
LOG_ERROR("{} - 获取会话 {} 最近消息失败: {}", rid, cssid, rsp.errmsg());
return false;
}
if (rsp.msg_list_size() > 0) {
msg.CopyFrom(rsp.msg_list(0));
return true;
}
return false;
}
bool GetUserInfo(const std::string &rid,
const std::unordered_set<std::string> &uid_list,
std::unordered_map<std::string, UserInfo> &user_list) {
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} - 获取用户子服务信道失败!!", rid);
return false;
}
GetMultiUserInfoReq req;
GetMultiUserInfoRsp rsp;
req.set_request_id(rid);
for (auto &id : uid_list) {
req.add_users_id(id);
}
brpc::Controller cntl;
bite_im::UserService_Stub stub(channel.get());
stub.GetMultiUserInfo(&cntl, &req, &rsp, nullptr);
if (cntl.Failed() == true) {
LOG_ERROR("{} - 用户子服务调用失败: {}", rid, cntl.ErrorText());
return false;
}
if ( rsp.success() == false) {
LOG_ERROR("{} - 批量获取用户信息失败: {}", rid, rsp.errmsg());
return false;
}
for (const auto & user_it : rsp.users_info()) {
user_list.insert(std::make_pair(user_it.first, user_it.second));
}
return true;
}
private:
ESUser::ptr _es_user;
FriendApplyTable::ptr _mysql_apply;
ChatSessionTable::ptr _mysql_chat_session;
ChatSessionMemeberTable::ptr _mysql_chat_session_member;
RelationTable::ptr _mysql_relation;
//这边是rpc调用客户端相关对象
std::string _user_service_name;
std::string _message_service_name;
ServiceManager::ptr _mm_channels;
};
class FriendServer {
public:
using ptr = std::shared_ptr<FriendServer>;
FriendServer(const Discovery::ptr service_discoverer,
const Registry::ptr &reg_client,
const std::shared_ptr<elasticlient::Client> &es_client,
const std::shared_ptr<odb::core::database> &mysql_client,
const std::shared_ptr<brpc::Server> &server):
_service_discoverer(service_discoverer),
_registry_client(reg_client),
_es_client(es_client),
_mysql_client(mysql_client),
_rpc_server(server){}
~FriendServer(){}
//搭建RPC服务器并启动服务器
void start() {
_rpc_server->RunUntilAskedToQuit();
}
private:
Discovery::ptr _service_discoverer;
Registry::ptr _registry_client;
std::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
class FriendServerBuilder {
public:
//构造es客户端对象
void make_es_object(const std::vector<std::string> host_list) {
_es_client = ESClientFactory::create(host_list);
}
//构造mysql客户端对象
void make_mysql_object(
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) {
_mysql_client = ODBFactory::create(user, pswd, host, db, cset, port, conn_pool_count);
}
//用于构造服务发现客户端&信道管理对象
void make_discovery_object(const std::string &reg_host,
const std::string &base_service_name,
const std::string &user_service_name,
const std::string &message_service_name) {
_user_service_name = user_service_name;
_message_service_name = message_service_name;
_mm_channels = std::make_shared<ServiceManager>();
_mm_channels->declared(user_service_name);
_mm_channels->declared(message_service_name);
LOG_DEBUG("设置用户子服务为需添加管理的子服务:{}", user_service_name);
LOG_DEBUG("设置消息子服务为需添加管理的子服务:{}", message_service_name);
auto put_cb = std::bind(&ServiceManager::onServiceOnline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&ServiceManager::onServiceOffline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
_service_discoverer = std::make_shared<Discovery>(reg_host, base_service_name, put_cb, del_cb);
}
//用于构造服务注册客户端对象
void make_registry_object(const std::string &reg_host,
const std::string &service_name,
const std::string &access_host) {
_registry_client = std::make_shared<Registry>(reg_host);
_registry_client->registry(service_name, access_host);
}
void make_rpc_server(uint16_t port, int32_t timeout, uint8_t num_threads) {
if (!_es_client) {
LOG_ERROR("还未初始化ES搜索引擎模块");
abort();
}
if (!_mysql_client) {
LOG_ERROR("还未初始化Mysql数据库模块");
abort();
}
if (!_mm_channels) {
LOG_ERROR("还未初始化信道管理模块!");
abort();
}
_rpc_server = std::make_shared<brpc::Server>();
FriendServiceImpl *friend_service = new FriendServiceImpl(_es_client,
_mysql_client, _mm_channels, _user_service_name, _message_service_name);
int ret = _rpc_server->AddService(friend_service,
brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
if (ret == -1) {
LOG_ERROR("添加Rpc服务失败");
abort();
}
brpc::ServerOptions options;
options.idle_timeout_sec = timeout;
options.num_threads = num_threads;
ret = _rpc_server->Start(port, &options);
if (ret == -1) {
LOG_ERROR("服务启动失败!");
abort();
}
}
//构造RPC服务器对象
FriendServer::ptr build() {
if (!_service_discoverer) {
LOG_ERROR("还未初始化服务发现模块!");
abort();
}
if (!_registry_client) {
LOG_ERROR("还未初始化服务注册模块!");
abort();
}
if (!_rpc_server) {
LOG_ERROR("还未初始化RPC服务器模块");
abort();
}
FriendServer::ptr server = std::make_shared<FriendServer>(
_service_discoverer, _registry_client,
_es_client, _mysql_client, _rpc_server);
return server;
}
private:
Registry::ptr _registry_client;
std::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
std::string _user_service_name;
std::string _message_service_name;
ServiceManager::ptr _mm_channels;
Discovery::ptr _service_discoverer;
std::shared_ptr<brpc::Server> _rpc_server;
};
}

View File

@ -0,0 +1,286 @@
#include "etcd.hpp"
#include "channel.hpp"
#include "utils.hpp"
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include <thread>
#include "friend.pb.h"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(etcd_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(friend_service, "/service/friend_service", "服务监控根目录");
bite_im::ServiceManager::ptr sm;
void apply_test(const std::string &uid1, const std::string &uid2) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::FriendAddReq req;
bite_im::FriendAddRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
req.set_respondent_id(uid2);
brpc::Controller cntl;
stub.FriendAdd(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}
void get_apply_list(const std::string &uid1) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::GetPendingFriendEventListReq req;
bite_im::GetPendingFriendEventListRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
brpc::Controller cntl;
stub.GetPendingFriendEventList(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
for (int i = 0; i < rsp.event_size(); i++) {
std::cout << "---------------\n";
std::cout << rsp.event(i).sender().user_id() << std::endl;
std::cout << rsp.event(i).sender().nickname() << std::endl;
std::cout << rsp.event(i).sender().avatar() << std::endl;
}
}
void process_apply_test(const std::string &uid1, bool agree, const std::string &apply_user_id) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::FriendAddProcessReq req;
bite_im::FriendAddProcessRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
req.set_agree(agree);
req.set_apply_user_id(apply_user_id);
brpc::Controller cntl;
stub.FriendAddProcess(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
if (agree) {
std::cout << rsp.new_session_id() << std::endl;
}
}
void search_test(const std::string &uid1, const std::string &key) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::FriendSearchReq req;
bite_im::FriendSearchRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
req.set_search_key(key);
brpc::Controller cntl;
stub.FriendSearch(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
for (int i = 0; i < rsp.user_info_size(); i++) {
std::cout << "-------------------\n";
std::cout << rsp.user_info(i).user_id() << std::endl;
std::cout << rsp.user_info(i).nickname() << std::endl;
std::cout << rsp.user_info(i).avatar() << std::endl;
}
}
void friend_list_test(const std::string &uid1) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::GetFriendListReq req;
bite_im::GetFriendListRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
brpc::Controller cntl;
stub.GetFriendList(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
for (int i = 0; i < rsp.friend_list_size(); i++) {
std::cout << "-------------------\n";
std::cout << rsp.friend_list(i).user_id() << std::endl;
std::cout << rsp.friend_list(i).nickname() << std::endl;
std::cout << rsp.friend_list(i).avatar() << std::endl;
}
}
void remove_test(const std::string &uid1, const std::string &uid2) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::FriendRemoveReq req;
bite_im::FriendRemoveRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
req.set_peer_id(uid2);
brpc::Controller cntl;
stub.FriendRemove(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}
void create_css_test(const std::string &uid1, const std::vector<std::string> &uidlist) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::ChatSessionCreateReq req;
bite_im::ChatSessionCreateRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
req.set_chat_session_name("快乐一家人");
for (auto &id : uidlist) {
req.add_member_id_list(id);
}
brpc::Controller cntl;
stub.ChatSessionCreate(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
std::cout << rsp.chat_session_info().chat_session_id() << std::endl;
std::cout << rsp.chat_session_info().chat_session_name() << std::endl;
}
void cssmember_test(const std::string &uid1, const std::string &cssid) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::GetChatSessionMemberReq req;
bite_im::GetChatSessionMemberRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
req.set_chat_session_id(cssid);
brpc::Controller cntl;
stub.GetChatSessionMember(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
for (int i = 0; i < rsp.member_info_list_size(); i++) {
std::cout << "-------------------\n";
std::cout << rsp.member_info_list(i).user_id() << std::endl;
std::cout << rsp.member_info_list(i).nickname() << std::endl;
std::cout << rsp.member_info_list(i).avatar() << std::endl;
}
}
void csslist_test(const std::string &uid1) {
auto channel = sm->choose(FLAGS_friend_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::FriendService_Stub stub(channel.get());
bite_im::GetChatSessionListReq req;
bite_im::GetChatSessionListRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_user_id(uid1);
brpc::Controller cntl;
std::cout << "发送获取聊天会话列表请求!!\n";
stub.GetChatSessionList(&cntl, &req, &rsp, nullptr);
std::cout << "请求发送完毕1\n";
ASSERT_FALSE(cntl.Failed());
std::cout << "请求发送完毕2\n";
ASSERT_TRUE(rsp.success());
std::cout << "请求发送完毕,且成功!!\n";
for (int i = 0; i < rsp.chat_session_info_list_size(); i++) {
std::cout << "-------------------\n";
std::cout << rsp.chat_session_info_list(i).single_chat_friend_id() << std::endl;
std::cout << rsp.chat_session_info_list(i).chat_session_id() << std::endl;
std::cout << rsp.chat_session_info_list(i).chat_session_name() << std::endl;
std::cout << rsp.chat_session_info_list(i).avatar() << std::endl;
std::cout << "消息内容:\n";
std::cout << rsp.chat_session_info_list(i).prev_message().message_id() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().chat_session_id() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().timestamp() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().sender().user_id() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().sender().nickname() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().sender().avatar() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().message().file_message().file_name() << std::endl;
std::cout << rsp.chat_session_info_list(i).prev_message().message().file_message().file_contents() << std::endl;
}
}
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
//1. 先构造Rpc信道管理对象
sm = std::make_shared<bite_im::ServiceManager>();
sm->declared(FLAGS_friend_service);
auto put_cb = std::bind(&bite_im::ServiceManager::onServiceOnline, sm.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&bite_im::ServiceManager::onServiceOffline, sm.get(), std::placeholders::_1, std::placeholders::_2);
//2. 构造服务发现对象
bite_im::Discovery::ptr dclient = std::make_shared<bite_im::Discovery>(FLAGS_etcd_host, FLAGS_base_service, put_cb, del_cb);
// apply_test("ee55-9043bfd7-0001", "672f-c755e83e-0000");
// apply_test("67b1-35ca1b76-0000", "672f-c755e83e-0000");
// apply_test("d9ff-65692d4a-0001", "672f-c755e83e-0000");
// get_apply_list("672f-c755e83e-0000");
// process_apply_test("672f-c755e83e-0000", true, "ee55-9043bfd7-0001");
// process_apply_test("672f-c755e83e-0000", false, "67b1-35ca1b76-0000");
// process_apply_test("672f-c755e83e-0000", true, "d9ff-65692d4a-0001");
// std::cout << "**********************\n";
// search_test("672f-c755e83e-0000", "猪");
// std::cout << "++++++++++++++++++++++\n";
// search_test("ee55-9043bfd7-0001", "猪");
// std::cout << "======================\n";
// search_test("67b1-35ca1b76-0000", "乔治");
// friend_list_test("c4dc-68239a9a-0001");
// std::cout << "++++++++++++++++++++++\n";
// friend_list_test("731f-50086884-0000");
// std::cout << "++++++++++++++++++++++\n";
// friend_list_test("31ab-86a1209d-0000");
// remove_test("c4dc-68239a9a-0001", "053f-04e5e4c5-0001");
// std::vector<std::string> uidlist = {
// "731f-50086884-0000",
// "c4dc-68239a9a-0001",
// "31ab-86a1209d-0000",
// "053f-04e5e4c5-0001"};
// create_css_test("731f-50086884-0000", uidlist);
// cssmember_test("731f-50086884-0000", "36b5-edaf4987-0000");
// std::cout << "++++++++++++++++++++++\n";
// cssmember_test("c4dc-68239a9a-0001", "36b5-edaf4987-0000");
// csslist_test("c4dc-68239a9a-0001");
return 0;
}

View File

@ -0,0 +1,121 @@
#include "mysql_chat_session.hpp"
#include "mysql_apply.hpp"
#include "mysql_relation.hpp"
#include <gflags/gflags.h>
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
void r_insert_test(bite_im::RelationTable &tb) {
tb.insert("用户ID1", "用户ID2");
tb.insert("用户ID1", "用户ID3");
}
void r_select_test(bite_im::RelationTable &tb) {
auto res = tb.friends("用户ID1");
for (auto &uid:res) {
std::cout << uid << std::endl;
}
}
void r_remove_test(bite_im::RelationTable &tb) {
tb.remove("用户ID2", "用户ID1");
}
void r_exists_test(bite_im::RelationTable &tb) {
std::cout << tb.exists("用户ID2", "用户ID1") << std::endl;
std::cout << tb.exists("用户ID3", "用户ID1") << std::endl;
}
void a_insert_test(bite_im::FriendApplyTable &tb) {
bite_im::FriendApply fa1("uuid1", "用户ID1", "用户ID2");
tb.insert(fa1);
bite_im::FriendApply fa2("uuid2", "用户ID1", "用户ID3");
tb.insert(fa2);
bite_im::FriendApply fa3("uuid3", "用户ID2", "用户ID3");
tb.insert(fa3);
}
void a_remove_test(bite_im::FriendApplyTable &tb) {
tb.remove("用户ID2", "用户ID3");
}
void a_select_test(bite_im::FriendApplyTable &tb) {
// bite_im::FriendApply fa3("uuid3", "用户ID2", "用户ID3");
// tb.insert(fa3);
auto res = tb.applyUsers("用户ID2");
for (auto &uid:res) {
std::cout << uid << std::endl;
}
}
void a_exists_test(bite_im::FriendApplyTable &tb) {
std::cout << tb.exists("用户ID1", "用户ID2") << std::endl;
std::cout << tb.exists("31ab-86a1209d-0000", "c4dc-68239a9a-0001") << std::endl;
std::cout << tb.exists("053f-04e5e4c5-0001", "c4dc-68239a9a-0001") << std::endl;
}
void c_insert_test(bite_im::ChatSessionTable &tb) {
bite_im::ChatSession cs1("会话ID1", "会话名称1", bite_im::ChatSessionType::SINGLE);
tb.insert(cs1);
bite_im::ChatSession cs2("会话ID2", "会话名称2", bite_im::ChatSessionType::GROUP);
tb.insert(cs2);
}
void c_select_test(bite_im::ChatSessionTable &tb) {
auto res = tb.select("会话ID1");
std::cout << res->chat_session_id() << std::endl;
std::cout << res->chat_session_name() << std::endl;
std::cout << (int)res->chat_session_type() << std::endl;
}
void c_single_test(bite_im::ChatSessionTable &tb) {
auto res = tb.singleChatSession("731f-50086884-0000");
for (auto &info : res) {
std::cout << info.chat_session_id << std::endl;
std::cout << info.friend_id << std::endl;
}
}
void c_group_test(bite_im::ChatSessionTable &tb) {
auto res = tb.groupChatSession("用户ID1");
for (auto &info : res) {
std::cout << info.chat_session_id << std::endl;
std::cout << info.chat_session_name << std::endl;
}
}
void c_remove_test(bite_im::ChatSessionTable &tb) {
tb.remove("会话ID3");
}
void c_remove_test2(bite_im::ChatSessionTable &tb) {
tb.remove("731f-50086884-0000", "c4dc-68239a9a-0001");
}
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
auto db = bite_im::ODBFactory::create("root", "123456", "127.0.0.1", "bite_im", "utf8", 0, 1);
bite_im::RelationTable rtb(db);
bite_im::FriendApplyTable fatb(db);
bite_im::ChatSessionTable cstb(db);
// r_insert_test(rtb);
// r_select_test(rtb);
// r_remove_test(rtb);
// r_exists_test(rtb);
// a_insert_test(fatb);
// a_remove_test(fatb);
// a_select_test(fatb);
// a_exists_test(fatb);
// c_insert_test(cstb);
// c_select_test(cstb);
// c_single_test(cstb);
// std::cout << "--------------\n";
// c_group_test(cstb);
// c_remove_test(cstb);
// c_remove_test2(cstb);
return 0;
}

56
gateway/CMakeLists.txt Normal file
View File

@ -0,0 +1,56 @@
# 1. 添加cmake版本说明
cmake_minimum_required(VERSION 3.1.3)
# 2. 声明工程名称
project(gateway_server)
set(target "gateway_server")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 3. 检测并生成ODB框架代码
# 1. 添加所需的proto映射代码文件名称
set(proto_path ${CMAKE_CURRENT_SOURCE_DIR}/../proto)
set(proto_files base.proto user.proto file.proto friend.proto gateway.proto message.proto notify.proto speech.proto transmite.proto )
# 2. 检测框架代码文件是否已经生成
set(proto_hxx "")
set(proto_cxx "")
set(proto_srcs "")
foreach(proto_file ${proto_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".proto" ".pb.cc" proto_cc ${proto_file})
string(REPLACE ".proto" ".pb.h" proto_hh ${proto_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${proto_cc})
add_custom_command(
PRE_BUILD
COMMAND protoc
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I ${proto_path} --experimental_allow_proto3_optional ${proto_path}/${proto_file}
DEPENDS ${proto_path}/${proto_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
COMMENT "生成Protobuf框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
)
endif()
list(APPEND proto_srcs ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc})
endforeach()
# 4. 获取源码目录下的所有源码文件
set(src_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files)
# 5. 声明目标及依赖
add_executable(${target} ${src_files} ${proto_srcs} ${odb_srcs})
# 7. 设置需要连接的库
target_link_libraries(${target} -lgflags
-lspdlog -lfmt -lbrpc -lssl -lcrypto
-lprotobuf -lleveldb -letcd-cpp-api
-lodb-mysql -lodb -lodb-boost
-lhiredis -lredis++
-lcpprest -lcurl
-lpthread -lboost_system)
# 6. 设置头文件默认搜索路径
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third/include)
#8. 设置安装路径
INSTALL(TARGETS ${target} RUNTIME DESTINATION bin)

16
gateway/dockerfile Normal file
View File

@ -0,0 +1,16 @@
# 声明基础经镜像来源
FROM debian:12
# 声明工作目录
WORKDIR /im
RUN mkdir -p /im/logs &&\
mkdir -p /im/data &&\
mkdir -p /im/conf &&\
mkdir -p /im/bin
# 将可执行程序依赖,拷贝进镜像
COPY ./build/gateway_server /im/bin/
# 将可执行程序文件,拷贝进镜像
COPY ./depends /lib/x86_64-linux-gnu/
# 设置容器启动的默认操作 ---运行程序
CMD /im/bin/gateway_server -flagfile=/im/conf/gateway_server.conf

View File

@ -0,0 +1,17 @@
-run_mode=true
-log_file=/im/logs/gateway.log
-log_level=0
-http_listen_port=9000
-websocket_listen_port=9001
-registry_host=http://10.0.0.235:2379
-base_service=/service
-file_service=/service/file_service
-friend_service=/service/friend_service
-message_service=/service/message_service
-user_service=/service/user_service
-speech_service=/service/speech_service
-transmite_service=/service/transmite_service
-redis_host=10.0.0.235
-redis_port=6379
-redis_db=0
-redis_keep_alive=true

View File

@ -0,0 +1,64 @@
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include "logger.hpp"
namespace bite_im {
typedef websocketpp::server<websocketpp::config::asio> server_t;
// 连接的类型: server_t::connection_ptr
class Connection {
public:
struct Client {
Client(const std::string &u, const std::string &s):uid(u), ssid(s){}
std::string uid;
std::string ssid;
};
using ptr = std::shared_ptr<Connection>;
Connection(){}
~Connection() {}
void insert(const server_t::connection_ptr &conn,
const std::string &uid, const std::string &ssid) {
std::unique_lock<std::mutex> lock(_mutex);
_uid_connections.insert(std::make_pair(uid, conn));
_conn_clients.insert(std::make_pair(conn, Client(uid, ssid)));
LOG_DEBUG("新增长连接用户信息:{}-{}-{}", (size_t)conn.get(), uid, ssid);
}
server_t::connection_ptr connection(const std::string &uid) {
std::unique_lock<std::mutex> lock(_mutex);
auto it = _uid_connections.find(uid);
if (it == _uid_connections.end()) {
LOG_ERROR("未找到 {} 客户端的长连接!", uid);
return server_t::connection_ptr();
}
LOG_DEBUG("找到 {} 客户端的长连接!", uid);
return it->second;
}
bool client(const server_t::connection_ptr &conn, std::string &uid, std::string &ssid) {
std::unique_lock<std::mutex> lock(_mutex);
auto it = _conn_clients.find(conn);
if (it == _conn_clients.end()) {
LOG_ERROR("获取-未找到长连接 {} 对应的客户端信息!", (size_t)conn.get());
return false;
}
uid = it->second.uid;
ssid = it->second.ssid;
LOG_DEBUG("获取长连接客户端信息成功!");
return true;
}
void remove(const server_t::connection_ptr &conn) {
std::unique_lock<std::mutex> lock(_mutex);
auto it = _conn_clients.find(conn);
if (it == _conn_clients.end()) {
LOG_ERROR("删除-未找到长连接 {} 对应的客户端信息!", (size_t)conn.get());
return;
}
_uid_connections.erase(it->second.uid);
_conn_clients.erase(it);
LOG_DEBUG("删除长连接信息完毕!");
}
private:
std::mutex _mutex;
std::unordered_map<std::string, server_t::connection_ptr> _uid_connections;
std::unordered_map<server_t::connection_ptr, Client> _conn_clients;
};
}

View File

@ -0,0 +1,39 @@
//主要实现语音识别子服务的服务器的搭建
#include "gateway_server.hpp"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_int32(http_listen_port, 9000, "HTTP服务器监听端口");
DEFINE_int32(websocket_listen_port, 9001, "Websocket服务器监听端口");
DEFINE_string(registry_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(file_service, "/service/file_service", "文件存储子服务名称");
DEFINE_string(friend_service, "/service/friend_service", "好友管理子服务名称");
DEFINE_string(message_service, "/service/message_service", "消息存储子服务名称");
DEFINE_string(user_service, "/service/user_service", "用户管理子服务名称");
DEFINE_string(speech_service, "/service/speech_service", "语音识别子服务名称");
DEFINE_string(transmite_service, "/service/transmite_service", "转发管理子服务名称");
DEFINE_string(redis_host, "127.0.0.1", "Redis服务器访问地址");
DEFINE_int32(redis_port, 6379, "Redis服务器访问端口");
DEFINE_int32(redis_db, 0, "Redis默认库号");
DEFINE_bool(redis_keep_alive, true, "Redis长连接保活选项");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
bite_im::GatewayServerBuilder gsb;
gsb.make_redis_object(FLAGS_redis_host, FLAGS_redis_port, FLAGS_redis_db, FLAGS_redis_keep_alive);
gsb.make_discovery_object(FLAGS_registry_host, FLAGS_base_service, FLAGS_file_service,
FLAGS_speech_service, FLAGS_message_service, FLAGS_friend_service,
FLAGS_user_service, FLAGS_transmite_service);
gsb.make_server_object(FLAGS_websocket_listen_port, FLAGS_http_listen_port);
auto server = gsb.build();
server->start();
return 0;
}

File diff suppressed because it is too large Load Diff

89
message/CMakeLists.txt Normal file
View File

@ -0,0 +1,89 @@
# 1. 添加cmake版本说明
cmake_minimum_required(VERSION 3.1.3)
# 2. 声明工程名称
project(message_server)
set(target "message_server")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 3. 检测并生成ODB框架代码
# 1. 添加所需的proto映射代码文件名称
set(proto_path ${CMAKE_CURRENT_SOURCE_DIR}/../proto)
set(proto_files base.proto user.proto file.proto message.proto)
# 2. 检测框架代码文件是否已经生成
set(proto_hxx "")
set(proto_cxx "")
set(proto_srcs "")
foreach(proto_file ${proto_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".proto" ".pb.cc" proto_cc ${proto_file})
string(REPLACE ".proto" ".pb.h" proto_hh ${proto_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${proto_cc})
add_custom_command(
PRE_BUILD
COMMAND protoc
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I ${proto_path} --experimental_allow_proto3_optional ${proto_path}/${proto_file}
DEPENDS ${proto_path}/${proto_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
COMMENT "生成Protobuf框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
)
endif()
list(APPEND proto_srcs ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc})
endforeach()
# 3. 检测并生成ODB框架代码
# 1. 添加所需的odb映射代码文件名称
set(odb_path ${CMAKE_CURRENT_SOURCE_DIR}/../odb)
set(odb_files message.hxx)
# 2. 检测框架代码文件是否已经生成
set(odb_hxx "")
set(odb_cxx "")
set(odb_srcs "")
foreach(odb_file ${odb_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".hxx" "-odb.hxx" odb_hxx ${odb_file})
string(REPLACE ".hxx" "-odb.cxx" odb_cxx ${odb_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${odb_cxx})
add_custom_command(
PRE_BUILD
COMMAND odb
ARGS -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time ${odb_path}/${odb_file}
DEPENDS ${odb_path}/${odb_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
COMMENT "生成ODB框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
)
endif()
# 4. 将所有生成的框架源码文件名称保存起来 student-odb.cxx classes-odb.cxx
list(APPEND odb_srcs ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx})
endforeach()
# 4. 获取源码目录下的所有源码文件
set(src_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files)
# 5. 声明目标及依赖
add_executable(${target} ${src_files} ${proto_srcs} ${odb_srcs})
# 7. 设置需要连接的库
target_link_libraries(${target} -lgflags
-lspdlog -lfmt -lbrpc -lssl -lcrypto
-lprotobuf -lleveldb -letcd-cpp-api
-lcpprest -lcurl -lodb-mysql -lodb -lodb-boost
/usr/lib/x86_64-linux-gnu/libjsoncpp.so.19
-lcpr -lelasticlient
-lamqpcpp -lev)
set(test_client "message_client")
set(test_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/test test_files)
add_executable(${test_client} ${test_files} ${proto_srcs})
target_link_libraries(${test_client} -pthread -lgtest -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
# 6. 设置头文件默认搜索路径
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../odb)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third/include)
#8. 设置安装路径
INSTALL(TARGETS ${target} ${test_client} RUNTIME DESTINATION bin)

16
message/dockerfile Normal file
View File

@ -0,0 +1,16 @@
# 声明基础经镜像来源
FROM debian:12
# 声明工作目录
WORKDIR /im
RUN mkdir -p /im/logs &&\
mkdir -p /im/data &&\
mkdir -p /im/conf &&\
mkdir -p /im/bin
# 将可执行程序依赖,拷贝进镜像
COPY ./build/message_server /im/bin/
# 将可执行程序文件,拷贝进镜像
COPY ./depends /lib/x86_64-linux-gnu/
# 设置容器启动的默认操作 ---运行程序
CMD /im/bin/message_server -flagfile=/im/conf/message_server.conf

View File

@ -0,0 +1,26 @@
-run_mode=true
-log_file=/im/logs/message.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/message_service/instance
-access_host=10.0.0.235:10005
-listen_port=10005
-rpc_timeout=-1
-rpc_threads=1
-base_service=/service
-user_service=/service/user_service
-file_service=/service/file_service
-es_host=http://10.0.0.235:9200/
-mysql_host=10.0.0.235
-mysql_user=root
-mysql_pswd=123456
-mysql_db=bite_im
-mysql_cset=utf8
-mysql_port=0
-mysql_pool_count=4
-mq_user=root
-mq_pswd=123456
-mq_host=10.0.0.235:5672
-mq_msg_exchange=msg_exchange
-mq_msg_queue=msg_queue
-mq_msg_binding_key=msg_queue

View File

@ -0,0 +1,56 @@
//主要实现语音识别子服务的服务器的搭建
#include "message_server.hpp"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(registry_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(instance_name, "/message_service/instance", "当前实例名称");
DEFINE_string(access_host, "127.0.0.1:10005", "当前实例的外部访问地址");
DEFINE_int32(listen_port, 10005, "Rpc服务器监听端口");
DEFINE_int32(rpc_timeout, -1, "Rpc调用超时时间");
DEFINE_int32(rpc_threads, 1, "Rpc的IO线程数量");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(file_service, "/service/file_service", "文件管理子服务名称");
DEFINE_string(user_service, "/service/user_service", "用户管理子服务名称");
DEFINE_string(es_host, "http://127.0.0.1:9200/", "ES搜索引擎服务器URL");
DEFINE_string(mysql_host, "127.0.0.1", "Mysql服务器访问地址");
DEFINE_string(mysql_user, "root", "Mysql服务器访问用户名");
DEFINE_string(mysql_pswd, "123456", "Mysql服务器访问密码");
DEFINE_string(mysql_db, "bite_im", "Mysql默认库名称");
DEFINE_string(mysql_cset, "utf8", "Mysql客户端字符集");
DEFINE_int32(mysql_port, 0, "Mysql服务器访问端口");
DEFINE_int32(mysql_pool_count, 4, "Mysql连接池最大连接数量");
DEFINE_string(mq_user, "root", "消息队列服务器访问用户名");
DEFINE_string(mq_pswd, "123456", "消息队列服务器访问密码");
DEFINE_string(mq_host, "127.0.0.1:5672", "消息队列服务器访问地址");
DEFINE_string(mq_msg_exchange, "msg_exchange", "持久化消息的发布交换机名称");
DEFINE_string(mq_msg_queue, "msg_queue", "持久化消息的发布队列名称");
DEFINE_string(mq_msg_binding_key, "msg_queue", "持久化消息的发布队列名称");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
bite_im::MessageServerBuilder msb;
msb.make_mq_object(FLAGS_mq_user, FLAGS_mq_pswd, FLAGS_mq_host,
FLAGS_mq_msg_exchange, FLAGS_mq_msg_queue, FLAGS_mq_msg_binding_key);
msb.make_es_object({FLAGS_es_host});
msb.make_mysql_object(FLAGS_mysql_user, FLAGS_mysql_pswd, FLAGS_mysql_host,
FLAGS_mysql_db, FLAGS_mysql_cset, FLAGS_mysql_port, FLAGS_mysql_pool_count);
msb.make_discovery_object(FLAGS_registry_host, FLAGS_base_service, FLAGS_file_service, FLAGS_user_service);
msb.make_rpc_server(FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads);
msb.make_registry_object(FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host);
auto server = msb.build();
server->start();
return 0;
}

View File

@ -0,0 +1,579 @@
//实现语音识别子服务
#include <brpc/server.h>
#include <butil/logging.h>
#include "data_es.hpp" // es数据管理客户端封装
#include "mysql_message.hpp" // mysql数据管理客户端封装
#include "etcd.hpp" // 服务注册模块封装
#include "logger.hpp" // 日志模块封装
#include "utils.hpp" // 基础工具接口
#include "channel.hpp" // 信道管理模块封装
#include "rabbitmq.hpp"
#include "message.pb.h" // protobuf框架代码
#include "base.pb.h" // protobuf框架代码
#include "file.pb.h" // protobuf框架代码
#include "user.pb.h" // protobuf框架代码
namespace bite_im{
class MessageServiceImpl : public bite_im::MsgStorageService {
public:
MessageServiceImpl(
const std::shared_ptr<elasticlient::Client> &es_client,
const std::shared_ptr<odb::core::database> &mysql_client,
const ServiceManager::ptr &channel_manager,
const std::string &file_service_name,
const std::string &user_service_name) :
_es_message(std::make_shared<ESMessage>(es_client)),
_mysql_message(std::make_shared<MessageTable>(mysql_client)),
_file_service_name(file_service_name),
_user_service_name(user_service_name),
_mm_channels(channel_manager){
_es_message->createIndex();
}
~MessageServiceImpl(){}
virtual void GetHistoryMsg(::google::protobuf::RpcController* controller,
const ::bite_im::GetHistoryMsgReq* request,
::bite_im::GetHistoryMsgRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取关键要素会话ID起始时间结束时间
std::string rid = request->request_id();
std::string chat_ssid = request->chat_session_id();
boost::posix_time::ptime stime = boost::posix_time::from_time_t(request->start_time());
boost::posix_time::ptime etime = boost::posix_time::from_time_t(request->over_time());
//2. 从数据库中进行消息查询
auto msg_lists = _mysql_message->range(chat_ssid, stime, etime);
if (msg_lists.empty()) {
response->set_request_id(rid);
response->set_success(true);
return ;
}
//3. 统计所有文件类型消息的文件ID并从文件子服务进行批量文件下载
std::unordered_set<std::string> file_id_lists;
for (const auto &msg : msg_lists) {
if (msg.file_id().empty()) continue;
LOG_DEBUG("需要下载的文件ID {}", msg.file_id());
file_id_lists.insert(msg.file_id());
}
std::unordered_map<std::string, std::string> file_data_lists;
bool ret = _GetFile(rid, file_id_lists, file_data_lists);
if (ret == false) {
LOG_ERROR("{} 批量文件数据下载失败!", rid);
return err_response(rid, "批量文件数据下载失败!");
}
//4. 统计所有消息的发送者用户ID从用户子服务进行批量用户信息获取
std::unordered_set<std::string> user_id_lists; // {猪爸爸吧, 祝妈妈,猪爸爸吧,祝爸爸}
for (const auto &msg : msg_lists) {
user_id_lists.insert(msg.user_id());
}
std::unordered_map<std::string, UserInfo> user_lists;
ret = _GetUser(rid, user_id_lists, user_lists);
if (ret == false) {
LOG_ERROR("{} 批量用户数据获取失败!", rid);
return err_response(rid, "批量用户数据获取失败!");
}
//5. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto &msg : msg_lists) {
auto message_info = response->add_msg_list();
message_info->set_message_id(msg.message_id());
message_info->set_chat_session_id(msg.session_id());
message_info->set_timestamp(boost::posix_time::to_time_t(msg.create_time()));
message_info->mutable_sender()->CopyFrom(user_lists[msg.user_id()]);
switch(msg.message_type()) {
case MessageType::STRING:
message_info->mutable_message()->set_message_type(MessageType::STRING);
message_info->mutable_message()->mutable_string_message()->set_content(msg.content());
break;
case MessageType::IMAGE:
message_info->mutable_message()->set_message_type(MessageType::IMAGE);
message_info->mutable_message()->mutable_image_message()->set_file_id(msg.file_id());
message_info->mutable_message()->mutable_image_message()->set_image_content(file_data_lists[msg.file_id()]);
break;
case MessageType::FILE:
message_info->mutable_message()->set_message_type(MessageType::FILE);
message_info->mutable_message()->mutable_file_message()->set_file_id(msg.file_id());
message_info->mutable_message()->mutable_file_message()->set_file_size(msg.file_size());
message_info->mutable_message()->mutable_file_message()->set_file_name(msg.file_name());
message_info->mutable_message()->mutable_file_message()->set_file_contents(file_data_lists[msg.file_id()]);
break;
case MessageType::SPEECH:
message_info->mutable_message()->set_message_type(MessageType::SPEECH);
message_info->mutable_message()->mutable_speech_message()->set_file_id(msg.file_id());
message_info->mutable_message()->mutable_speech_message()->set_file_contents(file_data_lists[msg.file_id()]);
break;
default:
LOG_ERROR("消息类型错误!!");
return;
}
}
return;
}
virtual void GetRecentMsg(::google::protobuf::RpcController* controller,
const ::bite_im::GetRecentMsgReq* request,
::bite_im::GetRecentMsgRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//1. 提取请求中的关键要素请求ID会话ID要获取的消息数量
std::string rid = request->request_id();
std::string chat_ssid = request->chat_session_id();
int msg_count = request->msg_count();
//2. 从数据库,获取最近的消息元信息
auto msg_lists = _mysql_message->recent(chat_ssid, msg_count);
if (msg_lists.empty()) {
response->set_request_id(rid);
response->set_success(true);
return ;
}
//3. 统计所有消息中文件类型消息的文件ID列表从文件子服务下载文件
std::unordered_set<std::string> file_id_lists;
for (const auto &msg : msg_lists) {
if (msg.file_id().empty()) continue;
LOG_DEBUG("需要下载的文件ID: {}", msg.file_id());
file_id_lists.insert(msg.file_id());
}
std::unordered_map<std::string, std::string> file_data_lists;
bool ret = _GetFile(rid, file_id_lists, file_data_lists);
if (ret == false) {
LOG_ERROR("{} 批量文件数据下载失败!", rid);
return err_response(rid, "批量文件数据下载失败!");
}
//4. 统计所有消息的发送者用户ID从用户子服务进行批量用户信息获取
std::unordered_set<std::string> user_id_lists;
for (const auto &msg : msg_lists) {
user_id_lists.insert(msg.user_id());
}
std::unordered_map<std::string, UserInfo> user_lists;
ret = _GetUser(rid, user_id_lists, user_lists);
if (ret == false) {
LOG_ERROR("{} 批量用户数据获取失败!", rid);
return err_response(rid, "批量用户数据获取失败!");
}
//5. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto &msg : msg_lists) {
auto message_info = response->add_msg_list();
message_info->set_message_id(msg.message_id());
message_info->set_chat_session_id(msg.session_id());
message_info->set_timestamp(boost::posix_time::to_time_t(msg.create_time()));
message_info->mutable_sender()->CopyFrom(user_lists[msg.user_id()]);
switch(msg.message_type()) {
case MessageType::STRING:
message_info->mutable_message()->set_message_type(MessageType::STRING);
message_info->mutable_message()->mutable_string_message()->set_content(msg.content());
break;
case MessageType::IMAGE:
message_info->mutable_message()->set_message_type(MessageType::IMAGE);
message_info->mutable_message()->mutable_image_message()->set_file_id(msg.file_id());
message_info->mutable_message()->mutable_image_message()->set_image_content(file_data_lists[msg.file_id()]);
break;
case MessageType::FILE:
message_info->mutable_message()->set_message_type(MessageType::FILE);
message_info->mutable_message()->mutable_file_message()->set_file_id(msg.file_id());
message_info->mutable_message()->mutable_file_message()->set_file_size(msg.file_size());
message_info->mutable_message()->mutable_file_message()->set_file_name(msg.file_name());
message_info->mutable_message()->mutable_file_message()->set_file_contents(file_data_lists[msg.file_id()]);
break;
case MessageType::SPEECH:
message_info->mutable_message()->set_message_type(MessageType::SPEECH);
message_info->mutable_message()->mutable_speech_message()->set_file_id(msg.file_id());
message_info->mutable_message()->mutable_speech_message()->set_file_contents(file_data_lists[msg.file_id()]);
break;
default:
LOG_ERROR("消息类型错误!!");
return;
}
}
return;
}
virtual void MsgSearch(::google::protobuf::RpcController* controller,
const ::bite_im::MsgSearchReq* request,
::bite_im::MsgSearchRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
auto err_response = [this, response](const std::string &rid,
const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//关键字的消息搜索--只针对文本消息
//1. 从请求中提取关键要素请求ID会话ID, 关键字
std::string rid = request->request_id();
std::string chat_ssid = request->chat_session_id();
std::string skey = request->search_key();
//2. 从ES搜索引擎中进行关键字消息搜索得到消息列表
auto msg_lists = _es_message->search(skey, chat_ssid);
if (msg_lists.empty()) {
response->set_request_id(rid);
response->set_success(true);
return ;
}
//3. 组织所有消息的用户ID从用户子服务获取用户信息
std::unordered_set<std::string> user_id_lists;
for (const auto &msg : msg_lists) {
user_id_lists.insert(msg.user_id());
}
std::unordered_map<std::string, UserInfo> user_lists;
bool ret = _GetUser(rid, user_id_lists, user_lists);
if (ret == false) {
LOG_ERROR("{} 批量用户数据获取失败!", rid);
return err_response(rid, "批量用户数据获取失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto &msg : msg_lists) {
auto message_info = response->add_msg_list();
message_info->set_message_id(msg.message_id());
message_info->set_chat_session_id(msg.session_id());
message_info->set_timestamp(boost::posix_time::to_time_t(msg.create_time()));
message_info->mutable_sender()->CopyFrom(user_lists[msg.user_id()]);
message_info->mutable_message()->set_message_type(MessageType::STRING);
message_info->mutable_message()->mutable_string_message()->set_content(msg.content());
}
return;
}
//rabbitmq获得消息
void onMessage(const char *body, size_t sz) {
LOG_DEBUG("收到新消息,进行存储处理!");
//1. 取出序列化的消息内容,进行反序列化
bite_im::MessageInfo message;
bool ret = message.ParseFromArray(body, sz);
if (ret == false) {
LOG_ERROR("对消费到的消息进行反序列化失败!");
return;
}
//2. 根据不同的消息类型进行不同的处理
std::string file_id, file_name, content;
int64_t file_size;
switch(message.message().message_type()) {
// 1. 如果是一个文本类型消息取元信息存储到ES中
case MessageType::STRING:
content = message.message().string_message().content();
ret = _es_message->appendData(
message.sender().user_id(),
message.message_id(),
message.timestamp(),
message.chat_session_id(),
content);
if (ret == false) {
LOG_ERROR("文本消息向存储引擎进行存储失败!");
return;
}
break;
// 2. 如果是一个图片/语音/文件消息则取出数据存储到文件子服务中并获取文件ID
case MessageType::IMAGE:
{
const auto &msg = message.message().image_message();
ret = _PutFile("", msg.image_content(), msg.image_content().size(), file_id);
if (ret == false) {
LOG_ERROR("上传图片到文件子服务失败!");
return ;
}
}
break;
case MessageType::FILE:
{
const auto &msg = message.message().file_message();
file_name = msg.file_name();
file_size = msg.file_size();
ret = _PutFile(file_name, msg.file_contents(), file_size, file_id);
if (ret == false) {
LOG_ERROR("上传文件到文件子服务失败!");
return ;
}
}
break;
case MessageType::SPEECH:
{
const auto &msg = message.message().speech_message();
ret = _PutFile("", msg.file_contents(), msg.file_contents().size(), file_id);
if (ret == false) {
LOG_ERROR("上传语音到文件子服务失败!");
return ;
}
}
break;
default:
LOG_ERROR("消息类型错误!");
return;
}
//3. 提取消息的元信息存储到mysql数据库中
bite_im::Message msg(message.message_id(),
message.chat_session_id(),
message.sender().user_id(),
message.message().message_type(),
boost::posix_time::from_time_t(message.timestamp()));
msg.content(content);
msg.file_id(file_id);
msg.file_name(file_name);
msg.file_size(file_size);
ret = _mysql_message->insert(msg);
if (ret == false) {
LOG_ERROR("向数据库插入新消息失败!");
return;
}
}
private:
bool _GetUser(const std::string &rid,
const std::unordered_set<std::string> &user_id_lists,
std::unordered_map<std::string, UserInfo> &user_lists) {
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 没有可供访问的用户子服务节点!", _user_service_name);
return false;
}
UserService_Stub stub(channel.get());
GetMultiUserInfoReq req;
GetMultiUserInfoRsp rsp;
req.set_request_id(rid);
for (const auto &id : user_id_lists) {
req.add_users_id(id);
}
brpc::Controller cntl;
stub.GetMultiUserInfo(&cntl, &req, &rsp, nullptr);
if (cntl.Failed() == true || rsp.success() == false) {
LOG_ERROR("用户子服务调用失败:{}", cntl.ErrorText());
return false;
}
const auto &umap = rsp.users_info();
for (auto it = umap.begin(); it != umap.end(); ++it) {
user_lists.insert(std::make_pair(it->first, it->second));
}
return true;
}
bool _GetFile(const std::string &rid,
const std::unordered_set<std::string> &file_id_lists,
std::unordered_map<std::string, std::string> &file_data_lists) {
auto channel = _mm_channels->choose(_file_service_name);
if (!channel) {
LOG_ERROR("{} 没有可供访问的文件子服务节点!", _file_service_name);
return false;
}
FileService_Stub stub(channel.get());
GetMultiFileReq req;
GetMultiFileRsp rsp;
req.set_request_id(rid);
for (const auto &id : file_id_lists) {
req.add_file_id_list(id);
}
brpc::Controller cntl;
stub.GetMultiFile(&cntl, &req, &rsp, nullptr);
if (cntl.Failed() == true || rsp.success() == false) {
LOG_ERROR("文件子服务调用失败:{}", cntl.ErrorText());
return false;
}
const auto &fmap = rsp.file_data();
for (auto it = fmap.begin(); it != fmap.end(); ++it) {
file_data_lists.insert(std::make_pair(it->first, it->second.file_content()));
}
return true;
}
bool _PutFile(const std::string &filename,
const std::string &body,
const int64_t fsize,
std::string &file_id) {
//实现文件数据的上传
auto channel = _mm_channels->choose(_file_service_name);
if (!channel) {
LOG_ERROR("{} 没有可供访问的文件子服务节点!", _file_service_name);
return false;
}
FileService_Stub stub(channel.get());
PutSingleFileReq req;
PutSingleFileRsp rsp;
req.mutable_file_data()->set_file_name(filename);
req.mutable_file_data()->set_file_size(fsize);
req.mutable_file_data()->set_file_content(body);
brpc::Controller cntl;
stub.PutSingleFile(&cntl, &req, &rsp, nullptr);
if (cntl.Failed() == true || rsp.success() == false) {
LOG_ERROR("文件子服务调用失败:{}", cntl.ErrorText());
return false;
}
file_id = rsp.file_info().file_id();
return true;
}
private:
ESMessage::ptr _es_message;
MessageTable::ptr _mysql_message;
//这边是rpc调用客户端相关对象
std::string _user_service_name;
std::string _file_service_name;
ServiceManager::ptr _mm_channels;
};
class MessageServer {
public:
using ptr = std::shared_ptr<MessageServer>;
MessageServer(const MQClient::ptr &mq_client,
const Discovery::ptr service_discoverer,
const Registry::ptr &reg_client,
const std::shared_ptr<elasticlient::Client> &es_client,
const std::shared_ptr<odb::core::database> &mysql_client,
const std::shared_ptr<brpc::Server> &server):
_mq_client(mq_client),
_service_discoverer(service_discoverer),
_registry_client(reg_client),
_es_client(es_client),
_mysql_client(mysql_client),
_rpc_server(server){}
~MessageServer(){}
//搭建RPC服务器并启动服务器
void start() {
_rpc_server->RunUntilAskedToQuit();
}
private:
Discovery::ptr _service_discoverer;
Registry::ptr _registry_client;
MQClient::ptr _mq_client;
std::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
class MessageServerBuilder {
public:
//构造es客户端对象
void make_es_object(const std::vector<std::string> host_list) {
_es_client = ESClientFactory::create(host_list);
}
//构造mysql客户端对象
void make_mysql_object(
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) {
_mysql_client = ODBFactory::create(user, pswd, host, db, cset, port, conn_pool_count);
}
//用于构造服务发现客户端&信道管理对象
void make_discovery_object(const std::string &reg_host,
const std::string &base_service_name,
const std::string &file_service_name,
const std::string &user_service_name) {
_user_service_name = user_service_name;
_file_service_name = file_service_name;
_mm_channels = std::make_shared<ServiceManager>();
_mm_channels->declared(file_service_name);
_mm_channels->declared(user_service_name);
LOG_DEBUG("设置文件子服务为需添加管理的子服务:{}", file_service_name);
auto put_cb = std::bind(&ServiceManager::onServiceOnline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&ServiceManager::onServiceOffline, _mm_channels.get(), std::placeholders::_1, std::placeholders::_2);
_service_discoverer = std::make_shared<Discovery>(reg_host, base_service_name, put_cb, del_cb);
}
//用于构造服务注册客户端对象
void make_registry_object(const std::string &reg_host,
const std::string &service_name,
const std::string &access_host) {
_registry_client = std::make_shared<Registry>(reg_host);
_registry_client->registry(service_name, access_host);
}
//用于构造消息队列客户端对象
void make_mq_object(const std::string &user,
const std::string &passwd,
const std::string &host,
const std::string &exchange_name,
const std::string &queue_name,
const std::string &binding_key) {
_exchange_name = exchange_name;
_queue_name = queue_name;
_mq_client = std::make_shared<MQClient>(user, passwd, host);
_mq_client->declareComponents(exchange_name, queue_name, binding_key);
}
void make_rpc_server(uint16_t port, int32_t timeout, uint8_t num_threads) {
if (!_es_client) {
LOG_ERROR("还未初始化ES搜索引擎模块");
abort();
}
if (!_mysql_client) {
LOG_ERROR("还未初始化Mysql数据库模块");
abort();
}
if (!_mm_channels) {
LOG_ERROR("还未初始化信道管理模块!");
abort();
}
_rpc_server = std::make_shared<brpc::Server>();
MessageServiceImpl *msg_service = new MessageServiceImpl(_es_client,
_mysql_client, _mm_channels, _file_service_name, _user_service_name);
int ret = _rpc_server->AddService(msg_service,
brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
if (ret == -1) {
LOG_ERROR("添加Rpc服务失败");
abort();
}
brpc::ServerOptions options;
options.idle_timeout_sec = timeout;
options.num_threads = num_threads;
ret = _rpc_server->Start(port, &options);
if (ret == -1) {
LOG_ERROR("服务启动失败!");
abort();
}
auto callback = std::bind(&MessageServiceImpl::onMessage, msg_service,
std::placeholders::_1, std::placeholders::_2);
_mq_client->consume(_queue_name, callback);
}
//构造RPC服务器对象
MessageServer::ptr build() {
if (!_service_discoverer) {
LOG_ERROR("还未初始化服务发现模块!");
abort();
}
if (!_registry_client) {
LOG_ERROR("还未初始化服务注册模块!");
abort();
}
if (!_rpc_server) {
LOG_ERROR("还未初始化RPC服务器模块");
abort();
}
MessageServer::ptr server = std::make_shared<MessageServer>(
_mq_client, _service_discoverer, _registry_client,
_es_client, _mysql_client, _rpc_server);
return server;
}
private:
Registry::ptr _registry_client;
std::shared_ptr<elasticlient::Client> _es_client;
std::shared_ptr<odb::core::database> _mysql_client;
std::string _user_service_name;
std::string _file_service_name;
ServiceManager::ptr _mm_channels;
Discovery::ptr _service_discoverer;
std::string _exchange_name;
std::string _queue_name;
MQClient::ptr _mq_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
}

View File

@ -0,0 +1,34 @@
#include "../../../common/data_es.hpp"
#include <gflags/gflags.h>
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(es_host, "http://127.0.0.1:9200/", "es服务器URL");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
auto es_client = bite_im::ESClientFactory::create({FLAGS_es_host});
auto es_msg = std::make_shared<bite_im::ESMessage>(es_client);
// es_msg->createIndex();
// es_msg->appendData("用户ID1", "消息ID1", 1723025035, "会话ID1", "吃饭了吗?");
// es_msg->appendData("用户ID2", "消息ID2", 1723025035 - 100, "会话ID1", "吃的盖浇饭!");
// es_msg->appendData("用户ID3", "消息ID3", 1723025035, "会话ID2", "吃饭了吗?");
// es_msg->appendData("用户ID4", "消息ID4", 1723025035 - 100, "会话ID2", "吃的盖浇饭!");
auto res = es_msg->search("盖浇", "会话ID1");
for (auto &u : res) {
std::cout << "-----------------" << std::endl;
std::cout << u.user_id() << std::endl;
std::cout << u.message_id() << std::endl;
std::cout << u.session_id() << std::endl;
std::cout << boost::posix_time::to_simple_string(u.create_time()) << std::endl;
std::cout << u.content() << std::endl;
}
return 0;
}

View File

@ -0,0 +1,72 @@
POST /message/_doc
{
"settings" : {
"analysis" : {
"analyzer" : {
"ik" : {
"tokenizer" : "ik_max_word"
}
}
}
},
"mappings" : {
"dynamic" : true,
"properties" : {
"chat_session_id" : {
"type" : "keyword",
"analyzer" : "standard"
},
"message_id" : {
"type" : "keyword",
"analyzer" : "standard"
},
"content" : {
"type" : "text",
"analyzer" : "ik_max_word"
}
}
}
}
GET /message/_doc/_search?pretty
{
"query": {
"match_all": {}
}
}
POST /message/_doc/_bulk
{"index":{"_id":"1"}}
{"chat_session_id" : "会话ID1","message_id" : "消息ID1","content" : "吃饭了么?"}
{"index":{"_id":"2"}}
{"chat_session_id" : "会话ID1","message_id" : "消息ID2","content" : "吃的盖浇饭。"}
{"index":{"_id":"3"}}
{"chat_session_id" : "会话ID2","message_id" : "消息ID3","content" : "昨天吃饭了么?"}
{"index":{"_id":"4"}}
{"chat_session_id" : "会话ID2","message_id" : "消息ID4","content" : "昨天吃的盖浇饭。"}
GET /message/_doc/_search?pretty
{
"query": {
"bool": {
"must": [
{
"term": {
"chat_session_id.keyword": "会话ID1"
}
},
{
"match": {
"content": "盖浇饭"
}
}
]
}
}
}
DELETE /message

View File

@ -0,0 +1,170 @@
#include "etcd.hpp"
#include "channel.hpp"
#include "utils.hpp"
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include <thread>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "message.pb.h"
#include "base.pb.h"
#include "user.pb.h"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(etcd_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(message_service, "/service/message_service", "服务监控根目录");
bite_im::ServiceManager::ptr sm;
void range_test(const std::string &ssid,
const boost::posix_time::ptime &stime,
const boost::posix_time::ptime &etime) {
auto channel = sm->choose(FLAGS_message_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::MsgStorageService_Stub stub(channel.get());
bite_im::GetHistoryMsgReq req;
bite_im::GetHistoryMsgRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_chat_session_id(ssid);
req.set_start_time(boost::posix_time::to_time_t(stime));
req.set_over_time(boost::posix_time::to_time_t(etime));
brpc::Controller cntl;
stub.GetHistoryMsg(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
std::cout << rsp.msg_list_size() << std::endl;
for (int i = 0; i < rsp.msg_list_size(); i++) {
std::cout << "-----------------------获取时间区间消息--------------------------\n";
auto msg = rsp.msg_list(i);
std::cout << msg.message_id() << std::endl;
std::cout << msg.chat_session_id() << std::endl;
std::cout << boost::posix_time::to_simple_string(boost::posix_time::from_time_t(msg.timestamp())) << std::endl;
std::cout << msg.sender().user_id() << std::endl;
std::cout << msg.sender().nickname() << std::endl;
std::cout << msg.sender().avatar() << std::endl;
if (msg.message().message_type() == bite_im::MessageType::STRING) {
std::cout << "文本消息:" << msg.message().string_message().content() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::IMAGE) {
std::cout << "图片消息:" << msg.message().image_message().image_content() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::FILE) {
std::cout << "文件消息:" << msg.message().file_message().file_contents() << std::endl;
std::cout << "文件名称:" << msg.message().file_message().file_name() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::SPEECH) {
std::cout << "语音消息:" << msg.message().speech_message().file_contents() << std::endl;
}else {
std::cout << "类型错误!!\n";
}
}
}
void recent_test(const std::string &ssid, int count) {
auto channel = sm->choose(FLAGS_message_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::MsgStorageService_Stub stub(channel.get());
bite_im::GetRecentMsgReq req;
bite_im::GetRecentMsgRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_chat_session_id(ssid);
req.set_msg_count(count);
brpc::Controller cntl;
stub.GetRecentMsg(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
for (int i = 0; i < rsp.msg_list_size(); i++) {
std::cout << "----------------------获取最近消息---------------------------\n";
auto msg = rsp.msg_list(i);
std::cout << msg.message_id() << std::endl;
std::cout << msg.chat_session_id() << std::endl;
std::cout << boost::posix_time::to_simple_string(boost::posix_time::from_time_t(msg.timestamp())) << std::endl;
std::cout << msg.sender().user_id() << std::endl;
std::cout << msg.sender().nickname() << std::endl;
std::cout << msg.sender().avatar() << std::endl;
if (msg.message().message_type() == bite_im::MessageType::STRING) {
std::cout << "文本消息:" << msg.message().string_message().content() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::IMAGE) {
std::cout << "图片消息:" << msg.message().image_message().image_content() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::FILE) {
std::cout << "文件消息:" << msg.message().file_message().file_contents() << std::endl;
std::cout << "文件名称:" << msg.message().file_message().file_name() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::SPEECH) {
std::cout << "语音消息:" << msg.message().speech_message().file_contents() << std::endl;
}else {
std::cout << "类型错误!!\n";
}
}
}
void search_test(const std::string &ssid, const std::string &key) {
auto channel = sm->choose(FLAGS_message_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
bite_im::MsgStorageService_Stub stub(channel.get());
bite_im::MsgSearchReq req;
bite_im::MsgSearchRsp rsp;
req.set_request_id(bite_im::uuid());
req.set_chat_session_id(ssid);
req.set_search_key(key);
brpc::Controller cntl;
stub.MsgSearch(&cntl, &req, &rsp, nullptr);
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
for (int i = 0; i < rsp.msg_list_size(); i++) {
std::cout << "----------------------关键字搜索消息---------------------------\n";
auto msg = rsp.msg_list(i);
std::cout << msg.message_id() << std::endl;
std::cout << msg.chat_session_id() << std::endl;
std::cout << boost::posix_time::to_simple_string(boost::posix_time::from_time_t(msg.timestamp())) << std::endl;
std::cout << msg.sender().user_id() << std::endl;
std::cout << msg.sender().nickname() << std::endl;
std::cout << msg.sender().avatar() << std::endl;
if (msg.message().message_type() == bite_im::MessageType::STRING) {
std::cout << "文本消息:" << msg.message().string_message().content() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::IMAGE) {
std::cout << "图片消息:" << msg.message().image_message().image_content() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::FILE) {
std::cout << "文件消息:" << msg.message().file_message().file_contents() << std::endl;
std::cout << "文件名称:" << msg.message().file_message().file_name() << std::endl;
}else if (msg.message().message_type() == bite_im::MessageType::SPEECH) {
std::cout << "语音消息:" << msg.message().speech_message().file_contents() << std::endl;
}else {
std::cout << "类型错误!!\n";
}
}
}
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
//1. 先构造Rpc信道管理对象
sm = std::make_shared<bite_im::ServiceManager>();
sm->declared(FLAGS_message_service);
auto put_cb = std::bind(&bite_im::ServiceManager::onServiceOnline, sm.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&bite_im::ServiceManager::onServiceOffline, sm.get(), std::placeholders::_1, std::placeholders::_2);
//2. 构造服务发现对象
bite_im::Discovery::ptr dclient = std::make_shared<bite_im::Discovery>(FLAGS_etcd_host, FLAGS_base_service, put_cb, del_cb);
boost::posix_time::ptime stime(boost::posix_time::time_from_string("2000-08-02 00:00:00"));
boost::posix_time::ptime etime(boost::posix_time::time_from_string("2050-08-09 00:00:00"));
range_test("会话ID1", stime, etime);
recent_test("会话ID1", 2);
search_test("会话ID1", "盖浇");
return 0;
}

View File

@ -0,0 +1,63 @@
#include "mysql_message.hpp"
#include <gflags/gflags.h>
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
void insert_test(bite_im::MessageTable &tb) {
bite_im::Message m1("消息ID1", "会话ID1", "用户ID1", 0, boost::posix_time::time_from_string("2002-01-20 23:59:59.000"));
tb.insert(m1);
bite_im::Message m2("消息ID2", "会话ID1", "用户ID2", 0, boost::posix_time::time_from_string("2002-01-21 23:59:59.000"));
tb.insert(m2);
bite_im::Message m3("消息ID3", "会话ID1", "用户ID3", 0, boost::posix_time::time_from_string("2002-01-22 23:59:59.000"));
tb.insert(m3);
//另一个会话
bite_im::Message m4("消息ID4", "会话ID2", "用户ID4", 0, boost::posix_time::time_from_string("2002-01-20 23:59:59.000"));
tb.insert(m4);
bite_im::Message m5("消息ID5", "会话ID2", "用户ID5", 0, boost::posix_time::time_from_string("2002-01-21 23:59:59.000"));
tb.insert(m5);
}
void remove_test(bite_im::MessageTable &tb) {
tb.remove("会话ID2");
}
void recent_test(bite_im::MessageTable &tb) {
auto res = tb.recent("会话ID1", 2);
auto begin = res.rbegin();
auto end = res.rend();
for (; begin != end; ++begin) {
std::cout << begin->message_id() << std::endl;
std::cout << begin->session_id() << std::endl;
std::cout << begin->user_id() << std::endl;
std::cout << boost::posix_time::to_simple_string(begin->create_time()) << std::endl;
}
}
void range_test(bite_im::MessageTable &tb) {
boost::posix_time::ptime stime(boost::posix_time::time_from_string("2002-01-20 23:59:59.000"));
boost::posix_time::ptime etime(boost::posix_time::time_from_string("2002-01-21 23:59:59.000"));
auto res = tb.range("会话ID1", stime, etime);
for (const auto &m : res) {
std::cout << m.message_id() << std::endl;
std::cout << m.session_id() << std::endl;
std::cout << m.user_id() << std::endl;
std::cout << boost::posix_time::to_simple_string(m.create_time()) << std::endl;
}
}
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
auto db = bite_im::ODBFactory::create("root", "123456", "127.0.0.1", "bite_im", "utf8", 0, 1);
bite_im::MessageTable tb(db);
// insert_test(tb);
// remove_test(tb);
// recent_test(tb);
// range_test(tb);
return 0;
}

68
odb/chat_session.hxx Normal file
View File

@ -0,0 +1,68 @@
#pragma once
#include <string>
#include <cstddef>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
#include "chat_session_member.hxx"
namespace bite_im {
enum class ChatSessionType {
SINGLE = 1,
GROUP = 2
};
#pragma db object table("chat_session")
class ChatSession {
public:
ChatSession(){}
ChatSession(const std::string &ssid,
const std::string &ssname, const ChatSessionType sstype):
_chat_session_id(ssid),
_chat_session_name(ssname),
_chat_session_type(sstype){}
std::string chat_session_id() const { return _chat_session_id; }
void chat_session_id(std::string &ssid) { _chat_session_id = ssid; }
std::string chat_session_name() const { return _chat_session_name; }
void chat_session_name(std::string &ssname) { _chat_session_name = ssname; }
ChatSessionType chat_session_type() const { return _chat_session_type; }
void chat_session_type(ChatSessionType val) { _chat_session_type = val; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)") index unique
std::string _chat_session_id;
#pragma db type("varchar(64)")
std::string _chat_session_name;
#pragma db type("tinyint")
ChatSessionType _chat_session_type; //1-单聊; 2-群聊
};
// 这里条件必须是指定条件: css::chat_session_type==1 && csm1.user_id=uid && csm2.user_id != csm1.user_id
#pragma db view object(ChatSession = css)\
object(ChatSessionMember = csm1 : css::_chat_session_id == csm1::_session_id)\
object(ChatSessionMember = csm2 : css::_chat_session_id == csm2::_session_id)\
query((?))
struct SingleChatSession {
#pragma db column(css::_chat_session_id)
std::string chat_session_id;
#pragma db column(csm2::_user_id)
std::string friend_id;
};
// 这里条件必须是指定条件: css::chat_session_type==2 && csm.user_id=uid
#pragma db view object(ChatSession = css)\
object(ChatSessionMember = csm : css::_chat_session_id == csm::_session_id)\
query((?))
struct GroupChatSession {
#pragma db column(css::_chat_session_id)
std::string chat_session_id;
#pragma db column(css::_chat_session_name)
std::string chat_session_name;
};
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <cstddef>
#include <odb/core.hxx>
namespace bite_im {
#pragma db object table("chat_session_member")
class ChatSessionMember {
public:
ChatSessionMember(){}
ChatSessionMember(const std::string& ssid, const std::string &uid)
:_session_id(ssid), _user_id(uid) {}
~ChatSessionMember(){}
std::string session_id() const {return _session_id; }
void session_id(std::string &ssid) { _session_id = ssid; }
std::string user_id() const { return _user_id; }
void user_id(std::string &uid) {_user_id = uid; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)") index
std::string _session_id;
#pragma db type("varchar(64)")
std::string _user_id;
};
}
//odb -d mysql --generate-query --generate-schema --profile boost/date-time person.hxx

34
odb/friend_apply.hxx Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <cstddef>
#include <odb/core.hxx>
namespace bite_im {
#pragma db object table("friend_apply")
class FriendApply{
public:
FriendApply() {}
FriendApply(const std::string &eid,
const std::string &uid, const std::string &pid):
_user_id(uid), _peer_id(pid), _event_id(eid){}
std::string event_id() const { return _event_id; }
void event_id(std::string &eid) { _event_id = eid; }
std::string user_id() const { return _user_id; }
void user_id(std::string &uid) { _user_id = uid; }
std::string peer_id() const { return _peer_id; }
void peer_id(std::string &uid) { _peer_id = uid; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)") index unique
std::string _event_id;
#pragma db type("varchar(64)") index
std::string _user_id;
#pragma db type("varchar(64)") index
std::string _peer_id;
};
}

83
odb/message.hxx Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include <string>
#include <cstddef>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
#include <boost/date_time/posix_time/posix_time.hpp>
namespace bite_im {
#pragma db object table("message")
class Message {
public:
Message(){}
Message(const std::string &mid,
const std::string &ssid,
const std::string &uid,
const unsigned char mtype,
const boost::posix_time::ptime &ctime):
_message_id(mid), _session_id(ssid),
_user_id(uid), _message_type(mtype),
_create_time(ctime){}
std::string message_id() const { return _message_id; }
void message_id(const std::string &val) { _message_id = val; }
std::string session_id() const { return _session_id; }
void session_id(const std::string &val) { _session_id = val; }
std::string user_id() const { return _user_id; }
void user_id(const std::string &val) { _user_id = val; }
unsigned char message_type() const { return _message_type; }
void message_type(unsigned char val) { _message_type = val; }
boost::posix_time::ptime create_time() const { return _create_time; }
void create_time(const boost::posix_time::ptime &val) { _create_time = val; }
std::string content() const {
if (!_content) return std::string();
return *_content;
}
void content(const std::string &val) { _content = val; }
std::string file_id() const {
if (!_file_id) return std::string();
return *_file_id;
}
void file_id(const std::string &val) { _file_id = val; }
std::string file_name() const {
if (!_file_name) return std::string();
return *_file_name;
}
void file_name(const std::string &val) { _file_name = val; }
unsigned int file_size() const {
if (!_file_size) return 0;
return *_file_size;
}
void file_size(unsigned int val) { _file_size = val; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)") index unique
std::string _message_id;
#pragma db type("varchar(64)") index
std::string _session_id; //所属会话ID
#pragma db type("varchar(64)")
std::string _user_id; //发送者用户ID
unsigned char _message_type; //消息类型 0-文本1-图片2-文件3-语音
#pragma db type("TIMESTAMP")
boost::posix_time::ptime _create_time; //消息的产生时间
//可空信息字段
odb::nullable<std::string> _content; //文本消息内容--非文本消息可以忽略
#pragma db type("varchar(64)")
odb::nullable<std::string> _file_id; //文件消息的文件ID -- 文本消息忽略
#pragma db type("varchar(128)")
odb::nullable<std::string> _file_name; //文件消息的文件名称 -- 只针对文件消息有效
odb::nullable<unsigned int> _file_size; //文件消息的文件大小 -- 只针对文件消息有效
};
//odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time message.hxx
}

30
odb/relation.hxx Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <cstddef>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
namespace bite_im {
#pragma db object table("relation")
class Relation {
public:
Relation(){}
Relation(const std::string &uid, const std::string &pid):
_user_id(uid), _peer_id(pid){}
std::string user_id() const { return _user_id; }
void user_id(std::string &uid) { _user_id = uid; }
std::string peer_id() const { return _peer_id; }
void peer_id(std::string &uid) { _peer_id = uid; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)") index
std::string _user_id;
#pragma db type("varchar(64)")
std::string _peer_id;
};
//odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time person.hxx
}

71
odb/user.hxx Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <string>
#include <cstddef>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
typedef boost::posix_time::ptime ptime;
#pragma db object table("user")
class User
{
public:
User(){}
//用户名--新增用户 -- 用户ID, 昵称,密码
User(const std::string &uid, const std::string &nickname, const std::string &password):
_user_id(uid), _nickname(nickname), _password(password){}
//手机号--新增用户 -- 用户ID, 手机号, 随机昵称
User(const std::string &uid, const std::string &phone):
_user_id(uid), _nickname(uid), _phone(phone){}
void user_id(const std::string &val) { _user_id = val; }
std::string user_id() { return _user_id; }
std::string nickname() {
if (_nickname) return *_nickname;
return std::string();
}
void nickname(const std::string &val) { _nickname = val; }
std::string description() {
if (!_description) return std::string();
return *_description;
}
void description(const std::string &val) { _description = val; }
std::string password() {
if (!_password) return std::string();
return *_password;
}
void password(const std::string &val) { _password = val; }
std::string phone() {
if (!_phone) return std::string();
return *_phone;
}
void phone(const std::string &val) { _phone = val; }
std::string avatar_id() {
if (!_avatar_id) return std::string();
return *_avatar_id;
}
void avatar_id(const std::string &val) { _avatar_id = val; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db index
std::string _user_id;
odb::nullable<std::string> _nickname; //用户昵称,不一定存在
#pragma db index
odb::nullable<std::string> _description; //用户签名 不一定存在
odb::nullable<std::string> _password; //用户密码 不一定存在
#pragma db index
odb::nullable<std::string> _phone; //用户手机号 不一定存在
odb::nullable<std::string> _avatar_id; //用户头像文件id 不一定存在
};
//odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time person.hxx

82
proto/base.proto Normal file
View File

@ -0,0 +1,82 @@
syntax = "proto3";
package bite_im;
option cc_generic_services = true;
//用户信息结构
message UserInfo {
string user_id = 1;//用户ID
string nickname = 2;//昵称
string description = 3;//个人签名/描述
string phone = 4; //绑定手机号
bytes avatar = 5;//头像照片,文件内容使用二进制
}
//聊天会话信息
message ChatSessionInfo {
//群聊会话不需要设置单聊会话设置为对方用户ID
optional string single_chat_friend_id = 1;
string chat_session_id = 2; //会话ID
string chat_session_name = 3;//会话名称git
//会话上一条消息,新建的会话没有最新消息
optional MessageInfo prev_message = 4;
//会话头像 --群聊会话不需要,直接由前端固定渲染,单聊就是对方的头像
optional bytes avatar = 5;
}
//消息类型
enum MessageType {
STRING = 0;
IMAGE = 1;
FILE = 2;
SPEECH = 3;
}
message StringMessageInfo {
string content = 1;//文字聊天内容
}
message ImageMessageInfo {
//图片文件id,客户端发送的时候不用设置由transmit服务器进行设置后交给storage的时候设置
optional string file_id = 1;
//图片数据在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候需要原样转发
optional bytes image_content = 2;
}
message FileMessageInfo {
optional string file_id = 1;//文件id,客户端发送的时候不用设置
optional int64 file_size = 2;//文件大小
optional string file_name = 3;//文件名称
//文件数据在ES中存储消息的时候只要id和元信息不要文件数据, 服务端转发的时候也不需要填充
optional bytes file_contents = 4;
}
message SpeechMessageInfo {
//语音文件id,客户端发送的时候不用设置
optional string file_id = 1;
//文件数据在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候也不需要填充
optional bytes file_contents = 2;
}
message MessageContent {
MessageType message_type = 1; //消息类型
oneof msg_content {
StringMessageInfo string_message = 2;//文字消息
FileMessageInfo file_message = 3;//文件消息
SpeechMessageInfo speech_message = 4;//语音消息
ImageMessageInfo image_message = 5;//图片消息
};
}
//消息结构
message MessageInfo {
string message_id = 1;//消息ID
string chat_session_id = 2;//消息所属聊天会话ID
int64 timestamp = 3;//消息产生时间
UserInfo sender = 4;//消息发送者信息
MessageContent message = 5;
}
message FileDownloadData {
string file_id = 1;
bytes file_content = 2;
}
message FileUploadData {
string file_name = 1; //文件名称
int64 file_size = 2; //文件大小
bytes file_content = 3; //文件数据
}

64
proto/file.proto Normal file
View File

@ -0,0 +1,64 @@
syntax = "proto3";
package bite_im;
import "base.proto";
option cc_generic_services = true;
message GetSingleFileReq {
string request_id = 1;
string file_id = 2;
optional string user_id = 3;
optional string session_id = 4;
}
message GetSingleFileRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
optional FileDownloadData file_data = 4;
}
message GetMultiFileReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
repeated string file_id_list = 4;
}
message GetMultiFileRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
map<string, FileDownloadData> file_data = 4;//文件ID与文件数据的映射map
}
message PutSingleFileReq {
string request_id = 1; //请求ID作为处理流程唯一标识
optional string user_id = 2;
optional string session_id = 3;
FileUploadData file_data = 4;
}
message PutSingleFileRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
FileMessageInfo file_info = 4; //返回了文件组织的元信息
}
message PutMultiFileReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
repeated FileUploadData file_data = 4;
}
message PutMultiFileRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated FileMessageInfo file_info = 4;
}
service FileService {
rpc GetSingleFile(GetSingleFileReq) returns (GetSingleFileRsp);
rpc GetMultiFile(GetMultiFileReq) returns (GetMultiFileRsp);
rpc PutSingleFile(PutSingleFileReq) returns (PutSingleFileRsp);
rpc PutMultiFile(PutMultiFileReq) returns (PutMultiFileRsp);
}

155
proto/friend.proto Normal file
View File

@ -0,0 +1,155 @@
syntax = "proto3";
package bite_im;
import "base.proto";
option cc_generic_services = true;
//--------------------------------------
//好友列表获取
message GetFriendListReq {
string request_id = 1; // 请求标识ID
optional string user_id = 2; // 当前请求的发起者用户ID
optional string session_id = 3; //登录会话ID--用于网关进行身份识别--其他子服务用不到
}
message GetFriendListRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated UserInfo friend_list = 4; //要返回的用户信息
}
//--------------------------------------
//好友删除
message FriendRemoveReq {
string request_id = 1;
optional string user_id = 2; //当前用户ID
optional string session_id = 3;
string peer_id = 4; //要删除的好友ID
}
message FriendRemoveRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//--------------------------------------
//添加好友--发送好友申请
message FriendAddReq {
string request_id = 1;
optional string session_id = 2;
optional string user_id = 3;//申请人id
string respondent_id = 4;//被申请人id
}
message FriendAddRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
string notify_event_id = 4;//通知事件id
}
//--------------------------------------
//好友申请的处理
message FriendAddProcessReq {
string request_id = 1;
string notify_event_id = 2;//通知事件id
bool agree = 3;//是否同意好友申请
string apply_user_id = 4; //申请人的用户id
optional string session_id = 5;
optional string user_id = 6; // 被申请人
}
// +++++++++++++++++++++++++++++++++
message FriendAddProcessRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
// 同意后会创建会话,向网关返回会话信息,用于通知双方会话的建立,这个字段客户端不需要关注
optional string new_session_id = 4;
}
//--------------------------------------
//获取待处理的,申请自己好友的信息列表
message GetPendingFriendEventListReq {
string request_id = 1;
optional string session_id = 2;
optional string user_id = 3;
}
message FriendEvent {
optional string event_id = 1;
UserInfo sender = 3;
}
message GetPendingFriendEventListRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated FriendEvent event = 4;
}
//--------------------------------------
//好友搜索
message FriendSearchReq {
string request_id = 1;
string search_key = 2;//就是名称模糊匹配关键字
optional string session_id = 3;
optional string user_id = 4;
}
message FriendSearchRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated UserInfo user_info = 4;
}
//--------------------------------------
//会话列表获取
message GetChatSessionListReq {
string request_id = 1;
optional string session_id = 2;
optional string user_id = 3;
}
message GetChatSessionListRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated ChatSessionInfo chat_session_info_list = 4;
}
//--------------------------------------
//创建会话
message ChatSessionCreateReq {
string request_id = 1;
optional string session_id = 2;
optional string user_id = 3;
string chat_session_name = 4;
//需要注意的是这个列表中也必须包含创建者自己的用户ID
repeated string member_id_list = 5;
}
message ChatSessionCreateRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
//这个字段属于后台之间的数据,给前端回复的时候不需要这个字段,会话信息通过通知进行发送
optional ChatSessionInfo chat_session_info = 4;
}
//--------------------------------------
//获取会话成员列表
message GetChatSessionMemberReq {
string request_id = 1;
optional string session_id = 2;
optional string user_id = 3;
string chat_session_id = 4;
}
message GetChatSessionMemberRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated UserInfo member_info_list = 4;
}
service FriendService {
rpc GetFriendList(GetFriendListReq) returns (GetFriendListRsp);
rpc FriendRemove(FriendRemoveReq) returns (FriendRemoveRsp);
rpc FriendAdd(FriendAddReq) returns (FriendAddRsp);
rpc FriendAddProcess(FriendAddProcessReq) returns (FriendAddProcessRsp);
rpc FriendSearch(FriendSearchReq) returns (FriendSearchRsp);
rpc GetChatSessionList(GetChatSessionListReq) returns (GetChatSessionListRsp);
rpc ChatSessionCreate(ChatSessionCreateReq) returns (ChatSessionCreateRsp);
rpc GetChatSessionMember(GetChatSessionMemberReq) returns (GetChatSessionMemberRsp);
rpc GetPendingFriendEventList(GetPendingFriendEventListReq) returns (GetPendingFriendEventListRsp);
}

53
proto/gateway.proto Normal file
View File

@ -0,0 +1,53 @@
syntax = "proto3";
package bite_im;
option cc_generic_services = true;
message ClientAuthenticationReq {
string request_id = 1;
string session_id = 2; // 用于向服务器表明当前长连接客户端的身份
}
//在客户端与网关服务器的通信中使用HTTP协议进行通信
// 通信时采用POST请求作为请求方法
// 通信时正文采用protobuf作为正文协议格式具体内容字段以前边各个文件中定义的字段格式为准
/* 以下是HTTP请求的功能与接口路径对应关系
SERVICE HTTP PATH:
{
获取随机验证码 /service/user/get_random_verify_code
获取短信验证码 /service/user/get_phone_verify_code
用户名密码注册 /service/user/username_register
用户名密码登录 /service/user/username_login
手机号码注册 /service/user/phone_register
手机号码登录 /service/user/phone_login
获取个人信息 /service/user/get_user_info
修改头像 /service/user/set_avatar
修改昵称 /service/user/set_nickname
修改签名 /service/user/set_description
修改绑定手机 /service/user/set_phone
获取好友列表 /service/friend/get_friend_list
获取好友信息 /service/friend/get_friend_info
发送好友申请 /service/friend/add_friend_apply
好友申请处理 /service/friend/add_friend_process
删除好友 /service/friend/remove_friend
搜索用户 /service/friend/search_friend
获取指定用户的消息会话列表 /service/friend/get_chat_session_list
创建消息会话 /service/friend/create_chat_session
获取消息会话成员列表 /service/friend/get_chat_session_member
获取待处理好友申请事件列表 /service/friend/get_pending_friend_events
获取历史消息/离线消息列表 /service/message_storage/get_history
获取最近N条消息列表 /service/message_storage/get_recent
搜索历史消息 /service/message_storage/search_history
发送消息 /service/message_transmit/new_message
获取单个文件数据 /service/file/get_single_file
获取多个文件数据 /service/file/get_multi_file
发送单个文件 /service/file/put_single_file
发送多个文件 /service/file/put_multi_file
语音转文字 /service/speech/recognition
}
*/

55
proto/message.proto Normal file
View File

@ -0,0 +1,55 @@
syntax = "proto3";
package bite_im;
import "base.proto";
option cc_generic_services = true;
message GetHistoryMsgReq {
string request_id = 1;
string chat_session_id = 2;
int64 start_time = 3;
int64 over_time = 4;
optional string user_id = 5;
optional string session_id = 6;
}
message GetHistoryMsgRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated MessageInfo msg_list = 4;
}
message GetRecentMsgReq {
string request_id = 1;
string chat_session_id = 2;
int64 msg_count = 3;
optional int64 cur_time = 4;//用于扩展获取指定时间前的n条消息
optional string user_id = 5;
optional string session_id = 6;
}
message GetRecentMsgRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated MessageInfo msg_list = 4;
}
message MsgSearchReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
string chat_session_id = 4;
string search_key = 5;
}
message MsgSearchRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
repeated MessageInfo msg_list = 4;
}
service MsgStorageService {
rpc GetHistoryMsg(GetHistoryMsgReq) returns (GetHistoryMsgRsp);
rpc GetRecentMsg(GetRecentMsgReq) returns (GetRecentMsgRsp);
rpc MsgSearch(MsgSearchReq) returns (MsgSearchRsp);
}

39
proto/notify.proto Normal file
View File

@ -0,0 +1,39 @@
syntax = "proto3";
package bite_im;
import "base.proto";
option cc_generic_services = true;
enum NotifyType {
FRIEND_ADD_APPLY_NOTIFY = 0;
FRIEND_ADD_PROCESS_NOTIFY = 1;
CHAT_SESSION_CREATE_NOTIFY = 2;
CHAT_MESSAGE_NOTIFY = 3;
FRIEND_REMOVE_NOTIFY = 4;
}
message NotifyFriendAddApply {
UserInfo user_info = 1; //申请人信息
}
message NotifyFriendAddProcess {
bool agree = 1;
UserInfo user_info = 2; //处理人信息
}
message NotifyFriendRemove {
string user_id = 1; //删除自己的用户 ID
}
message NotifyNewChatSession {
ChatSessionInfo chat_session_info = 1; //新建会话信息
}
message NotifyNewMessage {
MessageInfo message_info = 1; //新消息
}
message NotifyMessage {
optional string notify_event_id = 1;//通知事件操作 id有则填无则忽略
NotifyType notify_type = 2;//通知事件类型
oneof notify_remarks { //事件备注信息
NotifyFriendAddApply friend_add_apply = 3;
NotifyFriendAddProcess friend_process_result = 4;
NotifyFriendRemove friend_remove = 7;
NotifyNewChatSession new_chat_session_info = 5;//会话信息
NotifyNewMessage new_message_info = 6;//消息信息
}
}

23
proto/speech.proto Normal file
View File

@ -0,0 +1,23 @@
syntax = "proto3";
package bite_im;
option cc_generic_services = true;
message SpeechRecognitionReq {
string request_id = 1; //请求ID
bytes speech_content = 2; //语音数据
optional string user_id = 3; //用户ID
optional string session_id = 4; //登录会话ID -- 网关进行身份鉴权
}
message SpeechRecognitionRsp {
string request_id = 1; //请求ID
bool success = 2; //请求处理结果标志
optional string errmsg = 3; //失败原因
optional string recognition_result = 4; //识别后的文字数据
}
//语音识别Rpc服务及接口的定义
service SpeechService {
rpc SpeechRecognition(SpeechRecognitionReq) returns (SpeechRecognitionRsp);
}

32
proto/transmite.proto Normal file
View File

@ -0,0 +1,32 @@
syntax = "proto3";
package bite_im;
import "base.proto";
option cc_generic_services = true;
//这个用于和网关进行通信
message NewMessageReq {
string request_id = 1; //请求ID -- 全链路唯一标识
optional string user_id = 2;
optional string session_id = 3;//客户端身份识别信息 -- 这就是消息发送者
string chat_session_id = 4; //聊天会话ID -- 标识了当前消息属于哪个会话,应该转发给谁
MessageContent message = 5; // 消息内容--消息类型+内容
}
message NewMessageRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//这个用于内部的通信,生成完整的消息信息,并获取消息的转发人员列表
message GetTransmitTargetRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
MessageInfo message = 4; // 组织好的消息结构 --
repeated string target_id_list = 5; //消息的转发目标列表
}
service MsgTransmitService {
rpc GetTransmitTarget(NewMessageReq) returns (GetTransmitTargetRsp);
}

166
proto/user.proto Normal file
View File

@ -0,0 +1,166 @@
syntax = "proto3";
package bite_im;
import "base.proto";
option cc_generic_services = true;
//----------------------------
//用户名注册
message UserRegisterReq {
string request_id = 1;
string nickname = 2;
string password = 3;
optional string verify_code_id = 4; //目前客户端实现了本地验证,该字段没用了
optional string verify_code = 5;//目前客户端实现了本地验证,该字段没用了
}
message UserRegisterRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//----------------------------
//用户名登录
message UserLoginReq {
string request_id = 1;
string nickname = 2;
string password = 3;
optional string verify_code_id = 4;
optional string verify_code = 5;
}
message UserLoginRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
string login_session_id = 4;
}
//----------------------------
//手机号验证码获取
message PhoneVerifyCodeReq {
string request_id = 1;
string phone_number = 2;
}
message PhoneVerifyCodeRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
string verify_code_id = 4;
}
//----------------------------
//手机号注册
message PhoneRegisterReq {
string request_id = 1;
string phone_number = 2;
string verify_code_id = 3;
string verify_code = 4;
}
message PhoneRegisterRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//----------------------------
//手机号登录
message PhoneLoginReq {
string request_id = 1;
string phone_number = 2;
string verify_code_id = 3;
string verify_code = 4;
}
message PhoneLoginRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
string login_session_id = 4;
}
//个人信息获取-这个只用于获取当前登录用户的信息
// 客户端传递的时候只需要填充session_id即可
//其他个人/好友信息的获取在好友操作中完成
message GetUserInfoReq {
string request_id = 1;
optional string user_id = 2; // 这个字段是网关进行身份鉴权之后填入的字段
optional string session_id = 3; // 进行客户端身份识别的关键字段
}
message GetUserInfoRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
UserInfo user_info = 4;
}
//内部接口
message GetMultiUserInfoReq {
string request_id = 1;
repeated string users_id = 2;
}
message GetMultiUserInfoRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
map<string, UserInfo> users_info = 4;
}
//----------------------------
//用户头像修改
message SetUserAvatarReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
bytes avatar = 4;
}
message SetUserAvatarRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//----------------------------
//用户昵称修改
message SetUserNicknameReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
string nickname = 4;
}
message SetUserNicknameRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//----------------------------
//用户签名修改
message SetUserDescriptionReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
string description = 4;
}
message SetUserDescriptionRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
//----------------------------
//用户手机修改
message SetUserPhoneNumberReq {
string request_id = 1;
optional string user_id = 2;
optional string session_id = 3;
string phone_number = 4;
string phone_verify_code_id = 5;
string phone_verify_code = 6;
}
message SetUserPhoneNumberRsp {
string request_id = 1;
bool success = 2;
string errmsg = 3;
}
service UserService {
rpc UserRegister(UserRegisterReq) returns (UserRegisterRsp);
rpc UserLogin(UserLoginReq) returns (UserLoginRsp);
rpc GetPhoneVerifyCode(PhoneVerifyCodeReq) returns (PhoneVerifyCodeRsp);
rpc PhoneRegister(PhoneRegisterReq) returns (PhoneRegisterRsp);
rpc PhoneLogin(PhoneLoginReq) returns (PhoneLoginRsp);
rpc GetUserInfo(GetUserInfoReq) returns (GetUserInfoRsp);
rpc GetMultiUserInfo(GetMultiUserInfoReq) returns (GetMultiUserInfoRsp);
rpc SetUserAvatar(SetUserAvatarReq) returns (SetUserAvatarRsp);
rpc SetUserNickname(SetUserNicknameReq) returns (SetUserNicknameRsp);
rpc SetUserDescription(SetUserDescriptionReq) returns (SetUserDescriptionRsp);
rpc SetUserPhoneNumber(SetUserPhoneNumberReq) returns (SetUserPhoneNumberRsp);
}

54
speech/CMakeLists.txt Normal file
View File

@ -0,0 +1,54 @@
# 1. 添加cmake版本说明
cmake_minimum_required(VERSION 3.1.3)
# 2. 声明工程名称
project(speech_server)
set(target "speech_server")
set(test_client "speech_client")
# 3. 检测并生成ODB框架代码
# 1. 添加所需的proto映射代码文件名称
set(proto_path ${CMAKE_CURRENT_SOURCE_DIR}/../proto)
set(proto_files speech.proto)
# 2. 检测框架代码文件是否已经生成
set(proto_hxx "")
set(proto_cxx "")
set(proto_srcs "")
foreach(proto_file ${proto_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".proto" ".pb.cc" proto_cc ${proto_file})
string(REPLACE ".proto" ".pb.h" proto_hh ${proto_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${proto_cc})
add_custom_command(
PRE_BUILD
COMMAND protoc
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I ${proto_path} --experimental_allow_proto3_optional ${proto_path}/${proto_file}
DEPENDS ${proto_path}/${proto_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
COMMENT "生成Protobuf框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
)
endif()
list(APPEND proto_srcs ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc})
endforeach()
# 4. 获取源码目录下的所有源码文件
set(src_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files)
# 5. 声明目标及依赖
add_executable(${target} ${src_files} ${proto_srcs})
# 7. 设置需要连接的库
target_link_libraries(${target} -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
set(test_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/test test_files)
add_executable(${test_client} ${test_files} ${proto_srcs})
target_link_libraries(${test_client} -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
# 6. 设置头文件默认搜索路径
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third/include)
#8. 设置安装路径
INSTALL(TARGETS ${target} ${test_client} RUNTIME DESTINATION bin)

16
speech/dockerfile Normal file
View File

@ -0,0 +1,16 @@
# 声明基础经镜像来源
FROM debian:12
# 声明工作目录
WORKDIR /im
RUN mkdir -p /im/logs &&\
mkdir -p /im/data &&\
mkdir -p /im/conf &&\
mkdir -p /im/bin
# 将可执行程序依赖,拷贝进镜像
COPY ./build/speech_server /im/bin/
# 将可执行程序文件,拷贝进镜像
COPY ./depends /lib/x86_64-linux-gnu/
# 设置容器启动的默认操作 ---运行程序
CMD /im/bin/speech_server -flagfile=/im/conf/speech_server.conf

View File

@ -0,0 +1,33 @@
#include "speech_server.hpp"
//语音识别子服务
DEFINE_bool(run_mode, false, "程序的运行模式, false-调试true-发布");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志的输出等级");
DEFINE_string(registry_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(instance_name, "/speech_service/instance", "当前实例名称");
DEFINE_string(access_host, "127.0.0.1:10001", "当前实例的外部访问地址");
DEFINE_int32(listen_port, 10001, "Rpc服务器监听端口");
DEFINE_int32(rpc_timeout, -1, "Rpc调用超时时间");
DEFINE_int32(rpc_threads, 1, "Rpc的IO线程数量");
DEFINE_string(app_id, "118805148", "语音平台应用ID");
DEFINE_string(api_key, "tRBBbRWdTOjHgr8xZX0s4Z2d", "语音平台API密钥");
DEFINE_string(secret_key, "H2pyXuWi04uKEKK0T8jrTYo7Pj4UUUpC", "语音平台加密密钥");
int main(int argc, char* argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
bite_im::SpeechServerBuilder ssb;
ssb.make_asr_object(FLAGS_app_id, FLAGS_api_key, FLAGS_secret_key);
ssb.make_rpc_server(FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads);
ssb.make_reg_object(FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host);
auto server = ssb.build();
server->start();
return 0;
}

View File

@ -0,0 +1,123 @@
#include <brpc/server.h>
#include <butil/logging.h>
#include "asr.hpp" //语音识别模块
#include "etcd.hpp" //服务注册模块
#include "logger.hpp" //日志模块
#include "speech.pb.h" //protobuf框架代码
using namespace bite_im;
namespace bite_im{
class SpeechServiceImpl : public bite_im::SpeechService {
public:
SpeechServiceImpl(const ASRClient::ptr &asr_client):
_asr_client(asr_client){}
~SpeechServiceImpl(){}
void SpeechRecognition(google::protobuf::RpcController* controller,
const ::bite_im::SpeechRecognitionReq* request,
::bite_im::SpeechRecognitionRsp* response,
::google::protobuf::Closure* done) {
LOG_DEBUG("收到语音转文字请求!");
brpc::ClosureGuard rpc_guard(done);
//1. 取出请求中的语音数据
//2. 调用语音sdk模块进行语音识别得到响应
std::string err;
std::string res = _asr_client->recognize(request->speech_content(), err);
if (res.empty()) {
LOG_ERROR("{} 语音识别失败!", request->request_id());
response->set_request_id(request->request_id());
response->set_success(false);
response->set_errmsg("语音识别失败:" + err);
return;
}
//3. 组织响应
response->set_request_id(request->request_id());
response->set_success(true);
response->set_recognition_result(res);
}
private:
ASRClient::ptr _asr_client;
};
class SpeechServer {
public:
using ptr = std::shared_ptr<SpeechServer>;
SpeechServer(const ASRClient::ptr asr_client,
const Registry::ptr &reg_client,
const std::shared_ptr<brpc::Server> &server):
_asr_client(asr_client),
_reg_client(reg_client),
_rpc_server(server){}
~SpeechServer(){}
//搭建RPC服务器并启动服务器
void start() {
_rpc_server->RunUntilAskedToQuit();
}
private:
ASRClient::ptr _asr_client;
Registry::ptr _reg_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
class SpeechServerBuilder {
public:
//构造语音识别客户端对象
void make_asr_object(const std::string &app_id,
const std::string &api_key,
const std::string &secret_key) {
_asr_client = std::make_shared<ASRClient>(app_id, api_key, secret_key);
}
//用于构造服务注册客户端对象
void make_reg_object(const std::string &reg_host,
const std::string &service_name,
const std::string &access_host) {
_reg_client = std::make_shared<Registry>(reg_host);
_reg_client->registry(service_name, access_host);
}
//构造RPC服务器对象
void make_rpc_server(uint16_t port, int32_t timeout, uint8_t num_threads) {
if (!_asr_client) {
LOG_ERROR("还未初始化语音识别模块!");
abort();
}
_rpc_server = std::make_shared<brpc::Server>();
SpeechServiceImpl *speech_service = new SpeechServiceImpl(_asr_client);
int ret = _rpc_server->AddService(speech_service,
brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
if (ret == -1) {
LOG_ERROR("添加Rpc服务失败");
abort();
}
brpc::ServerOptions options;
options.idle_timeout_sec = timeout;
options.num_threads = num_threads;
ret = _rpc_server->Start(port, &options);
if (ret == -1) {
LOG_ERROR("服务启动失败!");
abort();
}
}
SpeechServer::ptr build() {
if (!_asr_client) {
LOG_ERROR("还未初始化语音识别模块!");
abort();
}
if (!_reg_client) {
LOG_ERROR("还未初始化服务注册模块!");
abort();
}
if (!_rpc_server) {
LOG_ERROR("还未初始化RPC服务器模块");
abort();
}
SpeechServer::ptr server = std::make_shared<SpeechServer>(
_asr_client, _reg_client, _rpc_server);
return server;
}
private:
ASRClient::ptr _asr_client;
Registry::ptr _reg_client;
std::shared_ptr<brpc::Server> _rpc_server;
};
}

13
speech/speech_server.conf Normal file
View File

@ -0,0 +1,13 @@
-run_mode=true
-log_file=/im/logs/speech.log
-log_level=0
-registry_host=http://10.0.0.235:2379
-instance_name=/speech_service/instance
-access_host=10.0.0.235:10001
-base_service=/service
-listen_port=10001
-rpc_timeout=-1
-rpc_threads=1
-app_id=60694095
-api_key=PWn6zlsxym8VwpBW8Or4PPGe
-secret_key=Bl0mn74iyAkr3FzCo5TZV7lBq7NYoms9

View File

@ -0,0 +1,73 @@
//speech_server的测试客户端实现
//进行服务的发现发现speech_server服务器节点地址信息并实例化通信信道
//读取语音文件数据
//发起语音识别rpc调用
#include <iostream>
#include "etcd.hpp"
#include "channel.hpp"
#include <gflags/gflags.h>
#include <thread>
#include "aip-cpp-sdk/speech.h"
#include "aip-cpp-sdk/base/utils.h"
#include "speech.pb.h"
DEFINE_bool(run_mode, false, "程序的运行模式false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(etcd_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(speech_service, "/service/speech_service", "服务监控根目录");
int main(int argc, char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
bite_im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
//1. 先构造Rpc信道管理对象
auto sm = std::make_shared<bite_im::ServiceManager>();
sm->declared(FLAGS_speech_service);
auto put_cb = std::bind(&bite_im::ServiceManager::onServiceOnline, sm.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&bite_im::ServiceManager::onServiceOffline, sm.get(), std::placeholders::_1, std::placeholders::_2);
//2. 构造服务发现对象
bite_im::Discovery::ptr dclient = std::make_shared<bite_im::Discovery>(FLAGS_etcd_host, FLAGS_base_service, put_cb, del_cb);
//3. 通过Rpc信道管理对象获取提供Echo服务的信道
auto channel = sm->choose(FLAGS_speech_service);
if (!channel) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return -1;
}
//读取语音文件数据
std::string file_content;
aip::get_file_content("16k.pcm", &file_content);
std::cout << file_content.size() << std::endl;
//4. 发起EchoRpc调用
bite_im::SpeechService_Stub stub(channel.get());
bite_im::SpeechRecognitionReq req;
req.set_speech_content(file_content);
req.set_request_id("111111");
brpc::Controller *cntl = new brpc::Controller();
bite_im::SpeechRecognitionRsp *rsp = new bite_im::SpeechRecognitionRsp();
stub.SpeechRecognition(cntl, &req, rsp, nullptr);
if (cntl->Failed() == true) {
std::cout << "Rpc调用失败" << cntl->ErrorText() << std::endl;
delete cntl;
delete rsp;
std::this_thread::sleep_for(std::chrono::seconds(1));
return -1;
}
if (rsp->success() == false) {
std::cout << rsp->errmsg() << std::endl;
return -1;
}
std::cout << "收到响应: " << rsp->request_id() << std::endl;
std::cout << "收到响应: " << rsp->recognition_result() << std::endl;
return 0;
}

View File

@ -0,0 +1,35 @@
# 安装百度AI开放平台 C++ SDK
**C++ SDK目录结构**
├── base
│ ├── base.h // 授权相关类
│ ├── base64.h // base64加密类
│ ├── http.h // http请求类
│ └── utils.h // 工具类
├── face.h // 人脸识别交互类
├── image_censor.h // 图像审核交互类
├── image_classify.h // 图像识别交互类
├── image_search.h // 图像搜索交互类
├── kg.h // 人脸识别交互类
├── nlp.h // 人脸识别交互类
├── ocr.h // 人脸识别交互类
└── speech.h // 语音识别交互类
**支持 C++ 11+**
**直接使用开发包步骤如下:**
1.在[官方网站](http://ai.baidu.com/sdk)下载C++ SDK压缩包。
2.将下载的`aip-cpp-sdk-version.zip`解压, 其中文件为包含实现代码的头文件。
3.安装依赖库curl(需要支持ssl) openssl jsoncpp(>1.6.2版本0.x版本将不被支持)。
4.编译工程时添加 C++11 支持 (gcc/clang 添加编译参数 -std=c++11), 添加第三方库链接参数 lcurl, lcrypto, ljsoncpp。
5.在源码中include 您需要使用的交互类头文件face.h image_censor.h image_classify.h kg.h nlp.h ocr.h speech.h等引入压缩包中的头文即可使用aip命名空间下的类和方法。
# 详细使用文档
参考[百度AI开放平台官方文档](http://ai.baidu.com/docs)

View File

@ -0,0 +1,311 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_BASE_H__
#define __AIP_BASE_H__
#include <memory>
#include <cstring>
#include "http.h"
#include "json/json.h"
#include "base64.h"
#include "curl/curl.h"
#include "utils.h"
namespace aip {
static const char* AIP_SDK_VERSION = "0.3.3";
static const char* CURL_ERROR_CODE = "curl_error_code";
static const std::string ACCESS_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token";
static const std::map<std::string, std::string> null;
static const Json::Value json_null;
class AipBase
{
private:
std::string _app_id;
int _expired_time;
bool _is_bce;
bool _has_decide_type;
std::string _scope;
protected:
std::string getAccessToken()
{
time_t now = time(NULL);
if (!access_token.empty())
{
return this->access_token;
}
if (now < this->_expired_time - 60 * 60 * 24)
{
return this->access_token;
}
std::string response;
std::map<std::string, std::string> params;
params["grant_type"] = "client_credentials";
params["client_id"] = this->ak;
params["client_secret"] = this->sk;
int status_code = this->client.get(
ACCESS_TOKEN_URL,
&params,
nullptr,
&response
);
Json::Value obj;
if (status_code != CURLcode::CURLE_OK) {
obj[CURL_ERROR_CODE] = status_code;
return obj.toStyledString();
}
std::string error;
std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
this->access_token = obj["access_token"].asString();
this->_expired_time = obj["expires_in"].asInt() + (int) now;
this->_scope = obj["scope"].asString();
return this->access_token;
}
void merge_json(Json::Value& data, const Json::Value& options) {
Json::Value::Members mem = options.getMemberNames();
for (auto & iter : mem) {
data[iter.c_str()] = options[iter];
}
}
public:
std::string ak;
std::string sk;
HttpClient client;
Json::CharReaderBuilder crbuilder;
std::string access_token;
AipBase(const std::string & app_id, const std::string & ak, const std::string & sk):
_app_id(app_id),
_is_bce(false),
_has_decide_type(false),
ak(ak),
sk(sk)
{
if (_app_id == "")
{
}
}
void setConnectionTimeoutInMillis(int connect_timeout)
{
this->client.setConnectTimeout(connect_timeout);
}
void setSocketTimeoutInMillis(int socket_timeout)
{
this->client.setSocketTimeout(socket_timeout);
}
void setDebug(bool debug)
{
this->client.setDebug(debug);
}
std::string getAk() {
return ak;
}
Json::Value request(
std::string url,
std::map<std::string, std::string> const & params,
std::string const & data,
std::map<std::string, std::string> const & headers)
{
std::string response;
Json::Value obj;
std::string body;
auto headers_for_sign = headers;
auto temp_params = params;
temp_params["charset"] = "UTF-8";
this->prepare_request(url, temp_params, headers_for_sign);
int status_code = this->client.post(url, &temp_params, data, &headers_for_sign, &response);
if (status_code != CURLcode::CURLE_OK) {
obj[CURL_ERROR_CODE] = status_code;
return obj;
}
std::string error;
std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
return obj;
}
Json::Value request(
std::string url,
std::map<std::string, std::string> const & params,
std::map<std::string, std::string> const & data,
std::map<std::string, std::string> const & headers)
{
std::string response;
Json::Value obj;
auto headers_for_sign = headers;
auto temp_params = params;
this->prepare_request(url, temp_params, headers_for_sign);
int status_code = this->client.post(url, &temp_params, data, &headers_for_sign, &response);
if (status_code != CURLcode::CURLE_OK) {
obj[CURL_ERROR_CODE] = status_code;
return obj;
}
std::string error;
std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
return obj;
}
void prepare_request(std::string url,
std::map<std::string, std::string> & params,
std::map<std::string, std::string> & headers)
{
params["aipSdk"] = "C";
params["aipSdkVersion"] = AIP_SDK_VERSION;
if (_has_decide_type) {
if (_is_bce) {
std::string method = "POST";
sign(method, url, params, headers, ak, sk);
} else {
params["access_token"] = this->getAccessToken();
}
return;
}
if (getAccessToken() == "") {
_is_bce = true;
} else {
const char * t = std::strstr(this->_scope.c_str(), "brain_all_scope");
if (t == NULL)
{
_is_bce = true;
}
}
_has_decide_type = true;
prepare_request(url, params, headers);
}
Json::Value requestjson(
std::string url,
Json::Value & data,
std::map<std::string, std::string> & params,
std::map<std::string, std::string> const & headers)
{
std::string response;
Json::Value obj;
auto headers_for_sign = headers;
auto temp_params = params;
this->prepare_request(url, temp_params, headers_for_sign);
int status_code = this->client.post(url, nullptr, data, nullptr, &response);
if (status_code != CURLcode::CURLE_OK) {
obj[aip::CURL_ERROR_CODE] = status_code;
return obj;
}
std::string error;
std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
return obj;
}
// Json::Value request_com(
// std::string const & url,
// Json::Value & data)
// {
// std::string response;
// Json::Value obj;
// int status_code = this->client.post(url, nullptr, data, nullptr, &response);
//
// if (status_code != CURLcode::CURLE_OK) {
// obj[aip::CURL_ERROR_CODE] = status_code;
// return obj;
// }
// std::string error;
// std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
// reader->parse(response.data(), response.data() + response.size(), &obj, &error);
//
// return obj;
// }
Json::Value request_com(
std::string const & url,
Json::Value & data,
std::map<std::string, std::string>* headers = nullptr,
std::map<std::string, std::string>* params = nullptr)
{
std::string response;
Json::Value obj;
std::map<std::string, std::string> headers_for_sign;
if (headers != nullptr) {
headers_for_sign = *headers;
}
std::map<std::string, std::string> temp_params;
if (params != nullptr) {
temp_params = *params;
}
this->prepare_request(url, temp_params, headers_for_sign);
int status_code = CURLcode::CURLE_OK;
if (headers == nullptr || headers->find("Content-Type") == headers->end()
|| (*headers)["Content-Type"] == "application/json") {
status_code = this->client.post(url, &temp_params, data, &headers_for_sign, &response);
} else if ((*headers)["Content-Type"] == "application/x-www-form-urlencoded") {
status_code = this->client.post_form(url, &temp_params, data, &headers_for_sign, &response);
}
if (status_code != CURLcode::CURLE_OK) {
obj[aip::CURL_ERROR_CODE] = status_code;
return obj;
}
std::string error;
std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
return obj;
}
};
}
#endif

View File

@ -0,0 +1,130 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_BASE64_H__
#define __AIP_BASE64_H__
#include <iostream>
#include <string>
namespace aip {
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(const char c)
{
return (isalnum(c) || (c == '+') || (c == '/'));
}
std::string base64_encode(const char * bytes_to_encode, unsigned int in_len)
{
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--)
{
char_array_3[i++] = *(bytes_to_encode++);
if(i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
{
ret += base64_chars[char_array_4[i]];
}
i = 0;
}
}
if(i)
{
for(j = i; j < 3; j++)
{
char_array_3[j] = '\0';
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(j = 0; (j < i + 1); j++)
{
ret += base64_chars[char_array_4[j]];
}
while((i++ < 3))
{
ret += '=';
}
}
return ret;
}
std::string base64_decode(std::string const & encoded_string)
{
int in_len = (int) encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;
for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
}
#endif

View File

@ -0,0 +1,306 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_HTTP_H__
#define __AIP_HTTP_H__
#include "curl/curl.h"
#include <sstream>
#include <string>
#include <map>
#include <json/json.h>
namespace aip {
inline size_t onWriteData(void * buffer, size_t size, size_t nmemb, void * userp)
{
std::string * str = dynamic_cast<std::string *>((std::string *)userp);
str->append((char *)buffer, size * nmemb);
return nmemb;
}
class HttpClient
{
private:
bool debug = false;
int connect_timeout = 10000;
int socket_timeout = 10000;
void makeUrlencodedForm(std::map<std::string, std::string> const & params, std::string * content) const
{
content->clear();
std::map<std::string, std::string>::const_iterator it;
for(it=params.begin(); it!=params.end(); it++)
{
char * key = curl_escape(it->first.c_str(), (int) it->first.size());
char * value = curl_escape(it->second.c_str(),(int) it->second.size());
*content += key;
*content += '=';
*content += value;
*content += '&';
curl_free(key);
curl_free(value);
}
}
void appendUrlParams(std::map<std::string, std::string> const & params, std::string* url) const
{
if(params.empty()) {
return;
}
std::string content;
this->makeUrlencodedForm(params, &content);
bool url_has_param = false;
for (const auto& ch : *url) {
if (ch == '?') {
url_has_param = true;
break;
}
}
if (url_has_param) {
url->append("&");
} else {
url->append("?");
}
url->append(content);
}
void appendHeaders(std::map<std::string, std::string> const & headers, curl_slist ** slist) const
{
std::ostringstream ostr;
std::map<std::string, std::string>::const_iterator it;
for(it=headers.begin(); it!=headers.end(); it++)
{
ostr << it->first << ":" << it->second;
*slist = curl_slist_append(*slist, ostr.str().c_str());
ostr.str("");
}
}
public:
HttpClient() = default;
HttpClient(const HttpClient &) = delete;
HttpClient & operator=(const HttpClient &) = delete;
void setConnectTimeout(int connect_timeout)
{
this->connect_timeout = connect_timeout;
}
void setSocketTimeout(int socket_timeout)
{
this->socket_timeout = socket_timeout;
}
void setDebug(bool debug)
{
this->debug = debug;
}
int get(
std::string url,
std::map<std::string, std::string> const * params,
std::map<std::string, std::string> const * headers,
std::string * response) const
{
CURL * curl = curl_easy_init();
struct curl_slist * slist = NULL;
if (headers) {
this->appendHeaders(*headers, &slist);
}
if (params) {
this->appendUrlParams(*params, &url);
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, this->connect_timeout);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, this->socket_timeout);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(curl, CURLOPT_VERBOSE, this->debug);
int status_code = curl_easy_perform(curl);
curl_easy_cleanup(curl);
curl_slist_free_all(slist);
return status_code;
}
int post(
std::string url,
std::map<std::string, std::string> const * params,
const std::string & body,
std::map<std::string, std::string> const * headers,
std::string * response) const
{
struct curl_slist * slist = NULL;
CURL * curl = curl_easy_init();
if (headers) {
this->appendHeaders(*headers, &slist);
}
if (params) {
this->appendUrlParams(*params, &url);
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl, CURLOPT_POST, true);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onWriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, this->connect_timeout);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, this->socket_timeout);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(curl, CURLOPT_VERBOSE, this->debug);
int status_code = curl_easy_perform(curl);
curl_easy_cleanup(curl);
curl_slist_free_all(slist);
return status_code;
}
/**
* application/x-www-form-urlencoded
* @param url
* @param params
* @param data
* @param headers
* @param response
* @return
*/
int post(
std::string url,
std::map<std::string, std::string> const * params,
std::map<std::string, std::string> const & data,
std::map<std::string, std::string> const * headers,
std::string * response) const
{
std::string body;
this->makeUrlencodedForm(data, &body);
return this->post(std::move(url), params, body, headers, response);
}
/**
* application/json
* @param url
* @param params
* @param data
* @param headers
* @param response
* @return
*/
int post(
std::string url,
std::map<std::string, std::string> const * params,
Json::Value const & data,
std::map<std::string, std::string> const * headers,
std::string * response) const
{
std::string body;
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> writer(swb.newStreamWriter());
std::ostringstream os;
writer->write(data, &os);
body = os.str();
std::map<std::string, std::string> temp_headers;
if (headers != nullptr) {
for (const auto & iter : *headers) {
temp_headers[iter.first] = iter.second;
}
}
if (temp_headers.find("Content-Type") == temp_headers.end()) {
temp_headers["Content-Type"] = "application/json";
}
return this->post(url.c_str(), params, body, &temp_headers, response);
}
/**
* application/x-www-form-urlencoded
* all type data
* @param url
* @param params
* @param data
* @param headers
* @param response
* @return
*/
int post_form(
std::string url,
std::map<std::string, std::string> const * params,
Json::Value const & data,
std::map<std::string, std::string> const * headers,
std::string * response) const
{
std::string body;
body.clear();
Json::Value::Members mem = data.getMemberNames();
for (auto iter = mem.begin(); iter != mem.end(); iter++) {
std::string str = "";
char * curl_escape_value;
char * key = curl_escape((*iter).c_str(), (int)((*iter).size()));
body += key;
body += '=';
Json::Value jsonValue = data[*iter];
switch(jsonValue.type()) {
case Json::realValue:
body += std::to_string(data[*iter].asDouble());
break;
case Json::intValue:
body += std::to_string(data[*iter].asInt64());
break;
case Json::booleanValue:
body += std::to_string(data[*iter].asBool());
break;
case Json::stringValue:
str = data[*iter].asString();
curl_escape_value = curl_escape(str.c_str(), (int)(str.size()));
body += curl_escape_value;
curl_free(curl_escape_value);
break;
default:
break;
}
body += '&';
curl_free(key);
}
return this->post(std::move(url), params, body, headers, response);
}
int post(
std::string url,
std::map<std::string, std::string> const * params,
std::map<std::string, std::string> const * headers,
std::string * response) const
{
const static std::string EMPTY_STRING;
return this->post(std::move(url), params, EMPTY_STRING, headers, response);
}
};
}
#endif

View File

@ -0,0 +1,283 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_UTILS_H__
#define __AIP_UTILS_H__
#include <string>
#include <fstream>
#include <ctype.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <algorithm>
#include <openssl/md5.h>
const int __BCE_VERSION__ = 1;
const int __BCE_EXPIRE__ = 1800;
namespace aip {
template<class CharT, class Traits, class Allocator>
std::basic_istream<CharT, Traits>& getall(std::basic_istream<CharT, Traits>& input,
std::basic_string<CharT, Traits, Allocator>& str) {
std::ostringstream oss;
oss << input.rdbuf();
str.assign(oss.str());
return input;
}
inline int get_file_content(const char *filename, std::string* out) {
std::ifstream in(filename, std::ios::in | std::ios::binary);
if (in) {
getall(in, *out);
return 0;
} else {
return -1;
}
}
inline std::string to_upper(std::string src)
{
std::transform(src.begin(), src.end(), src.begin(), [](unsigned char c) { return std::toupper(c); });
return src;
}
inline std::string to_lower(std::string src)
{
std::transform(src.begin(), src.end(), src.begin(), [](unsigned char c) { return std::tolower(c); });
return src;
}
inline std::string to_hex(unsigned char c, bool lower = false)
{
const std::string hex = "0123456789ABCDEF";
std::stringstream ss;
ss << hex[c >> 4] << hex[c & 0xf];
return lower ? to_lower(ss.str()) : ss.str();
}
inline time_t now()
{
return time(NULL);
}
std::string utc_time(time_t timestamp)
{
struct tm result_tm;
char buffer[32];
#ifdef _WIN32
gmtime_s(&result_tm, &timestamp);
#else
gmtime_r(&timestamp, &result_tm);
#endif
size_t size = strftime(buffer, 32, "%Y-%m-%dT%H:%M:%SZ", &result_tm);
return std::string(buffer, size);
}
void url_parse(
const std::string & url,
std::map<std::string, std::string> & params)
{
int pos = (int)url.find("?");
if (pos != -1)
{
int key_start = pos + 1,
key_len = 0,
val_start = 0;
for (int i = key_start; i <= (int)url.size(); ++i)
{
switch (url[i])
{
case '=':
key_len = i - key_start;
val_start = i + 1;
break;
case '\0':
case '&':
if (key_len != 0)
{
params[url.substr(key_start, key_len)] = url.substr(val_start, i - val_start);
key_start = i + 1;
key_len = 0;
}
break;
default:
break;
}
}
}
}
std::string url_encode(const std::string & input, bool encode_slash=true)
{
std::stringstream ss;
const char *str = input.c_str();
for (uint32_t i = 0; i < input.size(); i++)
{
unsigned char c = str[i];
if (isalnum(c) || c == '_' || c == '-' || c == '~' || c == '.' || (!encode_slash && c == '/'))
{
ss << c;
}
else
{
ss << "%" << to_hex(c);
}
}
return ss.str();
}
std::string canonicalize_params(std::map<std::string, std::string> & params)
{
std::vector<std::string> v;
v.reserve(params.size());
for (auto & it : params) {
v.push_back(url_encode(it.first) + "=" + url_encode(it.second));
}
std::sort(v.begin(), v.end());
std::string result;
for (auto & it : v)
{
result.append((result.empty() ? "" : "&") + it);
}
return result;
}
std::string canonicalize_headers(std::map<std::string, std::string> & headers)
{
std::vector<std::string> v;
v.reserve(headers.size());
for (auto & it : headers) {
v.push_back(url_encode(to_lower(it.first)) + ":" + url_encode(it.second));
}
std::sort(v.begin(), v.end());
std::string result;
for (auto & it : v)
{
result.append((result.empty() ? "" : "\n") + it);
}
return result;
}
std::string get_headers_keys(std::map<std::string, std::string> & headers)
{
std::vector<std::string> v;
v.reserve(headers.size());
for (auto & it : headers) {
v.push_back(to_lower(it.first));
}
std::string result;
for (auto & it : v)
{
result.append((result.empty() ? "" : ";") + it);
}
return result;
}
std::string get_host(const std::string & url)
{
int pos = (int)url.find("://") + 3;
return url.substr(
pos,
url.find('/', pos) - pos
);
}
std::string get_path(const std::string & url)
{
int path_start = (int)url.find('/', url.find("://") + 3);
int path_end = (int)url.find('?');
path_end = path_end == -1 ? (int)url.size() : path_end;
return url.substr(path_start, path_end - path_start);
}
std::string hmac_sha256(
const std::string & src,
const std::string & sk)
{
const EVP_MD *evp_md = EVP_sha256();
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len = 0;
if (HMAC(evp_md,
reinterpret_cast<const unsigned char *>(sk.data()), (int)sk.size(),
reinterpret_cast<const unsigned char *>(src.data()), src.size(),
md, &md_len) == NULL)
{
return "";
}
std::stringstream ss;
for (int i = 0; i < (int)md_len; ++i)
{
ss << to_hex(md[i], true);
}
return ss.str();
}
void sign(
std::string method,
std::string & url,
std::map<std::string, std::string> & params,
std::map<std::string, std::string> & headers,
std::string & ak,
std::string & sk)
{
url_parse(url, params);
headers["Host"] = get_host(url);
std::string timestamp = utc_time(now());
headers["x-bce-date"] = timestamp;
std::stringstream ss;
ss << "bce-auth-v" << __BCE_VERSION__ << "/" << ak << "/"
<< timestamp << "/" << __BCE_EXPIRE__;
std::string val = ss.str();
std::string sign_key = hmac_sha256(val, sk);
ss.str("");
ss << to_upper(method) << '\n' << url_encode(get_path(url), false)
<< '\n' << canonicalize_params(params)
<< '\n' << canonicalize_headers(headers);
std::string signature = hmac_sha256(ss.str(), sign_key);
ss.str("");
ss << "bce-auth-v" << __BCE_VERSION__ << "/" << ak << "/"
<< timestamp << "/" << __BCE_EXPIRE__ << "/"
<< get_headers_keys(headers) << "/" << signature;
headers["authorization"] = ss.str();
}
}
#endif

View File

@ -0,0 +1,220 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_BODY_ANALYSIS_H__
#define __AIP_BODY_ANALYSIS_H__
#include "base/base.h"
namespace aip {
class Bodyanalysis : public AipBase
{
public:
std::string _body_analysis_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/body_analysis";
std::string _body_attr_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/body_attr";
std::string _body_num_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/body_num";
std::string _driver_behavior_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/driver_behavior";
std::string _body_seg_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/body_seg";
std::string _gesture_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/gesture";
std::string _body_tracking_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/body_tracking";
std::string _hand_analysis_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/hand_analysis";
std::string _body_danger_v1 =
"https://aip.baidubce.com/rest/2.0/video-classify/v1/body_danger";
std::string _fingertip_v1 =
"https://aip.baidubce.com/rest/2.0/image-classify/v1/fingertip";
Bodyanalysis(const std::string & app_id, const std::string & ak, const std::string & sk)
: AipBase(app_id, ak, sk)
{
}
/**
* 人体关键点识别
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/0k3cpyxme
*/
Json::Value body_analysis_v1(
std::string const &image)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
Json::Value result =
this->request(_body_analysis_v1, null, data, null);
return result;
}
/**
* 人体检测与属性识别
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/Ak3cpyx6v
*/
Json::Value body_attr_v1(
std::string const &image,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_body_attr_v1, null, data, null);
return result;
}
/**
* 人流量统计
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/7k3cpyy1t
*/
Json::Value body_num_v1(
std::string const &image,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_body_num_v1, null, data, null);
return result;
}
/**
* 驾驶行为分析
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/Nk3cpywct
*/
Json::Value driver_behavior_v1(
std::string const &image,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_driver_behavior_v1, null, data, null);
return result;
}
/**
* 人像分割
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/Fk3cpyxua
*/
Json::Value body_seg_v1(
std::string const &image,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_body_seg_v1, null, data, null);
return result;
}
/**
* 手势识别
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/4k3cpywrv
*/
Json::Value gesture_v1(
std::string const &image)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
Json::Value result =
this->request(_gesture_v1, null, data, null);
return result;
}
/**
* 人流量统计(动态版)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/wk3cpyyog
*/
Json::Value body_tracking_v1(
std::string const &dynamic,
std::string const &image,
Json::Value & options)
{
Json::Value data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
data["dynamic"] = dynamic;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_body_tracking_v1, data, &headers);
return result;
}
/**
* 手部关键点识别
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/Kk3cpyxeu
*/
Json::Value hand_analysis_v1(
std::string const &image)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
Json::Value result =
this->request(_hand_analysis_v1, null, data, null);
return result;
}
/**
* 危险行为识别
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/uk3cpywke
*/
Json::Value body_danger_v1(
std::string const &video_data)
{
std::map<std::string, std::string> data;
data["data"] = base64_encode(video_data.c_str(), (int) video_data.size());
Json::Value result =
this->request(_body_danger_v1, null, data, null);
return result;
}
/**
* 指尖检测
* 接口使用文档链接: https://ai.baidu.com/ai-doc/BODY/Jk7ir38ut
*/
Json::Value fingertip_v1(
std::string const &image)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
Json::Value result =
this->request(_fingertip_v1, null, data, null);
return result;
}
};
}
#endif

View File

@ -0,0 +1,327 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_CONTENTCENSOR_H__
#define __AIP_CONTENTCENSOR_H__
#include "base/base.h"
namespace aip {
class Contentcensor: public AipBase
{
public:
std::string _img_censor_user_defined_v2 =
"https://aip.baidubce.com/rest/2.0/solution/v1/img_censor/v2/user_defined";
std::string _text_censor_user_defined_v2 =
"https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined";
std::string _live_save_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/live/v1/config/save";
std::string _live_stop_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/live/v1/config/stop";
std::string _live_view_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/live/v1/config/view";
std::string _live_pull_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/live/v1/audit/pull";
std::string _video_censor_submit_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/video_censor/v1/video/submit";
std::string _video_censor_pull_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/video_censor/v1/video/pull";
std::string _async_voice_submit_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/async_voice/submit";
std::string _async_voice_pull_v1 = "https://aip.baidubce.com/rest/2.0/solution/v1/async_voice/pull";
std::string _document_censor_submit_url = "https://aip.baidubce.com/rest/2.0/solution/v1/solution/document/v1/submit";
std::string _document_censor_pull_url = "https://aip.baidubce.com/rest/2.0/solution/v1/solution/document/v1/pull";
Contentcensor(const std::string & app_id, const std::string & ak, const std::string & sk)
: AipBase(app_id, ak, sk)
{
}
/**
* 内容审核平台-图像
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/jk42xep4e
*/
Json::Value img_censor_user_defined_v2_img(std::string const &image, const Json::Value & options)
{
Json::Value data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_img_censor_user_defined_v2, data, &headers);
return result;
}
/**
* 内容审核平台-图像
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/jk42xep4e
*/
Json::Value img_censor_user_defined_v2_url(std::string const &imgUrl, const Json::Value & options)
{
Json::Value data;
data["imgUrl"] = imgUrl;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_img_censor_user_defined_v2, data, &headers);
return result;
}
/**
* 内容审核平台-文本
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/Rk3h6xb3i
*/
Json::Value text_censor_user_defined_v2(std::string const &text)
{
std::map<std::string, std::string> data;
data["text"] = text;
Json::Value result =
this->request(_text_censor_user_defined_v2, null, data, null);
return result;
}
/**
* 内容审核平台-直播流(新增任务)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/mkxlraoz5
*/
Json::Value live_save_v1(std::string const &streamUrl, std::string const &streamType,
std::string const &extId, long long const &startTime,
long long const &endTime, std::string const &streamName, const Json::Value & options)
{
Json::Value data;
data["streamUrl"] = streamUrl;
data["streamType"] = streamType;
data["extId"] = extId;
data["startTime"] = startTime;
data["endTime"] = endTime;
data["streamName"] = streamName;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_live_save_v1, data, &headers);
return result;
}
/**
* 内容审核平台-直播流(删除任务)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/Ckxls2owb
*/
Json::Value live_stop_v1(
std::string const &taskId,
const Json::Value & options)
{
Json::Value data;
data["taskId"] = taskId;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_live_stop_v1, data, &headers);
return result;
}
/**
* 内容审核平台-直播流(查看配置)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/ckxls6tl1
*/
Json::Value live_view_v1(
std::string const &taskId,
const Json::Value & options)
{
Json::Value data;
data["taskId"] = taskId;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_live_view_v1, data, &headers);
return result;
}
/**
* 内容审核平台-直播流(获取结果)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/Pkxlshd1s
*/
Json::Value live_pull_v1(
std::string const &taskId,
const Json::Value & options)
{
Json::Value data;
data["taskId"] = taskId;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_live_view_v1, data, &headers);
return result;
}
/**
* 内容审核平台-长视频(提交任务)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/bksy7ak30
*/
Json::Value video_censor_submit_v1(
std::string const &url,
std::string const &extId,
const Json::Value & options)
{
Json::Value data;
data["url"] = url;
data["extId"] = extId;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_video_censor_submit_v1, data, &headers);
return result;
}
/**
* 内容审核平台-长视频(获取结果)
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/jksy7j3jv
*/
Json::Value video_censor_pull_v1(
std::string const &taskId,
const Json::Value & options)
{
Json::Value data;
data["taskId"] = taskId;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_video_censor_pull_v1, data, &headers);
return result;
}
/**
* 音频文件异步审核
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/akxlple3t
*/
Json::Value async_voice_submit_v1(
std::string const &url, std::string const &fmt, int rate,
const Json::Value & options)
{
Json::Value data;
data["url"] = url;
data["fmt"] = fmt;
data["rate"] = rate;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_async_voice_submit_v1, data, &headers);
return result;
}
/**
* 音频文件异步审核-查询
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/jkxlpxllo
*/
Json::Value async_voice_pull_v1_taskid(
std::string const &taskId)
{
std::map<std::string, std::string> data;
data["taskId"] = taskId;
Json::Value result =
this->request(_async_voice_pull_v1, null, data, null);
return result;
}
/**
* 音频文件异步审核-查询
* 接口使用文档链接: https://ai.baidu.com/ai-doc/ANTIPORN/jkxlpxllo
*/
Json::Value async_voice_pull_v1_audioid(
std::string const &audioId)
{
std::map<std::string, std::string> data;
data["audioId"] = audioId;
Json::Value result =
this->request(_async_voice_pull_v1, null, data, null);
return result;
}
/**
* 文档审核-提交任务
* https://ai.baidu.com/ai-doc/ANTIPORN/2l8484xvl
*/
Json::Value document_censor_file_submit(
std::string const & file_name,
std::string const & document,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["fileBase64"] = base64_encode(document.c_str(), (int) document.size());
data["fileName"] = file_name;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_document_censor_submit_url, null, data, null);
return result;
}
/**
* 文档审核-提交任务
* https://ai.baidu.com/ai-doc/ANTIPORN/2l8484xvl
*/
Json::Value document_censor_url_submit(
std::string const & file_name,
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
data["fileName"] = file_name;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_document_censor_submit_url, null, data, null);
return result;
}
/**
* 文档审核-拉取结果
* https://ai.baidu.com/ai-doc/ANTIPORN/4l848df5n
*/
Json::Value document_censor_pull(
std::string const & task_id,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["taskId"] = task_id;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_document_censor_pull_url, null, data, null);
return result;
}
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_IMAGECENSOR_H__
#define __AIP_IMAGECENSOR_H__
#include "base/base.h"
namespace aip {
class Imagecensor: public AipBase
{
public:
std::string _anti_porn =
"https://aip.baidubce.com/rest/2.0/antiporn/v1/detect";
std::string _anti_porn_gif =
"https://aip.baidubce.com/rest/2.0/antiporn/v1/detect_gif";
std::string _anti_terror =
"https://aip.baidubce.com/rest/2.0/antiterror/v1/detect";
Imagecensor(const std::string & app_id, const std::string & ak, const std::string & sk): AipBase(app_id, ak, sk)
{
}
/**
* anti_porn
* 该请求用于鉴定图片的色情度。即对于输入的一张图片(可正常解码,且长宽比适宜),输出图片的色情度。目前支持三个维度:色情、性感、正常。
* @param image 图像文件二进制内容可以使用aip::get_file_content函数获取
* options 可选参数:
*/
Json::Value anti_porn(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_anti_porn, null, data, null);
return result;
}
/**
* anti_porn_gif
* 该请求用于鉴定GIF图片的色情度对于非gif接口请使用色情识别接口。接口会对图片中每一帧进行识别并返回所有检测结果中色情值最大的为结果。目前支持三个维度色情、性感、正常。
* @param image 图像文件二进制内容可以使用aip::get_file_content函数获取
* options 可选参数:
*/
Json::Value anti_porn_gif(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_anti_porn_gif, null, data, null);
return result;
}
/**
* anti_terror
* 该请求用于鉴定图片是否涉暴涉恐。即对于输入的一张图片(可正常解码,且长宽比适宜),输出图片的涉暴涉恐程度。
* @param image 图像文件二进制内容可以使用aip::get_file_content函数获取
* options 可选参数:
*/
Json::Value anti_terror(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_anti_terror, null, data, null);
return result;
}
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,693 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_IMAGEPROCESS_H__
#define __AIP_IMAGEPROCESS_H__
#include "base/base.h"
namespace aip {
class Imageprocess: public AipBase
{
public:
std::string _image_definition_enhance =
"https://aip.baidubce.com/rest/2.0/image-process/v1/image_definition_enhance";
std::string _sky_seg =
"https://aip.baidubce.com/rest/2.0/image-process/v1/sky_seg";
std::string _image_tyle_trans =
"https://aip.baidubce.com/rest/2.0/image-process/v1/style_trans";
std::string _selfie_anime =
"https://aip.baidubce.com/rest/2.0/image-process/v1/selfie_anime";
std::string _color_enhance =
"https://aip.baidubce.com/rest/2.0/image-process/v1/color_enhance";
std::string _image_inpainting =
"https://aip.baidubce.com/rest/2.0/image-process/v1/inpainting";
std::string _image_quality_enhance_v1 =
"https://aip.baidubce.com/rest/2.0/image-process/v1/image_quality_enhance";
std::string _contrast_enhance_v1 =
"https://aip.baidubce.com/rest/2.0/image-process/v1/contrast_enhance";
std::string _dehaze_v1 =
"https://aip.baidubce.com/rest/2.0/image-process/v1/dehaze";
std::string _colourize_v1 =
"https://aip.baidubce.com/rest/2.0/image-process/v1/colourize";
std::string _stretch_restore_v1 =
"https://aip.baidubce.com/rest/2.0/image-process/v1/stretch_restore";
std::string _remove_moire_v1 = "https://aip.baidubce.com/rest/2.0/image-process/v1/remove_moire";
std::string _customize_stylization_v1 =
"https://aip.baidubce.com/rest/2.0/image-process/v1/customize_stylization";
std::string _doc_repair_v1 = "https://aip.baidubce.com/rest/2.0/image-process/v1/doc_repair";
std::string _denoise_v1 = "https://aip.baidubce.com/rest/2.0/image-process/v1/denoise";
Imageprocess(const std::string & app_id, const std::string & ak, const std::string & sk):
AipBase(app_id, ak, sk)
{
}
/**
* 图像修复
* 去除图片中不需要的遮挡物,并用背景内容填充,提高图像质量。
* @param image 二进制图像数据
* @param rectangle 要去除的位置为规则矩形时,给出坐标信息.每个元素包含left, top, width, heightint 类型
* options 可选参数:
*/
Json::Value imageinpainting(
std::string const & image,
Json::Value & rectangle,
std::map<std::string, std::string> options)
{
Json::Value data;
std::string access_token = this->getAccessToken();
data["image"] = base64_encode(image.c_str(), (int) image.size());
data["rectangle"] = rectangle;
std::map< std::string,std::string >::iterator it ;
for(it = options.begin(); it != options.end(); it++)
{
data[it->first] = it->second;
}
std::string mid = "?access_token=";
std::string url = _image_inpainting+mid+access_token;
Json::Value result =
this->request_com(url, data);
return result;
}
/**
* 图像修复
* 去除图片中不需要的遮挡物,并用背景内容填充,提高图像质量。
* @param image 二进制图像数据
* @param rectangle 要去除的位置为规则矩形时,给出坐标信息.每个元素包含left, top, width, heightint 类型
* options 可选参数:
*/
Json::Value imageinpainting_url(
std::string const & url,
Json::Value & rectangle,
std::map<std::string, std::string> options)
{
Json::Value data;
data["url"] = url;
data["rectangle"] = rectangle;
std::map< std::string,std::string >::iterator it ;
for(it = options.begin(); it != options.end(); it++)
{
data[it->first] = it->second;
}
Json::Value result =
this->request_com(_image_inpainting, data);
return result;
}
/**
* 图像色彩增强
* 可智能调节图片的色彩饱和度、亮度、对比度,使得图片内容细节、色彩更加逼真,可用于提升网站图片、手机相册图片、视频封面图片的质量
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value colorenhance(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_color_enhance, null, data, null);
return result;
}
/**
* 人像动漫化接口
* 运用世界领先的对抗生成网络,结合人脸检测、头发分割、人像分割等技术,为用户量身定制千人千面的二次元动漫形象,并且可通过参数设置,生成戴口罩的二次元动漫人像
* @param image 二进制图像数据
* options 可选参数:
* type anime或者anime_mask。前者生成二次元动漫图后者生成戴口罩的二次元动漫人像
* mask_id 在type参数填入anime_mask时生效18之间的整数用于指定所使用的口罩的编码。type参数没有填入anime_mask或mask_id 为空时,生成不戴口罩的二次元动漫图。
*/
Json::Value selfieanime(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_selfie_anime, null, data, null);
return result;
}
/**
* 图像风格转换
* 可将图像转化成卡通画、铅笔画、彩色铅笔画或者哥特油画、彩色糖块油画、呐喊油画、神奈川冲浪里油画、奇异油画、薰衣草油画等共计9种风格可用于开展趣味活动或集成到美图应用中对图像进行风格转换
* @param image 二进制图像数据
* @param option 转换的风格
* options 可选参数:
*/
Json::Value imagestyletrans(
std::string const & image,
std::string const & option,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
data["option"] = option;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_image_tyle_trans, null, data, null);
return result;
}
/**
* 天空分割
* 可智能分割出天空边界位置,输出天空和其余背景的灰度图和二值图,可用于图像二次处理,进行天空替换、抠图等图片编辑场景。
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value skyseg(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_sky_seg, null, data, null);
return result;
}
/**
* 图像清晰增强
* 对压缩后的模糊图像实现智能快速去噪,优化图像纹理细节,使画面更加自然清晰
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value imagedefinitionenhance(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_image_definition_enhance, null, data, null);
return result;
}
/**
* 图像风格转换
* 图像风格转换
* @param url 图片完整url
* @param option 转换的风格
* options 可选参数:
*/
Json::Value imagestyletransurl(
std::string const & url,
std::string const & option,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
data["option"] = option;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_image_tyle_trans, null, data, null);
return result;
}
/**
* 图像色彩增强
* @param url 图片完整url
* options 可选参数:
*/
Json::Value colorenhanceurl(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_color_enhance, null, data, null);
return result;
}
/**
* 人像动漫化接口
* 人像动漫化接口
* @param url 图片完整url
* options 可选参数:
* type anime或者anime_mask。前者生成二次元动漫图后者生成戴口罩的二次元动漫人像
* mask_id 在type参数填入anime_mask时生效18之间的整数用于指定所使用的口罩的编码。type参数没有填入anime_mask或mask_id 为空时,生成不戴口罩的二次元动漫图。
*/
Json::Value selfieanimeurl(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_selfie_anime, null, data, null);
return result;
}
/**
* 天空分割
* @param url 图片完整url
* options 可选参数:
*/
Json::Value skysegurl(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_sky_seg, null, data, null);
return result;
}
/**
* 图像清晰增强
* @param url 图片完整url
* options 可选参数:
*/
Json::Value imagedefinitionenhanceurl(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_image_definition_enhance, null, data, null);
return result;
}
/**
* 图像无损放大
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value image_quality_enhance_v1(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_image_quality_enhance_v1, null, data, null);
return result;
}
/**
* 图像无损放大
* @param url 图片完整url
* options 可选参数:
*/
Json::Value image_quality_enhance_v1_url(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_image_quality_enhance_v1, null, data, null);
return result;
}
/**
* 图像对比度增强
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value contrast_enhance_v1(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_contrast_enhance_v1, null, data, null);
return result;
}
/**
* 图像对比度增强
* @param url 图片完整url
* options 可选参数:
*/
Json::Value contrast_enhance_v1_url(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_contrast_enhance_v1, null, data, null);
return result;
}
/**
* 图像去雾
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value dehaze_v1(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_dehaze_v1, null, data, null);
return result;
}
/**
* 图像去雾
* @param url 图片完整url
* options 可选参数:
*/
Json::Value dehaze_v1_url(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_dehaze_v1, null, data, null);
return result;
}
/**
* 黑白图像上色
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value colourize_v1(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_colourize_v1, null, data, null);
return result;
}
/**
* 黑白图像上色
* @param url 图片完整url
* options 可选参数:
*/
Json::Value colourize_v1_url(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_colourize_v1, null, data, null);
return result;
}
/**
* 拉伸图像恢复
* @param image 二进制图像数据
* options 可选参数:
*/
Json::Value stretch_restore_v1(
std::string const & image,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_stretch_restore_v1, null, data, null);
return result;
}
/**
* 拉伸图像恢复
* @param url 图片完整url
* options 可选参数:
*/
Json::Value stretch_restore_v1_url(
std::string const & url,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_stretch_restore_v1, null, data, null);
return result;
}
/**
* 图片去摩尔纹
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/ql4wdlnc0
*/
Json::Value remove_moire_v1(
std::string const & image,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_remove_moire_v1, null, data, null);
return result;
}
/**
* 图片去摩尔纹 - url
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/ql4wdlnc0
*/
Json::Value remove_moire_v1_url(
std::string const & url,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_remove_moire_v1, null, data, null);
return result;
}
/**
* 图片去摩尔纹 - pdf
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/ql4wdlnc0
*/
Json::Value remove_moire_v1_pdf(
std::string const & pdf,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["pdf_file"] = base64_encode(pdf.c_str(), (int) pdf.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_remove_moire_v1, null, data, null);
return result;
}
/**
* 图像风格自定义
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/al50vf6bq
*/
Json::Value customize_stylization_v1(std::string const & image, Json::Value & options)
{
Json::Value data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_customize_stylization_v1, data, &headers);
return result;
}
/**
* 图像风格自定义 - url
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/al50vf6bq
*/
Json::Value customize_stylization_v1_url(std::string const & url, Json::Value & options)
{
Json::Value data;
data["url"] = url;
merge_json(data, options);
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_customize_stylization_v1, data, &headers);
return result;
}
/**
* 文档图片去底纹
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/Nl6os53ab
*/
Json::Value doc_repair_v1(
std::string const & image,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_doc_repair_v1, null, data, null);
return result;
}
/**
* 文档图片去底纹 - url
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/Nl6os53ab
*/
Json::Value doc_repair_v1_url(
std::string const &url,
const std::map<std::string, std::string> &options)
{
std::map<std::string, std::string> data;
data["url"] = url;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_doc_repair_v1, null, data, null);
return result;
}
/**
* 图像去噪
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/Tl78sby7g
*/
Json::Value denoise_v1(
std::string const & image, int option)
{
Json::Value data;
data["image"] = base64_encode(image.c_str(), (int) image.size());
data["option"] = option;
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_denoise_v1, data, &headers);
return result;
}
/**
* 图像去噪 - url
* 接口使用文档链接: https://ai.baidu.com/ai-doc/IMAGEPROCESS/Tl78sby7g
*/
Json::Value denoise_v1_url(
std::string const &url, int option)
{
Json::Value data;
data["url"] = url;
data["option"] = option;
std::map<std::string, std::string> headers;
headers["Content-Type"] = "application/x-www-form-urlencoded";
Json::Value result = this->request_com(_denoise_v1, data, &headers);
return result;
}
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,206 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_KG_H__
#define __AIP_KG_H__
#include "base/base.h"
namespace aip {
class Kg: public AipBase
{
public:
std::string _create_task =
"https://aip.baidubce.com/rest/2.0/kg/v1/pie/task_create";
std::string _update_task =
"https://aip.baidubce.com/rest/2.0/kg/v1/pie/task_update";
std::string _task_info =
"https://aip.baidubce.com/rest/2.0/kg/v1/pie/task_info";
std::string _task_query =
"https://aip.baidubce.com/rest/2.0/kg/v1/pie/task_query";
std::string _task_start =
"https://aip.baidubce.com/rest/2.0/kg/v1/pie/task_start";
std::string _task_status =
"https://aip.baidubce.com/rest/2.0/kg/v1/pie/task_status";
Kg(const std::string & app_id, const std::string & ak, const std::string & sk): AipBase(app_id, ak, sk)
{
}
/**
* create_task
* 创建一个新的信息抽取任务
* @param name 任务名字
* @param template_content json string 解析模板内容
* @param input_mapping_file 抓取结果映射文件的路径
* @param url_pattern url pattern
* @param output_file 输出文件名字
* options 可选参数:
* limit_count 限制解析数量limit_count为0时进行全量任务limit_count>0时只解析limit_count数量的页面
*/
Json::Value create_task(
std::string const & name,
std::string const & template_content,
std::string const & input_mapping_file,
std::string const & url_pattern,
std::string const & output_file,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["name"] = name;
data["template_content"] = template_content;
data["input_mapping_file"] = input_mapping_file;
data["url_pattern"] = url_pattern;
data["output_file"] = output_file;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_create_task, null, data, null);
return result;
}
/**
* update_task
* 更新任务配置,在任务重新启动后生效
* @param id 任务ID
* options 可选参数:
* name 任务名字
* template_content json string 解析模板内容
* input_mapping_file 抓取结果映射文件的路径
* url_pattern url pattern
* output_file 输出文件名字
*/
Json::Value update_task(
const int & id,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["id"] = std::to_string(id);
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_update_task, null, data, null);
return result;
}
/**
* task_info
* 根据任务id获取单个任务的详细信息
* @param id 任务ID
* options 可选参数:
*/
Json::Value task_info(
const int & id,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["id"] = std::to_string(id);
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_task_info, null, data, null);
return result;
}
/**
* task_query
* 该请求用于菜品识别。即对于输入的一张图片(可正常解码,且长宽比适宜),输出图片的菜品名称、卡路里信息、置信度。
* options 可选参数:
* id 任务ID精确匹配
* name 中缀模糊匹配,abc可以匹配abc,aaabc,abcde等
* status 要筛选的任务状态
* page 页码
* per_page 页码
*/
Json::Value task_query(
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_task_query, null, data, null);
return result;
}
/**
* task_start
* 启动一个已经创建的信息抽取任务
* @param id 任务ID
* options 可选参数:
*/
Json::Value task_start(
const int & id,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["id"] = std::to_string(id);
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_task_start, null, data, null);
return result;
}
/**
* task_status
* 查询指定的任务的最新执行状态
* @param id 任务ID
* options 可选参数:
*/
Json::Value task_status(
const int & id,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["id"] = std::to_string(id);
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_task_status, null, data, null);
return result;
}
};
}
#endif

View File

@ -0,0 +1,144 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_MACHINE_TRANSLATION_H__
#define __AIP_MACHINE_TRANSLATION_H__
#include "base/base.h"
namespace aip {
class Machinetranslation : public AipBase
{
public:
std::string _pictrans_v1 =
"https://aip.baidubce.com/file/2.0/mt/pictrans/v1";
std::string _texttrans_v1 =
"https://aip.baidubce.com/rpc/2.0/mt/texttrans/v1";
std::string _texttrans_with_dict_v1 =
"https://aip.baidubce.com/rpc/2.0/mt/texttrans-with-dict/v1";
std::string _doc_translation_create_v2 =
"https://aip.baidubce.com/rpc/2.0/mt/v2/doc-translation/create";
std::string _doc_translation_query_v2 =
"https://aip.baidubce.com/rpc/2.0/mt/v2/doc-translation/query";
std::string _speech_translation_v2 =
"https://aip.baidubce.com/rpc/2.0/mt/v2/speech-translation";
Machinetranslation(const std::string & app_id, const std::string & ak, const std::string & sk)
: AipBase(app_id, ak, sk)
{
}
/**
* 文本翻译-通用版
* 接口使用文档链接: https://ai.baidu.com/ai-doc/MT/4kqryjku9
*/
Json::Value texttrans_v1(
std::string const &from,
std::string const &to,
std::string const &q,
const Json::Value & options)
{
Json::Value data;
data["from"] = from;
data["to"] = to;
data["q"] = q;
merge_json(data, options);
Json::Value result = this->request_com(_texttrans_v1, data);
return result;
}
/**
* 文本翻译-词典版
* 接口使用文档链接: https://ai.baidu.com/ai-doc/MT/nkqrzmbpc
*/
Json::Value texttrans_with_dict_v1(
std::string const &from,
std::string const &to,
std::string const &q,
const Json::Value & options)
{
Json::Value data;
data["from"] = from;
data["to"] = to;
data["q"] = q;
merge_json(data, options);
Json::Value result = this->request_com(_texttrans_with_dict_v1, data);
return result;
}
/**
* 文档翻译
* 接口使用文档链接: https://ai.baidu.com/ai-doc/MT/Xky9x5xub
*/
Json::Value doc_translation_create_v2(
std::string const &from,
std::string const &to,
Json::Value & options)
{
Json::Value data;
data["from"] = from;
data["to"] = to;
merge_json(data, options);
Json::Value result =
this->request_com(_doc_translation_create_v2, data);
return result;
}
/**
* 文档翻译-文档状态查询
* 接口使用文档链接: https://ai.baidu.com/ai-doc/MT/Xky9x5xub
*/
Json::Value doc_translation_query_v2(
std::string const &id)
{
Json::Value data;
data["id"] = id;
Json::Value result = this->request_com(_doc_translation_query_v2, data);
return result;
}
/**
* 语音翻译
* 接口使用文档链接: https://ai.baidu.com/ai-doc/MT/el4cmi76f
*/
Json::Value speech_translation_v2(
std::string const &from,
std::string const &to,
std::string const &voice,
std::string const &format)
{
Json::Value data;
data["from"] = from;
data["to"] = to;
data["voice"] = base64_encode(voice.c_str(), (int) voice.size());
data["format"] = format;
Json::Value result =
this->request_com(_speech_translation_v2, data);
return result;
}
};
}
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
#ifndef __AIP_SPEECH_H__
#define __AIP_SPEECH_H__
#include "base/base.h"
#include <json/json.h>
namespace aip {
class Speech : public AipBase {
public:
std::string _asr = "https://vop.baidu.com/server_api";
std::string _tts = "http://tsn.baidu.com/text2audio";
Speech(const std::string app_id, const std::string &ak, const std::string &sk) : AipBase(app_id, ak, sk) {
}
Json::Value request_asr(
std::string const &url,
Json::Value &data) {
std::string response;
Json::Value obj;
int status_code = this->client.post(url, nullptr, data, nullptr, &response);
if (status_code != CURLcode::CURLE_OK) {
obj[aip::CURL_ERROR_CODE] = status_code;
return obj;
}
std::string error;
std::unique_ptr<Json::CharReader> reader(crbuilder.newCharReader());
reader->parse(response.data(), response.data() + response.size(), &obj, &error);
return obj;
}
Json::Value request_tts(
const std::string url,
std::map<std::string, std::string> &data,
std::string &file_content) {
std::string response;
Json::Value obj;
Json::Value file_json;
int status_code = this->client.post(url, nullptr, data, nullptr, &response);
if (status_code != CURLcode::CURLE_OK) {
obj[aip::CURL_ERROR_CODE] = status_code;
return obj;
}
file_content = response;
return obj;
}
Json::Value recognize(const std::string voice_binary, const std::string &format, const int &rate,
std::map<std::string, std::string> const &options) {
Json::Value data;
std::map<std::string, std::string>::const_iterator it;
for (it = options.begin(); it != options.end(); it++) {
data[it->first] = it->second;
if (it->first == "dev_pid") {
data[it->first] = atoi(it->second.c_str());
}
}
std::string token = this->getAccessToken();
data["speech"] = base64_encode(voice_binary.c_str(), (int) voice_binary.size());
data["format"] = format;
data["rate"] = std::to_string(rate);
data["channel"] = "1";
data["token"] = token;
data["cuid"] = this->getAk();
data["len"] = (int) voice_binary.size();
Json::Value result = this->request_asr(_asr, data);
return result;
}
Json::Value recognize_url(const std::string &url,
const std::string &callback, const std::string &format,
const int &rate,
std::map<std::string, std::string> options) {
Json::Value data;
std::map<std::string, std::string>::iterator it;
for (it = options.begin(); it != options.end(); it++) {
data[it->first] = it->second;
if (it->first == "dev_pid") {
data[it->first] = atoi(it->second.c_str());
}
}
std::string token = this->getAccessToken();
data["url"] = url;
data["callback"] = callback;
data["format"] = format;
data["rate"] = std::to_string(rate);
data["channel"] = 1;
data["token"] = token;
data["cuid"] = this->getAk();
Json::Value result = this->request_asr(_asr, data);
return result;
}
Json::Value text2audio(const std::string &text, std::map<std::string, std::string> const &options,
std::string &file_content) {
std::map<std::string, std::string> data;
std::map<std::string, std::string>::const_iterator it;
for (it = options.begin(); it != options.end(); it++) {
data[it->first] = it->second;
}
std::string token = this->getAccessToken();
data["tex"] = text;
data["lan"] = "zh";
data["ctp"] = "1";
data["tok"] = token;
data["cuid"] = this->getAk();
Json::Value result = this->request_tts(_tts, data, file_content);
return result;
}
};
}
#endif

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_VIDEOCENSOR_H__
#define __AIP_VIDEOCENSOR_H__
#include "base/base.h"
namespace aip {
class Videocensor: public AipBase
{
public:
std::string _video_url =
"https://aip.baidubce.com/rest/2.0/solution/v1/video_censor/v2/user_defined";
Videocensor(const std::string & app_id, const std::string & ak, const std::string & sk): AipBase(app_id, ak, sk)
{
}
/**
* voice_censor
* 本接口除了支持自定义配置外,还对返回结果进行了总体的包装,按照用户在控制台中配置的规则直接返回是否合规,如果不合规则指出具体不合规的内容。
* @param voice 语音文件二进制内容可以使用aip::get_file_content函数获取
* options 可选参数:
*/
Json::Value video_censor(
std::string const & name,
std::string const & url,
std::string const & extId,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["videoUrl"] = url;
data["name"] = name;
data["extId"] = extId;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_video_url, null, data, null);
return result;
}
};
}
#endif

View File

@ -0,0 +1,87 @@
/**
* Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @author baidu aip
*/
#ifndef __AIP_IMAGECENSOR_H__
#define __AIP_IMAGECENSOR_H__
#include "base/base.h"
namespace aip {
class Voicecensor: public AipBase
{
public:
std::string _voice_url =
"https://aip.baidubce.com/rest/2.0/solution/v1/voice_censor/v3/user_defined";
Voicecensor(const std::string & app_id, const std::string & ak, const std::string & sk): AipBase(app_id, ak, sk)
{
}
/**
* voice_censor
* 本接口除了支持自定义配置外,还对返回结果进行了总体的包装,按照用户在控制台中配置的规则直接返回是否合规,如果不合规则指出具体不合规的内容。
* @param voice 语音文件二进制内容可以使用aip::get_file_content函数获取
* options 可选参数:
*/
Json::Value voice_censor(
std::string const & voice,
std::int32_t const & rate,
std::string const & fmt,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["base64"] = base64_encode(voice.c_str(), (int) voice.size());
data["fmt"] = fmt;
data["rate"] = rate;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_voice_url, null, data, null);
return result;
}
/**
* voice_censor
* 本接口除了支持自定义配置外,还对返回结果进行了总体的包装,按照用户在控制台中配置的规则直接返回是否合规,如果不合规则指出具体不合规的内容。
* @param voice 语音文件二进制内容可以使用aip::get_file_content函数获取
* options 可选参数:
*/
Json::Value voice_censorUrl(
std::string const & url,
std::int32_t const & rate,
std::string const & fmt,
const std::map<std::string, std::string> & options)
{
std::map<std::string, std::string> data;
data["url"] = url;
data["fmt"] = fmt;
data["rate"] = rate;
std::copy(options.begin(), options.end(), std::inserter(data, data.end()));
Json::Value result =
this->request(_voice_url, null, data, null);
return result;
}
};
}
#endif

92
transmite/CMakeLists.txt Normal file
View File

@ -0,0 +1,92 @@
# 1. 添加cmake版本说明
cmake_minimum_required(VERSION 3.1.3)
# 2. 声明工程名称
project(transmite_server)
set(target "transmite_server")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 3. 检测并生成ODB框架代码
# 1. 添加所需的proto映射代码文件名称
set(proto_path ${CMAKE_CURRENT_SOURCE_DIR}/../proto)
set(proto_files base.proto user.proto transmite.proto)
# 2. 检测框架代码文件是否已经生成
set(proto_hxx "")
set(proto_cxx "")
set(proto_srcs "")
foreach(proto_file ${proto_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".proto" ".pb.cc" proto_cc ${proto_file})
string(REPLACE ".proto" ".pb.h" proto_hh ${proto_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${proto_cc})
add_custom_command(
PRE_BUILD
COMMAND protoc
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR} -I ${proto_path} --experimental_allow_proto3_optional ${proto_path}/${proto_file}
DEPENDS ${proto_path}/${proto_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
COMMENT "生成Protobuf框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc}
)
endif()
list(APPEND proto_srcs ${CMAKE_CURRENT_BINARY_DIR}/${proto_cc})
endforeach()
# 3. 检测并生成ODB框架代码
# 1. 添加所需的odb映射代码文件名称
set(odb_path ${CMAKE_CURRENT_SOURCE_DIR}/../odb)
set(odb_files chat_session_member.hxx)
# 2. 检测框架代码文件是否已经生成
set(odb_hxx "")
set(odb_cxx "")
set(odb_srcs "")
foreach(odb_file ${odb_files})
# 3. 如果没有生成,则预定义生成指令 -- 用于在构建项目之间先生成框架代码
string(REPLACE ".hxx" "-odb.hxx" odb_hxx ${odb_file})
string(REPLACE ".hxx" "-odb.cxx" odb_cxx ${odb_file})
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${odb_cxx})
add_custom_command(
PRE_BUILD
COMMAND odb
ARGS -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time ${odb_path}/${odb_file}
DEPENDS ${odb_path}/${odb_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
COMMENT "生成ODB框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
)
endif()
# 4. 将所有生成的框架源码文件名称保存起来 student-odb.cxx classes-odb.cxx
list(APPEND odb_srcs ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx})
endforeach()
# 4. 获取源码目录下的所有源码文件
set(src_files "")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source src_files)
# 5. 声明目标及依赖
add_executable(${target} ${src_files} ${proto_srcs} ${odb_srcs})
# 7. 设置需要连接的库
target_link_libraries(${target} -lgflags
-lspdlog -lfmt -lbrpc -lssl -lcrypto
-lprotobuf -lleveldb -letcd-cpp-api
-lcpprest -lcurl -lodb-mysql -lodb -lodb-boost
-lamqpcpp -lev)
set(trans_user_client "trans_user_client")
set(trans_user_files ${CMAKE_CURRENT_SOURCE_DIR}/test/user_client.cc)
add_executable(${trans_user_client} ${trans_user_files} ${proto_srcs})
target_link_libraries(${trans_user_client} -pthread -lgtest -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
set(transmite_client "transmite_client")
set(transmite_files ${CMAKE_CURRENT_SOURCE_DIR}/test/transmite_client.cc)
add_executable(${transmite_client} ${transmite_files} ${proto_srcs})
target_link_libraries(${transmite_client} -pthread -lgtest -lgflags -lspdlog -lfmt -lbrpc -lssl -lcrypto -lprotobuf -lleveldb -letcd-cpp-api -lcpprest -lcurl /usr/lib/x86_64-linux-gnu/libjsoncpp.so.19)
# 6. 设置头文件默认搜索路径
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../common)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../odb)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../third/include)
#8. 设置安装路径
INSTALL(TARGETS ${target} ${trans_user_client} ${transmite_client} RUNTIME DESTINATION bin)

Some files were not shown because too many files have changed in this diff Show More