项目背景
类似于在CSDN中上传博客,选择上传图片后,会进去选择图片文件的界面,然后上传成功后,我们就可以看到上传的图片被显示,其实质就是将图片上传到了CSDN的后台服务器中。
核心功能
- 新增图片
- 查看所有的图片信息
- 查看指定图片信息
- 查看图片内容
- 删除图片
该项目的核心就是实现一个HTTP服务器,然后用该服务器来存储图片,针对每个图片提供唯一的URL,有了该URL后,就可以接住它将图片展示在其他网页上。 - 上传图片(得到唯一的URL)
- 根据图片的URL访问图片,并获取图片的内容
- 获取某个图片的属性
- 删除图片
模块划分
1.数据库存储模块
包括:设计数据库、使用MySQL C API操作
2. 服务器模块
包括新增图片、查看所有图片信息、查看指定图片的内容,删除图片
数据库设计
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 '图片所在路径'
);
其中,image_id为主键自增属性
md_5如何文件校验字符串?
这是一种常见的hash算法,具有三个特性:
1.不管源字符串多长,得到的最终的md5值都是固定长度的
2.源字符串稍微变化一点内容,md5值会变化很大(降低冲突概率)
3.通过源字符串很容易得到md5值,但是根据md5值推导源字符串很难(几乎不可能)
md5的作用:用于校验图片的正确性
(上传图片的时候,服务器可以计算出该图片的md5值,后续用户下载该图片时,也能够获取到该图片的md5值,用户可以把自己计算的md5值和服务器计算的md5进行对比,以此来判断下载的图片是否正确)
使用MySQL C API操作数据库
安装MySQL C API
yum install mysql -devel
代码中使用时需要连接上MySQL提供的库
-L /usr/lib64/mysql -lmysqlclient
服务器的 API设计
我们需要实现一个HTTP服务器,HTTP服务器需要接受HTTP请求,HTTP响应,因此我们需要约定不同的请求来表示不同的操作方式。
如有些请求表示上传图片,有些请求表示查看图片,有些请求表示删除图片。
- http method 表示操作的动词:GET查,POST增,PUT改,DELETE删
- http path表示操作的对象
- 补充信息一般使用body来传递,通常情况下body中使用json的格式来传递
- 响应数据通常也是json格式组织
json是一种数据组织格式,最主要的用途为序列化,json源于javascript,用来表示一个对象,类似于map的key:value格式
json的优势,方便调试
json的劣势,组织格式的效率低,更加占用存储空间和带宽
具体设计:
1.上传图片
请求:
POST /image
Content-Type:application/x-www-form-urlencoded
----------WebKitFormBoundary5muoelvEmAAVUyQB
Content-Disposition:form-data;name="filename";filename="图标.jpg"
Content-Type:image/jpeg
......[图片正文]......
响应;
HTTP/1.1 200 ok
{
"OK":true;
}
2.查看所有图片信息
请求:
GET /image/
HTTP/1.1 200 OK
[
{
"image_id":1,
"image_name":"1.png",
"content_type":"image/png",
"md5":"[md5值]"
}
]
3.查看指定图片的信息
请求:
GET /image/:image_id
响应:
HTTP/1.1 200 ok
{
"image_id":1,
"image_name":"1.png",
"content_type":"image/png",
"md5",:"[md5值]"
}
4.查看指定图片的内容
请求:
GET /image/show/:image_id
响应:
HTTP/1.1 200 OK
content_type :image/png
[响应 body中为图片的内容数据]
5.删除图片
请求:
DELETE /image/:image_id
响应:
HTTP/1.1 200 OK
{
"OK":true
}
6.服务端设计
封装数据库操作
namespace image_system
{
static MYSQL* MySQLInit(){}
static void MySQLRelease(MYSQL* mysql){}
class ImageTable{
ImageTable(MYSQL* mysql){ }
bool Insert(const Json::Value& image);
bool SelectAll(Json::Value* images);
bool SelectOne(int32_t image_id,Json::Value* image);
bool Delete(int image_id);
};
}
服务器的基本框架
#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;
}
构建与http服务器约定的API接口
#include <signal.h>
#include <jsoncpp/json/json.h>
#include "util.hpp"
#include "db.hpp"
#include "httplib.h"
#include <openssl/md5.h>
std::string StringMD5(const std::string& str);
const std::string base_path = "./image_data/";
MYSQL* mysql = NULL;
int main() {
using namespace httplib;
using namespace image_system;
Server server;
// 1. 数据库客户端初始化和释放
mysql = MySQLInit();
signal(SIGINT, [](int) { MySQLRelease(mysql); exit(0);});
ImageTable image_table(mysql);
// 2. 先按照 cpp-httplib 的文档演示基本的图片上传处理过程
server.Post("/image_test", [](const Request& req, Response& resp) {
});
// 3. 新增图片.
server.Post("/image", [&image_table](const Request& req, Response& resp) {
});
// 4. 查看所有图片的元信息
server.Get("/image", [&image_table](const Request& req, Response& resp) {
});
// 5. 查看图片元信息
// raw string(c++ 11), 转义字符不生效. 用来表示正则表达式正好合适
// 关于正则表达式, 只介绍最基础概念即可. \d+ 表示匹配一个数字
// http://help.locoy.com/Document/Learn_Regex_For_30_Minutes.htm
server.Get(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) {
});
// 6. 查看图片内容
server.Get(R"(/image/show/(\d+))", [&image_table](const Request& req, Response& resp) {
});
// 设置静态文件目录
server.set_base_dir("./wwwroot");
server.listen("0.0.0.0", 9094);
return 0;
}
// 需要包含头文件
// #include <openssl/md5.h>
// Makefile 需要 -lcrypto
std::string StringMD5(const std::string& str) {
const int MD5LENTH = 16;
unsigned char MD5result[MD5LENTH];
// 调用 openssl 的函数计算 md5
MD5((const unsigned char*)str.c_str(),str.size(),MD5result);
// 转换成字符串的形式方便存储和观察
char output[1024] = {0};
int offset = 0;
for (int i = 0; i < MD5LENTH; ++i) {
offset += sprintf(output + offset, "%x", MD5result[i]);
}
return std::string(output);
}
测试上传图片,实现一个测试页面upload.html,生成一个按钮,点击之后弹出文件选择框,发送按钮,就会给服务器发送一个特殊的http请求。验证上传的图片是否能成功。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</head>
<body>
<form method="POST" enctype="multipart/form-data" action="http://0.0.0.0:9094/image">
<table>
<tr>
<td>文件上传:</td>
<td><input type="file" name="filename"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="上传"/></td>
</tr>
</table>
</form>
</body>
</html>
正则表达式的简单实用
raw string(C++11),作用是使得转移字符不生效,用来表示正则表达式。
正则表达式是一个带有特殊符号的字符串,描述了一个字符串的特征,(字符串应该包含什么信息,某个信息开头,以某个信息结尾,某个信息重复出现多少次),代码中使用的\d+表示匹配一个数字。
项目扩展
- 对相同的图片可以只存储一份,利用md5校验两张图片是否相同,然后通过计数器实现同一个图片只存储一份
- 支持一些简单图片的处理功能,例如在请求参数中带上参数w=50&h=50,得到一个50*50的图片
- 防盗链,其实就是权限控制,只能让图片被特定的用户使用,借助cookie,实现相关账户的验证功能。