Linux_图片搜索器

一个人拥有此生此世是不够的,他还应该拥有诗意的世界!

图片服务器

1. 项目背景

实现一个HTTP服务器, 然后利用这个服务器来存储图片, 针对每一张图片都有一张唯一的URL, 可以借助这个URL将图片展示到其他网页上.

存储图片 (写博客, 上传图片的时候最终显示到终端上), HTML构建网页所必须的东西,
https://csdnimg.cn/cdn/content-toolbar/csdn-logo_.png?v=20190924.1 (CSDN网络路径, 通过这个路径可以访问到网页上的图片)

需求诞生

  1. 针对有些软件不支持上传图片功能, 需要上传图片的地址, 例如:GitHub中的issue,项目中有bug需要改进什么的, 有时候需要截张图片, 如何向作者反馈, 就需要放一张链接, 对应图片的链接放到服务器上;

  2. 自己搭建的静态网页博客上传图片是需要上传图片的地址;

  3. 实际中所遇到的问题;

核心需求

  1. 上传图片;

  2. 根据图片的url访问图片,获取图片内容(即下载);

  3. 获取某个图片的属性 (大小,格式,上传时间,路径等等);

  4. 删除图片;

服务器针对的是网络通信, 谈到服务器就会涉及到客户端(主动发送请求)和服务端 (被动接受请求), 两者之间的角色有可能发替换, 同一个进程有可能使客户端也可能是服务端(例如: 手机上点外卖, 手机上的APP显示的是客户端, 饿了吗服务器是服务器, 点击付款的时候, 饿了吗服务器调用支付宝功能, 在支付场景下, 饿了吗服务器又相当于是客户端).
在这里插入图片描述

2. 项目模块

2.1 数据存储模块

数据存储模块主要是使用MySQL数据库对我们的文件进行存储和管理, 另一方面通过磁盘(外存)来保存我们上传的图片, 因为内存不能持久化存储.

2.1.1 数据库设计

我们在数据库中设计一张表用来存储图片的属性也就是表的结构.

创建数据库(图片内容即字符串)

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(256),size bigint,upload_time varchar(50), //图片的上传时间content_type varchar(50) comment '图片类型',path varchar(1024) comment '图片所在路径',md5 varchar(128) //校验和,用来校验图片内容的正确性
);

创建数据库,表都统一写到一个db.sql文件中
这样做的目的是例如数据库被重新重建, 自己将程序写到服务器上, 后边又因为服务器的磁盘空间不够用, 就需要将服务器迁移到其他服务器上, 数据库里面的内容也需要迁移, 建议可以将数据库中的建表语句都写到一个文件中, 让mysql客户端批量处理文件中的内容.

mysql -uroot < db.sql

重定向本质: 就是将文件描述符表中的数组下标映射到不同的文件结构上.

> 重定向, 本来是输出到标准输出上, 输出到文件中;
< 重定向, 把标准输入重定向到文件上;
2> 重定向, 把标准错误重定向到文件中.

md5
其中对于表单的创建我们使用到了md5对数据的完整性的保护,那么md5又是什么?
首先md5是一种字符串hash算法,md5有两种版本, 64位版本得到的是8个字节的数字, 128位版本得到的是16个字节的数字, 并且他有三个特性可以保证数据的安全性:

  1. 不管是什么样的字符串,最终得到的md5值都是一个固定的长度。

  2. 如果一个字符串,内容稍有变化,得到的md5值的差异还是很大的。

  3. 通过源字符串计算md5很容易,但是拿到md5还原源字符串理论上是不可能的。
    (其中md5这个字段是用来进行校验图片内容正确性的, 上传图片之后, 服务器就可以计算一个该图片的md5值, 后序用户下载图片的时候, 也能获取到该图片的md5, 用户可以把自己计算的md5和服务器计算的md5对比, 就知道自己的图片是否下载正确了.)

crc校验
循环校验, 直接把我们需要的数据的每个字节循环相加 不考虑溢出, 最终得到的结果就是.

2.1.2 插入语句(mysql_insert)
#include
#include
#include    //相对路径的目录int main()
{//使用mysql API来操作数据库了//1.创建mysql操作句柄(遥控器)结构体指针MYSQL * mysql = mysql_init(NULL);//2.拿着句柄和数据库建立连接//"127.0.0.1"环回IPif(mysql_real_connect(mysql,"127.0.0.1","root","940822","image_system",3306,NULL,0) == NULL){//数据库连接失败printf("连接失败!%s\n",mysql_error(mysql));return 1;}//3.设置编码格式(查看服务器编码方式与客户端编码方式一致)mysql_set_character_set(mysql,"utf8");//4.拼接sql语句char sql [4096]={0};// printf是将格式化字符串输出到标准输出上, sprintf字符串的格式化操作,输出到字符串里面sprintf(sql,"insert into image_table values(null,'test.png',1024,'2019/08/26','abcdef','png','data/test.png')");// 5.执行SQL语句,负责了客户端给服务器发送数据的过程int ret= mysql_query(mysql,sql);if(ret != 0){printf("执行 sql 失败! %s\n",mysql_error(mysql));return 1;                    //进程退出码}//6.关闭句柄mysql_close(mysql);return 0;
}
2.1.3 查询语句
#include
#include
#includeint main()
{//使用mysql API来操作数据库了//1.创建mysql操作句柄MYSQL * mysql =mysql_init(NULL);//2.拿着句柄和数据库建立连接if(mysql_real_connect(mysql,"127.0.0.1","root","940822","image_system",3306,NULL,0)==NULL){//数据库连接失败printf("连接失败!%s\n",mysql_error(mysql));return 1;}//3.设置编码格式mysql_set_character_set(mysql,"utf8");//4.拼接sql语句char sql [4096]={0};sprintf(sql,"select * from image_table");// 5.执行SQL语句,负责了客户端给服务器发送数据的过程int ret=mysql_query(mysql,sql);if(ret != 0){printf("执行 SQL 失败! %s\n",mysql_error(mysql));return 1;                    //进程退出码}//6.获取结果集合(遍历结果)将得到的结果看成一个二维数组, 先循环获取每一行, 再循环获取每一列;MYSQL_RES* result=mysql_store_result(mysql);int rows=mysql_num_rows(result);int cols=mysql_num_fields(result);for(int i=0;i<rows;++i){MYSQL_ROW row=mysql_fetch_row(result);for(int j=0;j<cols;++j)  {printf("%s\t",row[j]);}printf("\n");}//7. 释放结果集合(防止内存泄漏)mysql_free_result(result);//8.关闭句柄mysql_close(mysql);return 0;
}
2.1.4 实现一个数据库的客户端, 使用MySQL的API来实现
#pragma once
#include
#include
#include
#include
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","940822","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);}
class ImageTable{
public:ImageTable(MYSQL* mysql):mysql_(mysql){}//image就形如以下形式//{//  image_name: "test.png",//  size: 1024,//  upload_time: "2019/08/28",//  md5: "abcdef",//  type: "png",//  path: "data/test.png"//}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;}  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_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
**json**
json是一种数据组织格式, 最主要的用途之一就是序列化, json源于JavaScript用来表示一个"对象",使用键值对来表示:
{"hero_name": "锤石","skill 1": 死亡之钩,"skill 2": 灯笼,"skill 3 " 阻挡,"skill 4" 牢笼,
}
json优势: 方便调试;
json劣势: 组织格式的效率比较低, 更占用存储空间和带宽.
protobuf: 谷歌出品的一种二进制序列化协议.

2.2 服务器模块 (给前端提供一些接口, 和前端网页进行交互)

HTTP服务器需要接受http请求,返回http响应。此处需要约定不同的请求来表示不同的操作方式。例如有些请求表示上传图片,有些请求表示查看图片,有些表示删除图片,我们需要实现一个HTTP服务器此处使用Restful风格的设计,那么什么是Restful风格的设计?

  1. http method来表示操作的动词:GET查,POST增,PUT改,DELETE删;
  2. http path表示操作的对象;
  3. 补充信息一般使用body来传递,通常情况下使用json格式的数据来组织
  4. 响应数据通常也是用json格式组织;
    (我们在实现HTTP服务器的时候用到了GitHub上的cpp-httplib这个开源库)

我们在使用的时候将其放到库的链接搜索路径下,然后再代码中包含库的头文件就可以用这个库了;开始设计服务器的一些API了,服务器API设计有五部分组成。

  1. 上传图片
  2. 查看所有图片信息
  3. 查看指定图片信息
  4. 查看指定图片内容
  5. 删除图片

创建db.hpp (.hpp头文件即包含声明又包含实现)

#pragma once
#include 
#include 
#include 
#include 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", "940822", "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);
}// 操作数据库中的 image_table 这个表. 
// 此处 Insert 等操作, 函数依赖的输入信息比较多. 
// 为了防止参数太多, 可以使用 JSON 来封装参数
// 创建一个ImageTable这个类,和我们的数据库表进行交互
class ImageTable {
public:ImageTable(MYSQL* mysql) : mysql_(mysql) {}// image 就形如以下形式:// {//    image_name: "test.png",//    size: 1024,//    upload_time: "2019/08/28",//    md5: "abcdef",//    type: "png",//    path: "data/test.png"// }// 使用 Json 的原因: 1. 扩展更方便; 2. 方便和服务器接受到的数据打通// asCString 转换为C风格字符串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());// 可以更好地确定拼装的sql语句是否正确printf("[Insert sql] %s\n", sql);// 执行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,image_id通过方括号的形式插入一个键值对,row[0]得到的类型为char型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对象添加到参数之中, 若干个对象添加到参数之中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 命名空间结束

上传图片
为了让服务器在网页上提供一个最简单的图片上传功能,需要一个html文件;
upload.html

<html>
<head></head>
<bady><form id="upload-form" action="http://192.168.11.130:9094/image" method="post"enctype="multipart/form-data"><input type="file" id="upload" name="upload" /> <br /><input type="submit" value="Upload" />
</form>
</body>
</html>

然后我们再设计服务器接收图片的API
image_server.cc

#include   //C++版本的读写操作
#include 
#include 
#include "httplib.h"
#include "db.hpp"// C++读写文件
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;}// 包含#include  调用stat函数,获取文件长度更新为resize和文件长度一样长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);// 通过捕捉二维信号,在二维信号的回调函数中调用realeasesignal(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. 把图片保存到指定的磁盘目录中,需要创建data目录auto body = req.body.substr(file.offset, file.length);// asString() 得到std Stringfenge分隔的字符串FileUtil::Write(image["path"].asString(), body);// 5. 构造一个响应数据通知客户端上传成功resp_json["ok"] = true;resp.status = 200;resp.set_content(writer.write(resp_json), "application/json");return;});// Lambda表达式// 后续有客户端请求path路径为/image,即执行[&image_table](const Request& req,Response& resp)这样的函数// server.Get("/image", [&image_table](const Request& req,Response& resp))// [&]表示请求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),原始字符串中无转义字符// (\d+)只匹配一个或多个数字server.Get(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) {Json::FastWriter writer;Json::Value resp_json;// 1. 先获取到图片 id// matches返回的是字符串,stoi将返回的字符串int 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;
}
jsoncpp操作和解析json:
一个核心类, 两个重要方法.
json::value非常类似于std::map
Reader::parse把一个json字符串转换成json::value对象
Writer::write把一个json::value对象转换成字符串.

3. 项目测试

#include "db.hpp"// 单元测试
// C++ 的单元测试框架都有哪些? 
void TestImageTable() {// 创建一个 ImageTable 类, 去调用其中的方法, 验证结果// 使格式化出来后的json具有一定的序列化// 将一个json::value对象转换成字符串Json::StyledWriter writer;MYSQL* mysql = image_system::MySQLInit();image_system::ImageTable image_table(mysql);bool ret = false;// 1. 插入数据// Json::Value image;// image["image_name"] = "test.png";// image["size"] = 1024;// image["upload_time"] = "2019/08/28";// image["md5"] = "abcdef";// image["type"] = "png";// image["path"] = "data/test.png";// ret = image_table.Insert(image);// printf("ret = %d\n", ret);// 2. 查找所有图片信息//Json::Value images;//ret = image_table.SelectAll(&images);//printf("ret = %d\n", ret);//将一个std::string 转换为c风格的字符串//printf("%s\n", writer.write(images).c_str());// 3. 查找指定图片信息// Json::Value image;// ret = image_table.SelectOne(1, &image);// printf("ret = %d\n", ret);// printf("%s\n", writer.write(image).c_str());// 4. 删除指定图片ret = image_table.Delete(1);printf("ret = %d\n", ret);image_system::MySQLRelease(mysql);
}int main() {TestImageTable();return 0;
}

4. 项目的扩展

4.1 存储时合并文件

如果上传大量的比较小的文件时,在磁盘空间不太充裕时可能会产生磁盘碎片,把这些逻辑上比较小的文件合并成一个比较大的物理文件,在读取文件时,数据库中除了存该文件的路径之外,再存一个偏移量,在已知路径的相对偏移量开始读起,就可以正常读取文件;

磁盘碎片应该称为文件碎片,是因为文件被分散保存到整个磁盘的不同地方,而不是连续地保存在磁盘连续的簇中形成的。硬盘在使用一段时间后,由于反复写入和删除文件,磁盘中的空闲扇区会分散到整个磁盘中不连续的物理位置上,从而使文件不能存在连续的扇区里。这样,再读写文件时就需要到不同的地方去读取,增加了磁头的来回移动,降低了磁盘的访问速度。

4.2 防盗链

只要其他人拿到了我的url就可以使用我的图床, 所以可以增加权限控制,只让图片能被特定的用户使用;

防盗链的方法:使用登录验证,判断引用地址,使用cookie,使用POST下载,使用图形验证码,打包下载等其中使用cookie是通过实现用户账户功能,登录之后就得到了cookie,有了cookie就可以正常使用;

4.3 支持图片处理功能

比如在qq和手机相册中常见的缩略图功能,这样的好处是如果原图片比较大(像是2k和4k的图片),相同带宽下缩略图加载更快,可以在用户请求时选择添加一个参数,比如width=100&length=120,借助C++图片处理库:计算机视觉库OpenCV,开源图形库FreeImage等

4.4 相同图片只保留一份

节省服务器资源,用md5实现对文件内容是否相同的判断,在实现时还需要进行引用计数,比如一张图片上传了两次,但是删除一次,但是在另一路径下的相同的没被删除的图片文件也被删了.

5. 项目总结

本项目就是实现一个HTTP服务器,然后用这个服务器来存储图片,针对每个图片提供一个唯一的url,有了这个url之后就可以借助它把图片展示到其他网页上。项目主要分成两个模块:数据库存储模块和服务器模块。数据存储模块我们主要是通过MySQL的API来操作数据库,用JSON对数据库中的image_table这个表进行操作。服务器模块我们主要是通过cpp-httplib这个库来为服务器提供一些向外的接口。当项目代码完成之后我们又使用postman这个软件对我们所完成的操作进行了测试。
  通过这个项目,我觉得自己对于知识的掌握还是不太牢固,自己的知识面还是比较窄,需要学习的东西还比较多, 希望自己今后不断加油!


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部