项目:图片存储系统(图片服务器)

article/2025/10/2 23:11:06

图片存储系统

项目描述:
实现一个 HTTP 服务器,用该服务器来存储图片,针对每个图片提供一个唯一的url, 使用 url 对图片进行访问, 提供对图片的增删改查能力,同时搭配简单的页面辅助完成图片上传/展示

  • 利用 HTTP 服务器来为每个图片提供一个唯一访问的 url
  • 使用 Json 封装 http 请求,响应
  • 提供上传图片,查看图片信息/内容以及删除图片接口
  • 使用 lambda 表达式替换函数

实现环境:Linux MySQL-5.5.60 cpp-httplib 库
涉及技术:HTTP 协议 Json C++11 lambda 表达式

项目流程:
在这里插入图片描述
项目代码维护于github上:https://github.com/luchunabf/item

使用 JSON 作为数据交互格式

json 出自 JavaScript, 是一种非常方便的键值对数据组织格式,主要用途之一就是序列化.
C++ 中可以使用 jsoncpp 这个库来解析和构造 json 数据

yum install jsoncpp-devel 

1. 使用 MySQL C API 操作数据库

安装 MySQL C API

yum install mysql-devel

代码中使用时需要链接上 MySQL 提供的库

-L /usr/lib64/mysql -lmysqlclient

数据库设计

创建数据库:

create database if not exists image_system;
use image_system;

创建图片表:

drop table if exists image_table
create table image_table(image_id int not null primary key auto_increment,image_name varchar(50),size bigint,upload_time varchar(50),md5 varchar(128),content_type varchar(50) comment '图片类型', path varchar(1024) comment '图片所在路径')

db.hpp

#pragma once #include <cstdlib>
#include <cstring>
#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", "123", "image_system", 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);}//操作数据库中ImageTable 这个表,
//此处 Insert 等操作, 函数依赖的输入信息较多,
//为了防止参数太多, 可以使用 JSON 来封装参数
class ImageTable{
public:ImageTable(MYSQL* mysql): mysql_(mysql){}//image 就形如以下形式://{//   image_name: "test.png",//   size: 1024,//   upload_time: "2019/08/29",//   md5: "abcdef",//   type: "png",//   path: "test_8_29_image/test.png"//}//使用 JSON 的原因:1.扩展更方便 2.方便和服务器接受的数据打通/* bool Insert(const Json::Value& image){char sql[4096] = {0};sprintf(sql,"insert into image_table values(null,'%s', %d, '%s','%s','%s','%s')",image["image_name"].asCString(),image["size"].asInt(), image["uplode_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 执行失败!%s\n", mysql_error(mysql_));return false;}return true;}*/bool Insert(const Json::Value& image) {char sql[4096] = {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());int ret = mysql_query(mysql_, sql);if (ret != 0) {printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));return false;}return true;      }//如果是输入型参数,使用 const&//如果是输出型参数,使用 *(指针)//如果是输入输出型参数, 使用&bool SelectAll(Json::Value* images)//image是输出型参数{char sql[4096] = {0};sprintf(sql, "select * from image_table");int ret = mysql_query(mysql_, sql);if(ret != 0){printf("SelectAll 执行失败!%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[4096] = {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 的结果不是一条记录!实际查到 %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[4096] = {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 (namespace)

2. 图片服务器

服务器 API 设计

新增图片

请求:
POST /image HTTP/1.1
Content-Type: application/x-www-form-urlencoded
 
…[内容]…
 
响应:
HTTP/1.1 200 OK
{
  “ok”: true,
}

查看所有图片信息

请求:
GET /image/
HTTP/1.1 200 OK
[
{
 “image_id”: 1,
  “image_name”: “1.png”,
  “type”: “image/png”,
  “md5”: “[md5值]”
  “upload_time”: “2019/8/29”,
  path:"./data/test.png"
}, 
{
  “image_id”: 2,
  “image_name”: “2.png”,
  ……
}
]

查看指定图片信息

请求:
GET /image/:image_id
响应:
HTTP/1.1 200 OK
{
  “image_id”: 1,
  “image_name”: “1.png”,
  “type”: “image/png”,
  “md5”: “[md5值]”
  “upload_time”: “2019/8/29”,
  path:"./data/test.png"
}

查看图片内容

请求:
GET /image/show/:image_id
响应:
HTTP/1.1 200 OK
content-type: image/png
 
[响应 body 中为 图片内容 数据]

删除图片

请求:
DELETE /image/:image_id
响应:
HTTP/1.1 200 OK
{
  “ok”: true
}

构建 HTTP 服务器提供约定的 API 接口

服务器基本框架

使用 cpp-httplib

#include "httplib.h"
int main() {using namespace httplib;Server server;server.Get("/", [](const Request& req, Response& resp) {(void)req;resp.set_content("<html>hello</html>", "text/html");});server.set_base_dir("./wwwroot");server.listen("0.0.0.0", 9094);return 0;
} 

提供约定的 API 接口

这里只说明框架,代码维护于github:https://github.com/luchunabf/item

#include <signal.h>
#include "db.hpp"
#include <stdio.h>
#include <fstream>
#include "httplib.h" 
#include <jsoncpp/json/json.h>
#include <sys/stat.h>class FileUtil
{
public://写文件static bool Write(const std::string& file_name,const std::string& content)//将content写入到文件file_name中去{std::ofstream file(file_name.c_str()); //打开文件if(!file.is_open()){return false;}file.write(content.c_str(), content.length());//将content写入文件file.close();//关闭文件return true;}//读文件
static bool Read(const std:: string& file_name, std::string* content)//将file_name文件读入到content中去
{std::ifstream file(file_name.c_str());if(!file.is_open()){return false;}struct stat st;stat(file_name.c_str(), &st);计算file_name文件大小content->resize(st.st_size);//将content扩容到要读取文件file_name的大小//一口气把整个文件读完//需要先知道文件的大小//char* 缓冲区长度//int 读取多长file.read((char*)content->c_str(), content->size());file.close();return true;
}
};//回调函数
/*void Hello(const httplib::Request& req, httplib::Response& resp)//HTTP Content-Type
{resp.set_content("<h1>hello</h1>", "text/html");
}*/MYSQL* mysql = NULL;int main()
{using namespace httplib;mysql = image_system::MySQLInit();//这里连接数据库并设置编码格式image_system::ImageTable image_table(mysql);//创建一个(已经封装好)数据库类的对象,用对象来操作数据库signal(SIGINT,[](int){//处理一下信号处理函数,按ctrl+c的时候退出进程并且释放数据库image_system::MySQLRelease(mysql);exit(0);});Server server;//客户端请求 /hello 路径的时候,执行一个特定的函数//制定不同的路径对应到不同的函数上,这个过程//称为“设置路由”//服务器中有两个重要的概念//1.请求(Request)//2.响应(Response)//[&image_table] 这是lambda 的重要特性, 捕获变量//server.Post("/image",[&image_table](const Request& req, Response& resp){//1.对参数进行校验//2.根据文件名称获取到文件数据file对象//3.把文件属性信息插入到数据库中//4.把图片保存到指定的磁盘目录中//5.构造一个响应数据通知客户端上传成功});server.Get("/image", [&image_table](const Request& req, Response& resp){//void(req);//没有任何实际的效果//1.调用数据库接口来获取数据//2.构造响应结果返回给客户端});server.Get(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp){printf("获取单个图片信息\n");//1. 先获取图片id//2.根据图片信息查询数据库//3. 把查询结果返回给客户端});server.Get(R"(/show/(\d+))", [&image_table](const Request& req, Response& resp){printf("获取指定图片内容\n"); //这一步是查看图片,其他的是查信息,用Json封装响应,这里用string接收文件路径,从文件中读取内容//1. 先获取图片id//2.根据目录找到文件内容,读取文件内容//3.把文件内容构造成一个响应});server.Delete(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp){//1.根据图片id去数据库中查找对应的目录//2. 查找到对应文件的路径//3.调用数据库进行删除//4.删除磁盘上的文件//5.构造响应});server.set_base_dir("./wwwroot");// 设置静态文件目录server.listen("0.0.0.0", 9094);return 0;
}

3. 使用 Postman 进行测试

在 Postman 中构造请求, 并验证

使用upload.html上传图片:

在这里插入图片描述
响应:
在这里插入图片描述
查看所有图片信息
在这里插入图片描述
在这里插入图片描述
查看指定图片信息
在这里插入图片描述
在这里插入图片描述
查看指定图片内容
在这里插入图片描述
在这里插入图片描述
删除指定图片
在这里插入图片描述
在这里插入图片描述

遇到的问题

1. 在测试合同http服务器接口时,在浏览器上访问该服务器时一直未响应

最后调试发现linux防火墙未关闭,关闭后可以在浏览器中正常访问服务器

 //Linux查看防火墙状态及关闭或者重启的命令(CentOS7或者red hat)
//查看防火墙的状态(是否有开启)
systemctl status firewalld
service iptables status//暂时关闭防火墙
systemctl stop firewalld
service iptables stop//暂时关闭后,开启防火墙
systemctl start firewalld
service iptables start//永久关闭防火墙(开机禁用)
systemctl disable firewalld
chkconfig iptables off//重启防火墙
service iptables restart//永久关闭后,开启防火墙(开机自动启用)
systemctl enable firewalld
chkconfig iptables on//systemctl是CentOS7的服务管理工具中主要的工具,它融合之前service和chkconfig的功能于一体。

2. 使用Json封装响应后,输入格式较复杂

查阅资料,可以叫Json格式化进行输出

Json::FastWriter writer;resp.set_content(writer.write(resp_json), "application/json");

3. 在代码固定的情况下,不能匹配查询指定id的图片

使用正则表达式可以解决该问题

  // 1. 正则表达式// 2. 原始字符串(raw string)server.Get(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp){});

4. 使用了正则表达式后,发现代码运行总是报错,如下:

在这里插入图片描述
后来发现时编译器版本问题,g++4.8不支持正则表达式
在这里插入图片描述
升级g++至7.3即可:
使用 devtool 升级 g++ 到 7.3 版本

//以下命令在 root 下使用
yum install centos-release-scl -y
yum install devtoolset-7 -y
//命令
source /opt/rh/devtoolset-7/enable 

在这里插入图片描述
再次运行即可正确启动服务器

扩展

  1. md5 和 上传时间还需完善,目前暂时写死
  2. 存储时合并文件,提高存储效率
  3. 防盗链,权限控制,只让图片被指定用户使用, 借助cookie实现
  4. 对于你内容相同的文件,可以只存一份图片文件,引用计数,使用md5可以判断图片内容是否相同

http://chatgpt.dhexx.cn/article/3CgBXBdP.shtml

相关文章

搭建Nginx图片服务器

一、安装Nginx 先安装Nginx&#xff0c;看我之前发的文章&#xff1a; 搭建Nginx服务器 二、安装vsftpd 再安装vsftpd组件&#xff0c;看我之前发的文章&#xff1a; Linux安装ftp组件(8步完成) 三、开始搭建Nginx图片服务器 1、效果 例如&#xff1a;图片通过ftp服务上传…

[项目]图片服务器

目录 1. 项目背景2.项目模块划分2.1数据存储模块2.2服务器API 3. 总结4. 扩展 1. 项目背景 现在很多网页都可以见到图片上传功能&#xff0c;我们上传一张本地图片后&#xff0c;网页就会显示我们所上传的图片&#xff0c;比如博客、个人信息提交页面等等。那么这背后的原理是什…

nginx图片服务器

编辑nginx.conf linux下配置文件服务器 配置完以后需要执行nginx -s reload重新加载配置 springboot项目application.properties配置访问和保存图片路径 #nginx文件服务器ip ip127.0.0.1#保存图片绝对路径 save.pic/home/pic/images/#展示图片url get.pichttp://${ip}/image…

如何快速搭建图片服务器

前言 分布式集群的项目, 正常一般的工程是把图片放在web项目的自身服务器的工程中&#xff0c;但在集群环境下&#xff0c;会出现找不到图片的情况。 代码参考: https://github.com/zyjcxc/taotao.git 比如&#xff1a; 解决办法&#xff1a; linux做磁盘的映射&#xf…

图片服务器实现

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

图片服务器解决方案

最近经常有人问图片上传怎么做&#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…