C/C++精品项目之图床共享云存储(7):图片分享和浏览的优化,共享文件的排行榜
目录
C/C++精品项目之图床共享云存储(1):基础组件-CSDN博客
C/C++精品项目之图床共享云存储(2):MySql连接池_mysql 连接池c++ ping-CSDN博客
C/C++精品项目之图床共享云存储(3):网络缓冲区类和main-CSDN博客
C/C++精品项目之图床共享云存储(4):注册,登录,token,排序文件-CSDN博客
一:图片分享的优化
我们今天首先把分享和共享给搞清楚。我之前的文件好像将共享和分享经常说在一起,到今天我突然发现分享和共享用在的地方是不同的,下面让我带你们好好屡屡。
我们上次讲解了图片的分享,我们这个图片的分享,在前端就是一个按钮,我们点击这个分享按钮,我们就进入了分享的接口,然后在最后将我们生成的urlmd5进行返回。这是分享,分享给别人一个urlmd5,它使用这个urlmd5,就可以访问我们分享出来的文件。
而共享并不需要我们进行分享出来一个urlmd5出来,我们将文件共享之后,其他所有人都可以看到,而分享,只有有分享的urlmd5才能看到。
这里我们开始讲解我们优化后的图片分享功能,我们在之前的代码中添加了redis的服务,我们将一些数据存放到redis中去,我们之前使用的share表,其中存放了md5和urlmd5,我们的查询操作是在这里面进行的,后来我们经过修改,将这俩数据给分开了,md5仍然在info表中,而share表中只有urlmd5了,但是添加了这个文件的完整url下载地址了,也就是说我们不需要通过urlmd5去查找md5然后再找url了,我们直接可以在urlmd5这个表中找到url。
当然我们在这里加入了redis,我们不仅要在mysql中添加一组数据,而且还要在redis中也要添加一样的数据。当我们去查询的时候,直接去查询redis去不仅提高速度,还降低mysql的压力。
//现在是分享链接:生成urlmd5,然后找到info表里的完整url下载地址,然后将urlmd5,url,用户名等插入到新的分享表中去,这里面没有对应的md5了。 \
然后将这一份相同的信息,插入到redis中去,一样是没有对应的md5.
//分享图片
static void handleSharePicture(const char *user, const char *file_md5,
const char *file_name, string &str_json) {
char sql_cmd[SQL_MAX_LEN] = {0};
string file_url;
int file_size = 0;
CResultSet *result_set = NULL;
//数据库操作
// 获取数据库连接
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_master");
AUTO_REL_DBCONN(db_manager, db_conn);
CacheManager *cache_manager = CacheManager::getInstance();
CacheConn *cache_conn = cache_manager->GetCacheConn("token");
AUTO_REL_CACHECONN(cache_manager, cache_conn);
//查询file_info url
// 通过文件的MD5查找对应的url地址
sprintf(sql_cmd, "select url,size from file_info where md5 ='%s'", file_md5);
LogInfo("执行: {}", sql_cmd);
result_set = db_conn->ExecuteQuery(sql_cmd);
if (result_set && result_set->Next()) {
file_url = result_set->GetString("url");
file_size = result_set->GetInt("size");
delete result_set;
} else {
if (result_set)
delete result_set;
LogError("{} 操作失败", sql_cmd);
encodeSharePictureJson(HTTP_RESP_FAIL, "", str_json);
return;
}
LogInfo("url: {}, size: {}", file_url, file_size);
//操作数据库
string urlmd5 = RandomString(32); // uuid生成唯一值,再转成base64
//分享时间
//获取当前时间
time_t now = time(NULL);
char create_time[TIME_STRING_LEN];
strftime(create_time, TIME_STRING_LEN - 1, "%Y-%m-%d %H:%M:%S", localtime(&now));
LogInfo("urlmd5:{}", urlmd5);
char key[5] = {0}; //提取码目前没有给到前端
sprintf(sql_cmd,
"insert into share_picture_list2 (user, file_url, file_name, urlmd5, "
"`key`, pv, size, create_time) values ('%s', '%s', '%s', '%s', '%s', %d, %d, "
"'%s')", user, file_url.c_str(), file_name, urlmd5.c_str(), key, 0,
file_size, create_time);
LogInfo("执行: {}", sql_cmd);
if (!db_conn->ExecuteCreate(sql_cmd)) {
LogError("{} 操作失败", sql_cmd);
encodeSharePictureJson(HTTP_RESP_FAIL, urlmd5, str_json);
} else {
encodeSharePictureJson(HTTP_RESP_OK, urlmd5, str_json);
//成功则加载到redis
cache_conn->Hset(urlmd5, "user", user);
cache_conn->Hset(urlmd5, "file_url", file_url);
cache_conn->Hset(urlmd5, "file_name", file_name);
cache_conn->Hset(urlmd5, "pv", FormatString("%d", 0));
cache_conn->Hset(urlmd5, "size", FormatString("%d", file_size));
cache_conn->Hset(urlmd5, "create_time", create_time);
}
}
二:图片浏览的优化
我们在上面不是说了我们可以直接通过urlmd5找到完整的下载地址吗,于是我们下面先从redis中查找这个urlmd5的信息,如果找到,直接返回,找不到,我们再从mysql中进行查询,如果找不到说明我们已经取消文件的分享,如果在mysql中找到,我们直接包装返回即可。
当然下面我们还增加了一个浏览量的优化,因为访问Mysql是比较慢的,而访问redis是很快的,因此,我们可以每次先增加redis中的浏览量,每五次在mysql中进行一次更新操作,这样就减少了mysql的很多操作。
//分享图片时 通过hash存储 urlmd5对应的字段
//浏览图片时 先通过redis读取,如果没有再从mysql读取,如果mysql也没有则说明这个分享失效了
//这个浏览发来urlmd5,我们先从redis中读取这个数据,如果有,直接返回对应的url,如果没有,那么从mysql中查询:share_picture2,返回url \
没有也报错,然后更新阅读计数,我们这里的更新是先更新redis的引用计数,因为redis快,而mysql慢,我们每增加五个,我们就在mysql中更新一次,减少频率。
static void handleBrowsePicture(const char *urlmd5, string &str_json) {
int ret = 0;
char sql_cmd[SQL_MAX_LEN] = {0};
string file_name;
string user;
string file_url;
string create_time;
int pv = 0;
int file_size = 0;
//查询 share_picture_list2, file_info的url
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");
AUTO_REL_DBCONN(db_manager, db_conn);
CacheManager *cache_manager = CacheManager::getInstance();
CacheConn *cache_conn = cache_manager->GetCacheConn("token");
AUTO_REL_CACHECONN(cache_manager, cache_conn);
CResultSet *result_set = NULL;
LogInfo("urlmd5: {}", urlmd5);
//先从redis获取
std::map<string, string> values;
bool ret_b = cache_conn->HgetAll(urlmd5, values);
if(ret_b && values.size() != 0) { //说明redis是有缓存的
//c从redis获取到缓存
user = values["user"];
file_url = values["file_url"];
file_name = values["file_name"];
pv = stoi(values["pv"]);
file_size = stoi(values["size"]);
create_time = values["create_time"];
} else {
// 从分享图片列表查询到文件信息
sprintf(sql_cmd,
"select user, file_url, file_name, pv, size, create_time from "
"share_picture_list2 where urlmd5 = '%s'", urlmd5);
LogDebug("执行: {}", sql_cmd);
result_set = db_conn->ExecuteQuery(sql_cmd);
if (result_set && result_set->Next()) {
user = result_set->GetString("user");
file_url = result_set->GetString("file_url");
file_name = result_set->GetString("file_name");
pv = result_set->GetInt("pv");
file_size = result_set->GetInt("size");
create_time = result_set->GetString("create_time");
delete result_set;
} else {
if (result_set)
delete result_set;
ret = -1;
goto END;
}
//更新缓存到redis
//成功则加载到redis
cache_conn->Hset(urlmd5, "user", user);
cache_conn->Hset(urlmd5, "file_url", file_url);
cache_conn->Hset(urlmd5, "file_name", file_name);
cache_conn->Hset(urlmd5, "pv", FormatString("%d", pv));
cache_conn->Hset(urlmd5, "size", FormatString("%d", file_size));
cache_conn->Hset(urlmd5, "create_time", create_time);
}
LogInfo("url: {}", file_url);
//更新浏览计数
pv += 1;
if(cache_conn->Hset(urlmd5, "pv", FormatString("%d", pv)) < 0) {
ret = -1;
goto END;
}
if(pv %5 == 0) {
// 3. 更新浏览次数, 可以考虑保存到redis,减少数据库查询的压力 修改、创建是比较花时间
//浏览计数增加 redis实时更新,mysql 每隔5个计数再更新到MySQL
sprintf(sql_cmd, "update share_picture_list2 set pv = %d where urlmd5 = '%s'", pv, urlmd5);
LogDebug("执行: {}", sql_cmd);
if (!db_conn->ExecuteUpdate(sql_cmd)) {
LogError("{} 操作失败", sql_cmd);
ret = -1;
goto END;
}
}
ret = 0;
END:
// 4. 返回urlmd5 和提取码key
if (ret == 0) {
encodeBrowselPictureJson(HTTP_RESP_OK, pv, file_url, user,
create_time, str_json);
} else {
encodeBrowselPictureJson(HTTP_RESP_FAIL, pv, file_url, user,
create_time, str_json);
}
}
而对于自己分享的图片和取消操作这俩方法基本没什么改变。
三:文件的共享(不是分享)
我们对本文件来说,我们是对文件进行共享操作,首先就是通过redis在共享列表中查找是否已经有这个文件了,万一别人存放了或者自己存放了。如果存放了那么直接返回,如果没存放,我们就查找一遍MySQL,如果MySQL中有,那我们先将数据存放到redis中,然后直接返回。如果没有,那就说明,这个文件还没有人进行共享。我们先将用户表中的这个文件标记为共享,然后在mysql中进行插入到共享文件中去,然后就是同步到redis中去。
//分享文件
int handleShareFile(string &user, string &md5, string &filename) {
/*
a)先判断此文件是否已经分享,判断集合有没有这个文件,如果有,说明别人已经分享此文件,中断操作(redis操作)
b)如果集合没有此元素,可能因为redis中没有记录,再从mysql中查询,如果mysql也没有,说明真没有(mysql操作)
c)如果mysql有记录,而redis没有记录,说明redis没有保存此文件,redis保存此文件信息后,再中断操作(redis操作)
d)如果此文件没有被分享,mysql保存一份持久化操作(mysql操作)
e)redis集合中增加一个元素(redis操作)
f)redis对应的hash也需要变化 (redis操作)
*/
ShareState share_state = ShareFail;
int ret = 0;
char sql_cmd[SQL_MAX_LEN] = {0};
char fileid[1024] = {0};
int ret2 = 0;
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");
AUTO_REL_DBCONN(db_manager, db_conn);
CacheManager *cache_manager = CacheManager::getInstance();
CacheConn *cache_conn = cache_manager->GetCacheConn("token");
AUTO_REL_CACHECONN(cache_manager, cache_conn);
//文件标示,md5+文件名
sprintf(fileid, "%s%s", md5.c_str(), filename.c_str());
if (cache_conn) {
ret2 = cache_conn->ZsetExit(FILE_PUBLIC_ZSET, fileid);
} else {
ret2 = 0;
}
LogInfo("fileid: {}, ZsetExit: {}", fileid, ret2);
//===1、先判断此文件是否已经分享,判断集合有没有这个文件,如果有,说明别人已经分享此文件,中断操作(redis操作)
if (ret2 == 1) //存在
{
LogWarn("别人已经分享此文件");
share_state = ShareHad;
goto END;
} else if (ret2 == 0) //不存在
{
//===2、如果集合没有此元素,可能因为redis中没有记录,再从mysql中查询,如果mysql也没有,说明真没有(mysql操作)
//===3、如果mysql有记录,而redis没有记录,说明redis没有保存此文件,redis保存此文件信息后,再中断操作(redis操作)
//查看此文件别人是否已经分享了
sprintf(sql_cmd,
"select * from share_file_list where md5 = '%s' and file_name "
"= '%s'",
md5.c_str(), filename.c_str());
//返回值:1有记录
ret2 = CheckwhetherHaveRecord(
db_conn,
sql_cmd); //执行sql语句, 最后一个参数为NULL
//,如果有则说明没有及时保持到redis,这里需要保存到redis
if (ret2 == 1) //说明有结果,别人已经分享此文件
{
// redis保存此文件信息
cache_conn->ZsetAdd(FILE_PUBLIC_ZSET, 0, fileid);
cache_conn->Hset(FILE_NAME_HASH, fileid, filename);
LogWarn("别人已经分享此文件");
share_state = ShareHad;
goto END;
}
} else //出错
{
ret = -1;
goto END;
}
//===4、如果此文件没有被分享,mysql保存一份持久化操作(mysql操作)
// sql语句, 更新共享标志字段
sprintf(sql_cmd,
"update user_file_list set shared_status = 1 where user = '%s' and "
"md5 = '%s' and file_name = '%s'",
user.c_str(), md5.c_str(), filename.c_str());
if (!db_conn->ExecuteUpdate(sql_cmd, false)) {
LogError("{} 操作失败", sql_cmd);
ret = -1;
goto END;
}
time_t now;
;
char create_time[TIME_STRING_LEN];
//获取当前时间
now = time(NULL);
strftime(create_time, TIME_STRING_LEN - 1, "%Y-%m-%d %H:%M:%S",
localtime(&now));
//分享文件的信息,额外保存在share_file_list保存列表
/*
-- user 文件所属用户
-- md5 文件md5
-- create_time 文件共享时间
-- file_name 文件名字
-- pv 文件下载量,默认值为1,下载一次加1
*/
sprintf(sql_cmd,
"insert into share_file_list (user, md5, create_time, file_name, "
"pv) values ('%s', '%s', '%s', '%s', %d)",
user.c_str(), md5.c_str(), create_time, filename.c_str(), 0);
if (!db_conn->ExecuteCreate(sql_cmd)) {
LogError("{} 操作失败", sql_cmd);
ret = -1;
goto END;
}
// 共享文件数量+1
ret = CacheIncrCount(cache_conn, FILE_PUBLIC_COUNT);
if (ret < 0) {
LogError("CacheIncrCount failed");
ret = -1;
goto END;
}
//===5、redis集合中增加一个元素(redis操作)
cache_conn->ZsetAdd(FILE_PUBLIC_ZSET, 0,
fileid); // 如果失败是需要撤销mysql数据库的操作的
//===6、redis对应的hash也需要变化 (redis操作)
// fileid ------> filename
LogInfo("Hset FILE_NAME_HASH {}-{}", fileid, filename);
ret = cache_conn->Hset(FILE_NAME_HASH, fileid, filename);
if (ret < 0) {
LogWarn("Hset FILE_NAME_HASH failed");
}
share_state = ShareOk;
END:
return (int)share_state;
}
四:共享文件的排行榜
这里就到了我们一开始提到的分享和共享的问题了,我们这个共享文件和查找用户自己的文件是比较相似的。都是先解析命令。在这里对于获取总文件数量以及获取总文件信息,都没啥可讲的,都是正常思路,查找即可返回。
//这个函数是处理排行榜的。这个函数和上面的myfiles文件很像,那个文件是处理本用户的文件排序,而这个文件是处理在共享文件中的文件排序。
int ApiSharefiles(string &url, string &post_data, string &str_json) {
// 解析url有没有命令
// count 获取用户文件个数
// display 获取用户文件信息,展示到前端
char cmd[20];
string user_name;
string token;
int start = 0; //文件起点
int count = 0; //文件个数
LogInfo("post_data: {}", post_data);
//解析命令 解析url获取自定义参数
QueryParseKeyValue(url.c_str(), "cmd", cmd, NULL);
LogInfo("cmd = {}", cmd);
if (strcmp(cmd, "count") == 0){ // count 获取用户文件个数
// 解析json
if (handleGetSharefilesCount(count) < 0){ //获取共享文件个数
encodeSharefilesJson(1, 0, str_json);
} else {
encodeSharefilesJson(0, count, str_json);
}
return 0;
} else {
//获取共享文件信息 127.0.0.1:80/sharefiles&cmd=normal
//按下载量升序 127.0.0.1:80/sharefiles?cmd=pvasc
//按下载量降序127.0.0.1:80/sharefiles?cmd=pvdesc
if (decodeShareFileslistJson(post_data, start, count) < 0){ //通过json包获取信息
encodeSharefilesJson(1, 0, str_json);
return 0;
}
if (strcmp(cmd, "normal") == 0) {
handleGetShareFilelist(start, count, str_json); // 获取共享文件
} else if (strcmp(cmd, "pvdesc") == 0) {
handleGetRankingFilelist(start, count, str_json); ////获取共享文件排行版
} else {
encodeSharefilesJson(1, 0, str_json);
}
}
return 0;
}
下面这个函数才是比较重要的,就是获取共享文件中的排行榜。我们在redis中,对排行榜的操作还是很简单的,只需要按着浏览量进行排序即可了。但是我们在这之前先考虑一个问题,我们在共享文件列表中,如果传入两个file_name一样的文件,那么我们会怎么样?所以这个就是我们需要考虑的。
我们可以使用一种方法,因为md5是唯一的,如果名称一样,md5一样,那不就是一个文件吗,所以没必要上传,因此我们在redis中会将这个文件的MD5+file_name这俩放到一起作为名称存储,这样就不会出现上面的情况。我们会将文件的信息存储到一个FILE_NAME_HASH结构中和一个FILE_PUBLIC_ZSET结构中,一个存name,一个存浏览量。
首先我们要判断的就是查看redis中的共享文件和mysql中的共享文件数量是否一致,如果不一致,那我们需要将redis中的数据全部删除,从MySQL中重新同步,但是!数据太多怎么办?会十分的耗时阻塞。我们可以后台开一个线程让他去同步。当我们同步完成之后,我们还从redis中获取数据,通过redis的排序命令,将浏览量按着顺序返回,然后封装json返回即可。
//获取共享文件排行版
//按下载量降序127.0.0.1:80/api/sharefiles?cmd=pvdesc
void handleGetRankingFilelist(int start, int count, string &str_json) {
/*
a) mysql共享文件数量和redis共享文件数量对比,判断是否相等
b) 如果不相等,清空redis数据,从mysql中导入数据到redis (mysql和redis交互)
c) 从redis读取数据,给前端反馈相应信息
*/
int ret = 0;
char sql_cmd[SQL_MAX_LEN] = {0};
int total = 0;
char filename[1024] = {0};
int sql_num;
int redis_num;
int score;
int end;
RVALUES value = NULL;
Json::Value root;
Json::Value files;
int file_count = 0;
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");
AUTO_REL_DBCONN(db_manager, db_conn);
CResultSet *pCResultSet = NULL;
CacheManager *cache_manager = CacheManager::getInstance();
CacheConn *cache_conn = cache_manager->GetCacheConn("token");
AUTO_REL_CACHECONN(cache_manager, cache_conn);
// 获取共享文件的总数量
ret = getShareFilesCount(db_conn, cache_conn, total);
if (ret != 0) {
LogError("{} 操作失败", sql_cmd);
ret = -1;
goto END;
}
//===1、mysql共享文件数量
sql_num = total;
//===2、redis共享文件数量
redis_num = cache_conn->ZsetZcard(
FILE_PUBLIC_ZSET); // Zcard 命令用于计算集合中元素的数量。
if (redis_num == -1) {
LogError("ZsetZcard 操作失败");
ret = -1;
goto END;
}
LogInfo("sql_num: {}, redis_num: {}", sql_num, redis_num);
//===3、mysql共享文件数量和redis共享文件数量对比,判断是否相等
if (redis_num != sql_num) // 如果数量太多会导致阻塞, redis mysql数据不一致怎么处理?
{ //===4、如果不相等,清空redis数据,重新从mysql中导入数据到redis
//(mysql和redis交互)
// a) 清空redis有序数据
cache_conn->Del(FILE_PUBLIC_ZSET); // 删除集合
cache_conn->Del(FILE_NAME_HASH); // 删除hash, 理解 这里hash和集合的关系
// b) 从mysql中导入数据到redis
// sql语句
strcpy( sql_cmd, "select md5, file_name, pv from share_file_list order by pv desc");
LogInfo("执行: {}", sql_cmd);
pCResultSet = db_conn->ExecuteQuery(sql_cmd);
if (!pCResultSet) {
LogError("{} 操作失败", sql_cmd);
ret = -1;
goto END;
}
// mysql_fetch_row从使用mysql_store_result得到的结果结构中提取一行,并把它放到一个行结构中。
// 当数据用完或发生错误时返回NULL.
while (pCResultSet->Next()) // 这里如果文件数量特别多,导致耗时严重,
// 可以这么去改进当
// mysql的记录和redis不一致的时候,开启一个后台线程去做同步
{
char field[1024] = {0};
string md5 = pCResultSet->GetString("md5"); // 文件的MD5
string file_name = pCResultSet->GetString("file_name"); // 文件名
int pv = pCResultSet->GetInt("pv");
sprintf(field, "%s%s", md5.c_str(),
file_name.c_str()); //文件标示,md5+文件名
//增加有序集合成员
cache_conn->ZsetAdd(FILE_PUBLIC_ZSET, pv, field);
//增加hash记录
cache_conn->Hset(FILE_NAME_HASH, field, file_name);
}
}
//===5、从redis读取数据,给前端反馈相应信息
// char value[count][1024];
value = (RVALUES)calloc(count, VALUES_ID_SIZE); //堆区请求空间
if (value == NULL) {
ret = -1;
goto END;
}
file_count = 0;
end = start + count - 1; //加载资源的结束位置
//降序获取有序集合的元素 file_count获取实际返回的个数
ret = cache_conn->ZsetZrevrange(FILE_PUBLIC_ZSET, start, end, value,
file_count);
if (ret != 0) {
LogError("ZsetZrevrange 操作失败");
ret = -1;
goto END;
}
//遍历元素个数
for (int i = 0; i < file_count; ++i) {
// files[i]:
Json::Value file;
/*
{
"filename": "test.mp4",
"pv": 0
}
*/
ret = cache_conn->Hget(FILE_NAME_HASH, value[i],
filename); // 通过 文件md5+文件名 -> 获取文件名
if (ret != 0) {
LogError("hget 操作失败");
ret = -1;
goto END;
}
file["filename"] = filename; // 返回文件名
//-- pv 文件下载量
score = cache_conn->ZsetGetScore(FILE_PUBLIC_ZSET, value[i]);
if (score == -1) {
LogError("ZsetGetScore 操作失败");
ret = -1;
goto END;
}
file["pv"] = score; // 返回下载量
files[i] = file;
}
if (file_count > 0)
root["files"] = files;
END:
if (ret == 0) {
root["code"] = 0;
root["total"] = total;
root["count"] = file_count;
} else {
root["code"] = 1;
}
str_json = root.toStyledString();
}
本期讲解,主要是将分享和共享给区别开来,以及对文件分享,浏览的优化,以及对共享文件排行榜问题的解决。0voice · GitHub
更多推荐
所有评论(0)