图床项目实现:MD5秒传 + 个人文件列表 + 图片分享等功能的完善

一、功能概述

本次更新完善了三个核心功能:

功能 说明
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

八、总结

本次更新实现了三个核心功能:

  1. MD5秒传:通过文件内容MD5去重,相同文件只存储一份,多用户引用时count+1,大幅节省存储空间

  2. 个人文件列表:支持分页查询用户所有文件,关联file_info获取文件详情,code=0表示成功

  3. 图片分享:生成唯一分享链接urlmd5,支持浏览量统计,可查看分享者的所有分享图片

根据零声教育教学写作https://github .com/0voice

相关推荐
Irissgwe1 小时前
8-1\IP 分片和组装的具体过程
linux·网络·tcp/ip·网络层·分片·组装
闪电悠米1 小时前
黑马点评-秒杀优化-04_lua_and_db_fallback
服务器·开发语言·网络·数据库·缓存·junit·lua
Shadow(⊙o⊙)2 小时前
进程间通信0.0-pipe()匿名管道,详细分析进程池调度队列执行逻辑,进程池模拟实现。
linux·运维·服务器·开发语言·c++
CQU_JIAKE2 小时前
6.6aaaaaa
linux·运维·服务器
smallswan2 小时前
第十四 算数运算
linux·服务器·前端
Yang96112 小时前
风场光伏光缆分缆测损,DM-40A 光通信综合测试仪高效运维
网络·能源
努力搬砖的咸鱼2 小时前
容器编排底层原理:Kubernetes 网络模型与 CNI 插件
网络·微服务·云原生·容器·架构·kubernetes
ylscode2 小时前
Chrome桌面安全更新修复数百个漏洞
网络·windows·安全·安全威胁分析
.小小陈.2 小时前
从零构建可用 TCP 服务:从基础 Socket 到自定义协议与序列化
服务器·网络·tcp/ip