图片存储系统
项目描述:
实现一个 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
再次运行即可正确启动服务器
扩展
- md5 和 上传时间还需完善,目前暂时写死
- 存储时合并文件,提高存储效率
- 防盗链,权限控制,只让图片被指定用户使用, 借助cookie实现
- 对于你内容相同的文件,可以只存一份图片文件,引用计数,使用md5可以判断图片内容是否相同
今天的文章图片存储服务器 选型_服务器一般用什么系统分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/70494.html