图片服务器实现

article/2025/10/2 23:35:55

项目背景

现在很多地方仅仅支持文字发送,而不支持图片发送,,但是在很多特定的场景有需要图片发送等方式,所以我们可以构建一个HTTP服务器来完成这个功能,通过这个服务器为连接服务器的数据库上的每个图片生成一个特定的URL通过这个URL我们就可以完成图片的发送。

项目描述

首先这个项目是一个图片服务器,我们可以上传图片在上面,通过特定的URL来获取这些图片的内容,就可以解决很多需要图片而不支持发送图片的场景,我们就需要对于图片内容和图片信息进行保存,我们会分别采用数据库和磁盘对该信息进行存储

项目设计

我们需要对图片进行存储,图片存储分为了两个部分,首先是就是我们上传的图片信息包括(大小,名字等)这些可以用数据库来进行存储,图片内容部分以二进制方式进行存储,所以图片内容以二进制方式存储在,但是由于图片信息存储如果用类进行存储改动比较麻烦,所以我们引入了JSON格式进行存储,JSON使用键值对方式进行存储,方便修改

模块划分

首先进行数据库创建,然后对于数据库的表格进行创建,我们创建一张表,只需要一张表对于图片信息进行存储,然后在代码模块我们对于数据库的操作进行操作,所以我们需要对于数据库的操作进行封装构建SQL语句,然后再进行服务器的封装需要。
数据库构建
在这里插入图片描述
数据库功能封装

#pragma once#include <cstdio>
#include <cstdlib>
#include <mysql/mysql.h>
#include <jsoncpp/json/json.h>namespace image_system {static MYSQL* MySQLInit() {// 使用 mysql API 来操作数据库了// 1. 先创建一个 mysql 的句柄MYSQL* mysql = mysql_init(NULL);// 2. 拿着句柄和数据库建立链接if (mysql_real_connect(mysql, "127.0.0.1", "root", "", "image_system2", 3306, NULL, 0) == NULL) {// 数据库链接失败printf("连接失败! %s\n", mysql_error(mysql));return NULL;}// 3. 设置编码格式mysql_set_character_set(mysql, "utf8");return mysql;
}static void MySQLRelease(MYSQL* mysql) {mysql_close(mysql);
}// 操作数据库中的 image_table 这个表. 
// 此处 Insert 等操作, 函数依赖的输入信息比较多. 
// 为了防止参数太多, 可以使用 JSON 来封装参数
class ImageTable {
public:ImageTable(MYSQL* mysql) : mysql_(mysql) {}bool Insert(const Json::Value& image) {char sql[4 * 1024] = {0};sprintf(sql, "insert into image_table values(null, '%s', %d, '%s', '%s', '%s', '%s')", image["image_name"].asCString(),image["size"].asInt(), image["upload_time"].asCString(),image["md5"].asCString(), image["type"].asCString(),image["path"].asCString());printf("[Insert sql] %s\n", sql);int ret = mysql_query(mysql_, sql);if (ret != 0) {printf("Insert 执行 SQL 失败! %s\n", mysql_error(mysql_));return false;}return true;}// 如果是输入参数, 使用 const&// 如果是输出参数, 使用 *// 如果是输入输出参数, 使用 &bool SelectAll(Json::Value* images) {char sql[1024 * 4] = {0};sprintf(sql, "select * from image_table");int ret = mysql_query(mysql_, sql);if (ret != 0) {printf("SelectAll 执行 SQL 失败! %s\n", mysql_error(mysql_));return false;}// 遍历结果集合, 并把结果集写到 images 参数之中MYSQL_RES* result = mysql_store_result(mysql_);int rows = mysql_num_rows(result);for (int i = 0; i < rows; ++i) {MYSQL_ROW row = mysql_fetch_row(result);// 数据库查出的每条记录都相当于是一个图片的信息// 需要把这个信息转成 JSON 格式Json::Value image;image["image_id"] = atoi(row[0]);image["image_name"] = row[1];image["size"] = atoi(row[2]);image["upload_time"] = row[3];image["md5"] = row[4];image["type"] = row[5];image["path"] = row[6];images->append(image);}// 忘了就会导致内存泄露mysql_free_result(result);return true;}bool SelectOne(int image_id, Json::Value* image_ptr) {char sql[1024 * 4] = {0};sprintf(sql, "select * from image_table where image_id = %d",image_id);int ret = mysql_query(mysql_, sql);if (ret != 0) {printf("SelectOne 执行 SQL 失败! %s\n", mysql_error(mysql_));return false;}// 遍历结果集合MYSQL_RES* result = mysql_store_result(mysql_);int rows = mysql_num_rows(result);if (rows != 1) {printf("SelectOne 查询结果不是 1 条记录! 实际查到 %d 条!\n", rows);return false;}MYSQL_ROW row = mysql_fetch_row(result);Json::Value image;image["image_id"] = atoi(row[0]);image["image_name"] = row[1];image["size"] = atoi(row[2]);image["upload_time"] = row[3];image["md5"] = row[4];image["type"] = row[5];image["path"] = row[6];*image_ptr = image;// 释放结果集合mysql_free_result(result);return true;}bool Delete(int image_id) {char sql[1024 * 4] = {0};sprintf(sql, "delete from image_table where image_id = %d",image_id);int ret = mysql_query(mysql_, sql);if (ret != 0) {printf("Delete 执行 SQL 失败! %s\n", mysql_error(mysql_));return false;}return true;}private:MYSQL* mysql_;
};
}  // end image_system

服务器封装

#include <fstream>
#include <signal.h>
#include <sys/stat.h>
#include "httplib.h"
#include "db.hpp"class FileUtil {
public:static bool Write(const std::string& file_name,const std::string& content) {std::ofstream file(file_name.c_str());if (!file.is_open()) {return false;}file.write(content.c_str(), content.length());file.close();return true;}static bool Read(const std::string& file_name, std::string* content) {std::ifstream file(file_name.c_str());if (!file.is_open()) {return false;}struct stat st;stat(file_name.c_str(), &st);content->resize(st.st_size);// 一口气把整个文件都读完// 需要先知道该文件的大小// char* 缓冲区长度// int 读取多长file.read((char*)content->c_str(), content->size());file.close();return true;}
};MYSQL* mysql = NULL;int main() {using namespace httplib;mysql = image_system::MySQLInit();image_system::ImageTable image_table(mysql);signal(SIGINT, [](int) {image_system::MySQLRelease(mysql);exit(0);});Server server;// 客户端请求 /hello 路径的时候, 执行一个特定的函数// 指定不同的路径对应到不同的函数上, 这个过程// 称为 "设置路由"// 服务器中有两个重要的概念:// 1. 请求(Request)// 2. 响应(Response)// [&image_table] 这是 lambda 的重要特性, 捕获变量// 本来 lambda 内部是不能直接访问 image_table 的.// 捕捉之后就可以访问了. 其中 & 的含义是相当于按引用捕获server.Post("/image", [&image_table](const Request& req, Response& resp) {Json::FastWriter writer;Json::Value resp_json;printf("上传图片\n");// 1. 对参数进行校验auto ret = req.has_file("upload");if (!ret) {printf("文件上传出错!\n");resp.status = 404;// 可以使用 json 格式组织一个返回结果resp_json["ok"] = false;resp_json["reason"] = "上传文件出错, 没有需要的upload 字段";resp.set_content(writer.write(resp_json), "application/json");return;}// 2. 根据文件名获取到文件数据 file 对象const auto& file = req.get_file_value("upload");// file.filename;// file.content_type;// 3. 把图片属性信息插入到数据库中Json::Value image;image["image_name"] = file.filename;image["size"] = (int)file.length;image["upload_time"] = "2018/08/29";  // TODOimage["md5"] = "aaaaaaa"; // TODOimage["type"] = file.content_type;image["path"] = "./data/" + file.filename;ret = image_table.Insert(image);if (!ret) {printf("image_table Insert failed!\n");resp_json["ok"] = false;resp_json["reason"] = "数据库插入失败!";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}// 4. 把图片保存到指定的磁盘目录中auto body = req.body.substr(file.offset, file.length);FileUtil::Write(image["path"].asString(), body);// 5. 构造一个响应数据通知客户端上传成功resp_json["ok"] = true;resp.status = 200;resp.set_content(writer.write(resp_json), "application/json");return;});server.Get("/image", [&image_table](const Request& req,Response& resp) {(void) req; // 没有任何实际的效果printf("获取所有图片信息\n");Json::Value resp_json;Json::FastWriter writer;// 1. 调用数据库接口来获取数据bool ret = image_table.SelectAll(&resp_json);if (!ret) {printf("查询数据库失败!\n");resp_json["ok"] = false;resp_json["reason"] = "查询数据库失败!";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}// 2. 构造响应结果返回给客户端resp.status = 200;resp.set_content(writer.write(resp_json), "application/json");});// 1. 正则表达式// 2. 原始字符串(raw string)server.Get(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) {Json::FastWriter writer;Json::Value resp_json;// 1. 先获取到图片 idint image_id = std::stoi(req.matches[1]);printf("获取 id 为 %d 的图片信息!\n", image_id);// 2. 再根据图片 id 查询数据库bool ret = image_table.SelectOne(image_id, &resp_json);if (!ret) {printf("数据库查询出错!\n");resp_json["ok"] = false;resp_json["reason"] = "数据库查询出错";resp.status = 404;resp.set_content(writer.write(resp_json), "application/json");return;}// 3. 把查询结果返回给客户端resp_json["ok"] = true;resp.set_content(writer.write(resp_json), "application/json");return;});server.Get(R"(/show/(\d+))", [&image_table](const Request& req,Response& resp) {Json::FastWriter writer;Json::Value resp_json;// 1. 根据图片 id 去数据库中查到对应的目录int image_id = std::stoi(req.matches[1]);printf("获取 id 为 %d 的图片内容!\n", image_id);Json::Value image;bool ret = image_table.SelectOne(image_id, &image);if (!ret) {printf("读取数据库失败!\n");resp_json["ok"] = false;resp_json["reason"] = "数据库查询出错";resp.status = 404;resp.set_content(writer.write(resp_json), "application/json");return;}// 2. 根据目录找到文件内容, 读取文件内容std::string image_body;printf("%s\n", image["path"].asCString());ret = FileUtil::Read(image["path"].asString(), &image_body);if (!ret) {printf("读取图片文件失败!\n");resp_json["ok"] = false;resp_json["reason"] = "读取图片文件失败";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}// 3. 把文件内容构造成一个响应resp.status = 200;// 不同的图片, 设置的 content type 是不一样的. // 如果是 png 应该设为 image/png// 如果是 jpg 应该设为 image/jpgresp.set_content(image_body, image["type"].asCString());});server.Delete(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) {Json::FastWriter writer;Json::Value resp_json;// 1. 根据图片 id 去数据库中查到对应的目录int image_id = std::stoi(req.matches[1]);printf("删除 id 为 %d 的图片!\n", image_id);// 2. 查找到对应文件的路径Json::Value image;bool ret = image_table.SelectOne(image_id, &image);if (!ret) {printf("删除图片文件失败!\n");resp_json["ok"] = false;resp_json["reason"] = "删除图片文件失败";resp.status = 404;resp.set_content(writer.write(resp_json), "application/json");return;}// 3. 调用数据库操作进行删除ret = image_table.Delete(image_id);if (!ret) {printf("删除图片文件失败!\n");resp_json["ok"] = false;resp_json["reason"] = "删除图片文件失败";resp.status = 404;resp.set_content(writer.write(resp_json), "application/json");return;}// 4. 删除磁盘上的文件// C++ 标准库中, 没有删除文件的方法// C++ 17 标准里是有的. // 此处只能使用操作系统提供的函数了unlink(image["path"].asCString());// 5. 构造响应resp_json["ok"] = true;resp.status = 200;resp.set_content(writer.write(resp_json), "application/json");});server.set_base_dir("./wwwroot");server.listen("0.0.0.0", 9094);return 0;
}

设计服务器api

1.上传图片
请求
POST(新增)/image HTTP/1.1/
content-type;(application/x-www-form-urlencoded)
【内容】

响应
/HTTP/1.1 200 OK
{
OK : true
}
2.查看所有图片信息
请求
GET/image

响应
/HTTP/1.1 200 OK

{
image_id:1,
image_name:“test。png”,
type:“image/png”,
。。。。。。
},
{
。。。。。。。
}

3.查看指定图片信息
GET/image/:image_id
响应
/HTTP/1.1 200 OK
{
image_id:1,
image_name:“test。png”,
type:“image/png”,
。。。。。。
}

4.查看指定图片内容
GET/show/:image_id
响应
/HTTP/1.1 200 OK
content-type:image/png
[body 图片内容]

5.删除图片
DELETE/:image_id
响应
/HTTP/1.1 200 OK
{
OK : true
}

HTML文件只需要构造一个界面这里就不做展示了。
makefile
FLAG=-L /usr/lib64/mysql -lmysqlclient -ljsoncpp -g

all:image_server db_test

image_server:image_server.cc
g++ $^ -o $@ -std=c++11 -lpthread $(FLAG)

db_test:db_test.cc db.hpp
g++ db_test.cc -o $@ $(FLAG)

.PHONY:clean
clean:
rm db_test image_server
这样就可以实现图片服务器,保存的图片都有一个URL,我们就可以在很多地方使用这个URL对图片进行引用而不用担心输入。

项目总结

通过这个项目让我更加熟悉Linux操作系统和HTTP协议,让我对于网络有更加深刻的认识,同时熟悉了JSON和我外部库cpp-http让我学到了很多。


http://chatgpt.dhexx.cn/article/Hxt0jb3x.shtml

相关文章

图片服务器解决方案

最近经常有人问图片上传怎么做&#xff0c;有哪些方案做比较好&#xff0c;也看到过有关于上传图片的做法&#xff0c;但是都不是最好的 今天再这里简单讲一下上传图片以及图片服务器的大致理念 如果是个人项目或者企业小项目&#xff0c;仅仅只有十来号人使用的小项目&#…

搭建一个图片服务器

最近在学习一个电商项目&#xff0c;其中用到了图片上传服务&#xff0c;自己在学习过程中遇到了点问题&#xff0c;记录下来&#xff0c;以备以后查询 首先需要安装nginx和vsftpd&#xff0c;这两者的安装都有相应的手册&#xff0c;步骤非常详细&#xff0c;我就不啰嗦了&…

简单的本地图片服务器的搭建

简单的本地图片服务器的搭建 第一步&#xff1a;安装部署 Nginx下载 Nginx下载完解压后 第二步&#xff1a; 搭建图片服务器 第一步&#xff1a;安装部署 Nginx 下载 Nginx 保存文件路径不要包含中文&#xff01; Linux和Windows不一样&#xff01; 下载完成后&#xff0c;解…

图片服务器

图片服务器 图片服务器主要的功能是&#xff1a;上传图片&#xff0c;显示图片的功能 写博客的时候&#xff0c;插入的图片&#xff0c;本质上是往文章内插入一个url&#xff0c;图片其实是保存在在另一个服务器上&#xff0c;而我这个项目就是制作一个类似这样的服务器。 核心…

FastDFS搭建图片服务器

服务器规划 服务器名称IP地址和端口备注fastdfs-tracker*:22122跟踪服务器/调度服务器fastdfs-storage*:23000存储服务器 一、安装系统组件 yum install gcc -y 二、安装fastdfs 1、创建图片服务器存储目录 mkdir -p /data/image 2、下载FastDFS依赖包libfastcommon并安…

Zimg—轻量级图片服务器搭建利器

在一个互联网应用中&#xff0c;图片扮演着越来越重要的角色。有稳定的可扩展的图片存储服务器就显得尤为的重要&#xff0c;云厂商们提供了便利的图片存储服务&#xff0c;花钱就可以解决了。这里简单介绍一个开源的一个分布式图片存储服务器——zimg&#xff0c;来自己搭建一…

图片服务器的搭建

当高并发的时候容易发生图盘上传到一个服务器从另一个服务器需要读取照片 解决方法&#xff1a; 专门保存图片&#xff0c;不管是哪个服务器接收到图片&#xff0c;都把图片上传到图片服务器。 图片服务器上需要安装一个http服务器&#xff0c;可以使用tomcat、apache、ngin…

图片服务器搭建

图片服务器搭建 原先我们通过servlet上传一个用户的头像&#xff0c;需要把头像显示到网站上。就需要搭建一个图片服务器来显 示图片了。 图片服务器其实和tomcat/nginx容器的作用是一样的。目的都是要把文件从本地的磁盘上发布出去。一般常用 的图片服务器是apache服务器 &am…

三种图片服务器

2019独角兽企业重金招聘Python工程师标准>>> 到目前&#xff0c;工作中用到的图片服务器有下面三种&#xff1a; &#xff08;1&#xff09;使用Nginx搭建图片服务器 &#xff08;2&#xff09;使用阿里云图片服务器&#xff08;OOS&#xff09; &#xff08;3&…

【java】本地客户端内嵌浏览器2 - chrome/chromium/cef/jcef

目录 ★☆★ 写在前面 ★☆★★☆★ 本系列文章 ★☆★★☆★ 开源网址 ★☆★一、发现新大陆 - CEF/JCEF0、前言1、使用 jcef.jar 搭建项目2、启动包含 jcef.jar 的程序3、simple\MainFrame 注释翻译 二、定制自己的项目之 Swing1、删除导航栏2、程序启动最大化窗口&#xff0…

C#嵌入谷歌浏览器内核

1.右击项目&#xff0c;选择.net框架为4.5以上&#xff1a; 2.右击项目&#xff0c;选择“管理Nuget程序包”&#xff0c;点击“浏览”&#xff0c;搜索“CefSharp”&#xff0c;选择“CefSharp WinForms”下载安装。 安装之后到项目的引用下查看&#xff0c;会出现&#xff…

WinFrom内嵌chrome浏览器

选中项目&#xff0c;右键&#xff0c;下拉列表里选择“管理Nuget程序包&#xff08;N&#xff09;”选项&#xff0c;打开如图&#xff1a; 按照步骤装上这个nuget包&#xff0c;装上以后你的工具箱就有这个了&#xff1a; 不用拖拉控件&#xff0c;直接代码绑定把&#xff…

在网页中内嵌网页

目录&#xff1a; 文章目录 前言代码展示主页代码展示作品的代码球体运动方块旋转 结果演示 前言 在制作个人网站时&#xff0c;经常遇到一个问题&#xff0c;就是如何让自己的作品动态的显示在主页上而本文就是找到了解决办法&#xff0c;利用<embed src"xx.html&qu…

Qt实现 内嵌CEF制作浏览器(首篇)

介绍 cef支持跨平台&#xff0c;是基于Chromium的开源浏览器控件&#xff0c;全称Chromium Embedded Framework&#xff0c;因为其跨平台性&#xff0c;被广泛使用于制作浏览器。 本文主要介绍如何下载cef以及编译windows下的cef项目&#xff0c;并运行查看浏览器显示效果。 …

Qt 内嵌浏览器几种办法

1.使用axWidget QT axcontainer 然后ui里面就可以出现QAxWidget 直接拖入就可以 ui->axWidget->setControl(QString::fromUtf8("{8856F961-340A-11D0-A96B-00C04FD705A2}")); ui->axWidget->setFocusPolicy(Qt::StrongFocus); ui->axWidget…

CEF内嵌浏览器 编译

CEF github 笔记 https://github.com/fanfeilong/cefutil/blob/master/doc/CEF%20General%20Usage-zh-cn.md#using-binray 介绍 CEF全称Chromium Embedded Framework&#xff0c;是一个基于Google Chromium 的开源项目。Google Chromium项目主要是为Google Chrome应用开发的…

Unity内嵌浏览器插件(Android、iOS、Windows)

文章目录 一. Embedded Browser插件下载2. 使用 二. UniWebView插件1. 下载2. 使用 三、我自制的迷你浏览器 一. Embedded Browser插件 下载 平台&#xff1a;Windows 链接&#xff1a;https://pan.baidu.com/s/1h2oyGW6FsvPlOGCob0VrfA 提取码&#xff1a;02tu 2. 使用 导…

QT内嵌浏览器与JS通讯

QT内嵌浏览器与JS通讯 1. 概述2. JS调用QT方法2.1 QT代码2.2 HTML/代码 3 WebEngineView示例4. 效果展示 1. 概述 QT内嵌浏览器支持拦截请求、获取cookie、js代码注入及js调用QT方法。本篇主要介绍js调用QT方法其他方式的使用QT WebEngineView 拦截请求、获取cookie&#xff0…

Unity 工具之 内嵌网页/浏览器 web view / browser 插件的整理大全(包括Window Mac Android iOS 等)

Unity 工具之 内嵌网页/浏览器 web view / browser 插件的整理大全&#xff08;包括Window Mac Android iOS 等&#xff09; 目录 Unity 工具之 内嵌网页/浏览器 web view / browser 插件的整理大全&#xff08;包括Window Mac Android iOS 等&#xff09; 一、简单介绍 二、…

Unity 工具之 内嵌网页/浏览器插件使用和学习笔记

1、Embedded Browser 插件&#xff08;文件夹名ZFBrowserUnity&#xff09; 优点&#xff1a;设置简单&#xff0c;功能强大&#xff1a;输入url地址&#xff0c;拉取网页信息&#xff0c;可设置页面尺寸&#xff0c;可显示透明背景的网页&#xff0c;可与显示的页面进行互动&…