一、功能概述
本次更新完善了三个核心功能:
| 功能 | 说明 |
|---|---|
| MD5秒传 | 文件去重,相同内容只存一份,用户数+1即可秒传 |
| 个人文件列表 | 分页查询用户所有文件,支持数量统计 |
| 图片分享 | 生成唯一分享链接,支持提取码和浏览量统计 |
二、数据库表设计
2.1 文件信息表 file_info
存储所有文件的核心信息,用于MD5秒传判断:
sql
CREATE TABLE file_info (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
md5 VARCHAR(256) NOT NULL COMMENT '文件MD5,32位唯一标识',
file_id VARCHAR(256) NOT NULL COMMENT 'FastDFS文件ID',
url VARCHAR(512) NOT NULL COMMENT '完整访问URL',
size BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
type VARCHAR(32) DEFAULT '' COMMENT '文件类型',
count INT DEFAULT 0 COMMENT '引用计数,记录有多少用户拥有此文件',
PRIMARY KEY (id),
UNIQUE KEY uq_md5 (md5)
);
2.2 用户文件列表 user_file_list
存储每个用户拥有哪些文件:
sql
CREATE TABLE user_file_list (
id INT PRIMARY KEY AUTO_INCREMENT,
user VARCHAR(32) NOT NULL COMMENT '所属用户',
md5 VARCHAR(256) NOT NULL COMMENT '文件MD5',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
file_name VARCHAR(128) DEFAULT NULL COMMENT '文件名',
shared_status INT DEFAULT NULL COMMENT '共享状态:0未共享,1已共享',
pv INT DEFAULT NULL COMMENT '下载量',
UNIQUE KEY idx_user_md5_file_name (user, md5, file_name)
);
2.3 分享图片列表 share_picture_list
存储图片分享记录:
sql
CREATE TABLE share_picture_list (
id INT PRIMARY KEY AUTO_INCREMENT,
user VARCHAR(32) NOT NULL COMMENT '分享用户',
filemd5 VARCHAR(256) NOT NULL COMMENT '文件MD5',
file_name VARCHAR(128) DEFAULT NULL COMMENT '文件名',
urlmd5 VARCHAR(256) NOT NULL COMMENT '分享链接唯一标识',
key VARCHAR(8) NOT NULL COMMENT '提取码',
pv INT DEFAULT 1 COMMENT '浏览量',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
KEY idx_user_filemd5 (user, filemd5),
KEY idx_urlmd5_user (urlmd5, user)
);
三、MD5秒传功能实现
3.1 秒传原理
客户端计算文件MD5
|
v
查询file_info表是否存在此md5
|
+-- 不存在 --> 上传完整文件
|
+-- 存在 --> 秒传成功,user_file_list加一条记录,file_info.count+1
3.2 秒传处理流程
用户上传文件
|
v
计算文件内容MD5(32位字符串)
|
v
查询file_info表,根据md5查找记录
|
+-- 有记录 --> 检查用户是否已拥有此文件
| |
| +-- 已拥有 --> 返回code=5(文件已存在)
| |
| +-- 未拥有 --> count+1,写入user_file_list,Redis计数+1
| 返回code=0(秒传成功)
|
+-- 无记录 --> 返回code=1(秒传失败,需完整上传)
3.3 核心代码 --- handleDealMd5
cpp
void handleDealMd5(const char *user, const char *md5, const char *filename,
string &str_json) {
Md5State md5_state = Md5Failed;
int ret = 0;
int file_ref_count = 0;
char sql_cmd[SQL_MAX_LEN] = {0};
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_master");
AUTO_REL_DBCONN(db_manager, db_conn);
// 查询file_info获取引用计数
sprintf(sql_cmd, "select count from file_info where md5 = '%s'", md5);
LOG_INFO << "执行: " << sql_cmd;
ret = GetResultOneCount(db_conn, sql_cmd, file_ref_count);
if (ret == 0) // 找到记录,秒传可能成功
{
// 检查用户是否已有此文件
sprintf(sql_cmd,
"select * from user_file_list where user = '%s' and md5 = '%s' "
"and file_name = '%s'",
user, md5, filename);
ret = CheckwhetherHaveRecord(db_conn, sql_cmd);
if (ret == 1) // 用户已有此文件
{
md5_state = Md5FileExit; // code=5
goto END;
}
// 更新file_info引用计数+1
sprintf(sql_cmd, "update file_info set count = %d where md5 = '%s'",
file_ref_count + 1, md5);
db_conn->ExecutePassQuery(sql_cmd);
// 写入user_file_list
sprintf(sql_cmd,
"insert into user_file_list(user, md5, create_time, file_name, "
"shared_status, pv) values ('%s', '%s', '%s', '%s', %d, %d)",
user, md5, time_str, filename, 0, 0);
db_conn->ExecuteCreate(sql_cmd);
// Redis用户文件计数+1
CacheIncrCount(cache_conn, FILE_USER_COUNT + string(user));
md5_state = Md5Ok; // code=0
}
else // 没找到记录,秒传失败
{
md5_state = Md5Failed; // code=1
}
END:
encodeMd5Json((int)md5_state, str_json);
}
3.4 秒传响应码
| code | 含义 |
|---|---|
| 0 | 秒传成功 |
| 1 | 秒传失败(文件不存在,需完整上传) |
| 5 | 文件已存在(用户已拥有此文件) |
四、个人文件列表功能实现
4.1 接口设计
接口1:获取文件数量
- 路径:/api/myfiles?cmd=count
- 方法:POST
- 请求体:
json
{
"user": "用户名",
"token": "***"
}
- 响应:
json
{
"code": 0,
"total": 10
}
接口2:获取文件列表(分页)
- 路径:/api/myfiles?cmd=normal
- 方法:POST
- 请求体:
json
{
"user": "用户名",
"token": "***",
"start": 0,
"count": 10
}
- 响应:
json
{
"code": 0,
"count": 10,
"total": 25,
"files": [
{
"user": "用户名",
"md5": "文件MD5",
"create_time": "2026-06-06 20:00:00",
"file_name": "demo.jpg",
"share_status": 0,
"pv": 0,
"url": "http://192.168.181.138/group1/M00/00/00/xxx.jpg",
"size": 102400,
"type": "jpg"
}
]
}
4.2 处理流程
收到请求 /api/myfiles?cmd=xxx
|
v
解析cmd参数
|
+-- cmd=count --> 获取文件数量
| |
| v
| 反序列化JSON(user, token)
| |
| v
| 验证Token
| |
| v
| 查询user_file_list统计数量
| |
| v
| 返回{"code":0, "total":N}
|
+-- cmd=normal --> 获取文件列表
|
v
反序列化JSON(user, token, start, count)
|
v
验证Token
|
v
关联查询file_info + user_file_list
|
v
分页返回文件详情列表
4.3 核心代码 --- 关联查询
cpp
int getUserFileList(string cmd, string &user_name, int &start, int &count, string &str_json)
{
int ret = 0;
int total = 0;
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");
AUTO_REL_DBCONN(db_manager, db_conn);
// 先获取总数
ret = getUserFilesCount(db_conn, user_name, total);
if (ret < 0) {
return -1;
}
if (total == 0) {
// 无文件时返回空列表
root["code"] = 0;
root["count"] = 0;
root["total"] = 0;
return 0;
}
// 关联查询:file_info + user_file_list
string str_sql = FormatString(
"select user_file_list.*, file_info.url, file_info.size, file_info.type "
"from file_info, user_file_list "
"where user = '%s' and file_info.md5 = user_file_list.md5 "
"limit %d, %d",
user_name.c_str(), start, count);
CResultSet *result_set = db_conn->ExecuteQuery(str_sql.c_str());
if (result_set) {
Json::Value root;
Json::Value files;
root["code"] = 0;
root["total"] = total;
int file_index = 0;
while (result_set->Next()) {
Json::Value file;
file["user"] = result_set->GetString("user");
file["md5"] = result_set->GetString("md5");
file["create_time"] = result_set->GetString("create_time");
file["file_name"] = result_set->GetString("file_name");
file["share_status"] = result_set->GetInt("shared_status");
file["pv"] = result_set->GetInt("pv");
file["url"] = result_set->GetString("url");
file["size"] = result_set->GetInt("size");
file["type"] = result_set->GetString("type");
files[file_index++] = file;
}
root["files"] = files;
root["count"] = file_index;
str_json = root.toStyledString();
delete result_set;
}
return 0;
}
五、图片分享功能实现
5.1 分享图片接口
接口1:分享图片
- 路径:/api/sharepic?cmd=share
- 方法:POST
- 请求体:
json
{
"user": "用户名",
"token": "***",
"md5": "文件MD5",
"filename": "图片名.jpg"
}
- 响应:
json
{
"code": 0,
"urlmd5": "32位唯一分享标识"
}
接口2:浏览分享图片
- 路径:/api/sharepic?cmd=browse
- 方法:POST
- 请求体:
json
{
"urlmd5": "32位分享标识"
}
- 响应:
json
{
"code": 0,
"pv": 10,
"url": "http://192.168.181.138/group1/M00/00/00/xxx.jpg",
"user": "分享者",
"time": "2026-06-06 20:00:00"
}
接口3:获取分享列表
- 路径:/api/sharepic?cmd=normal
- 方法:POST
- 请求体:
json
{
"user": "用户名",
"token": "***",
"start": 0,
"count": 10
}
5.2 分享流程
用户分享图片
|
v
生成32位唯一urlmd5(随机字符串)
|
v
写入share_picture_list表
|
+-- user: 分享用户
+-- filemd5: 文件MD5
+-- file_name: 文件名
+-- urlmd5: 唯一分享标识(用于访问)
+-- key: 提取码
+-- pv: 浏览量初始为0
|
v
返回urlmd5给客户端
5.3 浏览流程
访客访问分享链接
|
v
用urlmd5查询share_picture_list
|
v
获取filemd5
|
v
用filemd5查询file_info获取图片URL
|
v
pv+1(浏览量+1)
|
v
返回图片URL和分享信息
5.4 核心代码 --- 分享图片
cpp
int handleSharePicture(const char *user, const char *filemd5,
const char *file_name, string &str_json)
{
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");
AUTO_REL_DBCONN(db_manager, db_conn);
// 生成32位唯一urlmd5
string urlmd5 = RandomString(32);
char create_time[TIME_STRING_LEN];
time_t now = time(NULL);
strftime(create_time, TIME_STRING_LEN - 1, "%Y-%m-%d %H:%M:%S", localtime(&now));
// 写入分享记录
string str_sql = FormatString(
"insert into share_picture_list (user, filemd5, file_name, urlmd5, key, pv, create_time) "
"values ('%s', '%s', '%s', '%s', '%s', %d, '%s')",
user, filemd5, file_name, urlmd5.c_str(), key.c_str(), 0, create_time);
if (!db_conn->ExecuteCreate(str_sql.c_str())) {
encodeSharePictureJson(HTTP_RESP_FAIL, urlmd5, str_json);
} else {
encodeSharePictureJson(HTTP_RESP_OK, urlmd5, str_json);
}
return 0;
}
5.5 核心代码 --- 浏览图片
cpp
int handleBrowsePicture(const char *urlmd5, string &str_json)
{
int ret = 0;
string picture_url;
string user;
string filemd5;
string create_time;
int pv = 0;
CDBManager *db_manager = CDBManager::getInstance();
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_master");
AUTO_REL_DBCONN(db_manager, db_conn);
// 1. 通过urlmd5查询分享记录
string sql_cmd = FormatString(
"select user, filemd5, file_name, pv, create_time "
"from share_picture_list where urlmd5 = '%s'", urlmd5);
CResultSet *result_set = db_conn->ExecuteQuery(sql_cmd.c_str());
if (result_set && result_set->Next()) {
user = result_set->GetString("user");
filemd5 = result_set->GetString("filemd5");
pv = result_set->GetInt("pv");
create_time = result_set->GetString("create_time");
delete result_set;
} else {
ret = -1;
goto END;
}
// 2. 通过filemd5查询文件URL
sql_cmd = FormatString("select url from file_info where md5 = '%s'", filemd5.c_str());
result_set = db_conn->ExecuteQuery(sql_cmd.c_str());
if (result_set && result_set->Next()) {
picture_url = result_set->GetString("url");
delete result_set;
} else {
ret = -1;
goto END;
}
// 3. 浏览量+1
pv += 1;
sql_cmd = FormatString("update share_picture_list set pv = %d where urlmd5 = '%s'", pv, urlmd5);
db_conn->ExecuteUpdate(sql_cmd.c_str());
END:
if (ret == 0) {
encodeBrowselPictureJson(HTTP_RESP_OK, pv, picture_url, user, create_time, str_json);
} else {
encodeBrowselPictureJson(HTTP_RESP_FAIL, pv, picture_url, user, create_time, str_json);
}
}
六、HTTP路由分发
6.1 新增路由入口
cpp
// http_conn.cc 新增两个处理函数
else if(strncmp(url.c_str(), "/api/myfiles", 12) == 0) {
_HandleMyfilesRequest(url, content);
}
else if(strncmp(url.c_str(), "/api/sharepic", 13) == 0) {
_HandleSharepictureRequest(url, content);
}
// 处理函数实现
int CHttpConn::_HandleMyfilesRequest(string &url, string &post_data) {
string str_json;
int ret = ApiMyfiles(url, post_data, str_json);
char *szContent = new char[HTTP_RESPONSE_JSON_MAX];
uint32_t ulen = str_json.length();
snprintf(szContent, HTTP_RESPONSE_JSON_MAX, HTTP_RESPONSE_JSON, ulen, str_json.c_str());
tcp_conn_->send(szContent);
delete [] szContent;
return 0;
}
int CHttpConn::_HandleSharepictureRequest(string &url, string &post_data) {
string str_json;
int ret = ApiSharepicture(url, post_data, str_json);
char *szContent = new char[HTTP_RESPONSE_JSON_MAX];
uint32_t ulen = str_json.length();
snprintf(szContent, HTTP_RESPONSE_JSON_MAX, HTTP_RESPONSE_JSON, ulen, str_json.c_str());
tcp_conn_->send(szContent);
delete [] szContent;
return 0;
}
6.2 API入口分发逻辑
请求路径 --> 调用的API函数
--------------------------------------------------------------
/api/reg --> ApiRegister
/api/login --> ApiLogin
/api/upload --> ApiUpload
/api/md5 --> ApiMd5
/api/myfiles?cmd=count --> ApiMyfiles (count分支)
/api/myfiles?cmd=normal --> ApiMyfiles (normal分支)
/api/sharepic?cmd=share --> ApiSharepicture (share分支)
/api/sharepic?cmd=browse --> ApiSharepicture (browse分支)
/api/sharepic?cmd=normal --> ApiSharepicture (normal分支)
七、文件新增清单
本次更新新增了2个API文件:
| 文件 | 功能 |
|---|---|
| api/api_myfiles.cc | 个人文件列表API(cmd=count数量统计,cmd=normal分页列表) |
| api/api_myfiles.h | api_myfiles.h |
| api/api_sharepicture.cc | 图片分享API(share分享,browse浏览,normal列表) |
| api/api_sharepicture.h | api_sharepicture.h |
同时修改了以下文件:
| 文件 | 修改内容 |
|---|---|
| api/api_common.cc | 新增DBGetSharePictureCountByUsername函数 |
| api/api_common.h | 新增DBGetSharePictureCountByUsername声明 |
| api/api_md5.cc | 切换到master库查询,启用MD5秒传逻辑 |
| http_conn.cc | 新增_HandleMyfilesRequest和_HandleSharepictureRequest |
八、总结
本次更新实现了三个核心功能:
-
MD5秒传:通过文件内容MD5去重,相同文件只存储一份,多用户引用时count+1,大幅节省存储空间
-
个人文件列表:支持分页查询用户所有文件,关联file_info获取文件详情,code=0表示成功
-
图片分享:生成唯一分享链接urlmd5,支持浏览量统计,可查看分享者的所有分享图片
根据零声教育教学写作https://github .com/0voice