一个网页的图片是如何展示的?
1.有一个url来表示图片的位置、
2.有一个image标签,里面引用这个位置
的项目核心需求:就是实现HTTP服务器,然后用这个服务器来存储图片。针对每个图片提供
一个唯一的url,有了这个url之后
就可以借助它把图片展示到其他网页上
核心的需求
1.上传图片(得到一个url)
2.根据图片url访问图片,获取图片内容
3.获取某个图片的属性
4.删除
模块划分
1.数据存储;
数据库MySQL
2.服务器模块(给前端提供一些接口)
数据库设计;
create table image_table(
-> image_id int,
-> image_name varchar(256),
-> size int,
-> upload_time varchar(50),
-> type varchar(50),
-> path varchar(1024),
-> md5 varchar(50),
-> );
md5是一种字符串hash算法
1.不管啥样的字符串,最终得到的md5值都是固定长度
2.如果一个字符串,内容稍有变化,得到md5值差异很大
3.通过源字符串计算md5很容易,但是拿到md5还原串理论上不可能
上传图片之后,服务器就可以计算一个该图片的md5值
后续用户下载图片的时候也能获取到该图片的md5值
后续用户下载图片的时候,也能获取到该图片的
md5,用户可以把自己计算的md5和服务器计算的
md5对比,就知道自己的图片是否下载正确了
>重定向,本来是输出到标准输出上,输出到文件上
<重定向,把标准输入重定向到文件上
2>重定向,把标准错误重定向到文件中。
create database if not exist image_system2;
use image_system2;
drop table if exists image_table;
create table image_table(
image_id int not null primary key auto_increment,
image_name varchar(256),
size int,
upload_time varchar(50),
md5 varchar(128),
type varchar(128),
path varchar(1024),
);
如何实现一个数据库的客户端程序?
MySQL已经提供了一系列的的api实现客户端,
#include<cstdio>
#include<cstdlib>
#include<mysql/mysql.h>
int main()
{
//使用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 1;
}
//3.设置编码格式
mysql_set_character_set(mysql,"utf8");
//4.拼接sql语句
char sql[4096]={0};
sprintf(sql,"insert into image_table values(null,'test.png',024,'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;
}
return 0;
}
服务器模块;
HTTP服务器需要接受http请求,返回http响应,此处我们需要约定不同的请求来表示不同的操作方式。
如有的请求是上传图片,有的请求是查看图片,有些则是表示删除的请求。
服务端代码如下:
#include<fstream> #include<openssl/md5.h>
#include<iostream>
#include<time.h>
#include <signal.h>
#include <sys/stat.h>
#include "httplib.h"
#include "db.hpp"
using namespace std;
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;
string get_strtime()
{
time_t tt;
time(&tt);
tm* t= localtime(&tt);
char buf[128] = {0};
int ret = strftime(buf, 128, "%F", t);
string tmp;
tmp.assign(buf, ret);
cout << tt << endl;
return tmp;
}
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.c
ontent_type;
// 3. 把图片属性信息插入到数据库中
Json::Value image;
image["image_name"] = file.filename;
image["size"] = (int)file.length;
image["upload_time"] = get_strtime();
image["md5"] = "aaaaaaaa"; // TODO
image["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. 先获取到图片 id
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/jpg
resp.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;
}
4.项目的扩展
存储时合并文件,上传大量的比较小的文件,把这些逻辑上比较小的文件合并成一个比较大的物理文件,数据库中除了存这个该文件路径外,再存一个偏移量。
5.项目中遇到的问题
首先就是对库文件的使用方法不熟悉,需要通过查阅网上相关内容才能继续进行,下来就是对有些协议的使用,对其概念有点生疏,因此还需要我再继续加强各种基础协议栈的掌握。