图床项目实现: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

相关推荐
2401_868534785 小时前
容灾、备份、存储
linux·网络
艾iYYY5 小时前
Linux基础操作指令
linux·运维·服务器
v_for_van5 小时前
C语言__attribute__
服务器·c语言·开发语言·mcu·嵌入式·嵌入式实时数据库
Multipath7126 小时前
5G CPE vs 5G聚合路由器 该怎么选
网络·5g·安全·智能路由器·实时音视频·信息与通信
GIS数据转换器6 小时前
智慧林草“一张图“平台
java·大数据·服务器·前端·javascript·数据库·人工智能
智恒百亿6 小时前
5090八卡服务器整机技术解析与部署常见问题(FAQ)
运维·服务器
萌动的小火苗6 小时前
Linux进程线程面试题【无答案】
linux·运维·服务器·c语言·开发语言
cuijiecheng20186 小时前
Linux下ss命令用法
linux·运维·服务器
云上工程笔记6 小时前
4090 GPU 服务器适合哪些 AI 场景?2026 年显存、推理性能、租用价格和风险对比
运维·服务器·人工智能
Doraemomo7 小时前
Linux编程-epoll多路IO复用
linux·运维·服务器