图酷

图库 整体架构
核心就是一个 HTTP 服务器, 提供对图片的增删改查能力. 搭配简单的页面辅助完成图片上传/展示,在 MySQL 数据库中存储对应的图片信息。
数据库设计
这个项目用到了一张表,存储图片的ID,name,id,name,size,upload_time,type,path,md5
图酷
文章图片

接口设计(hpp)

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); //删除某张图片 }; }

接口的实现
1.初始化(static MYSQL* MySQLInit() {})
static MYSQL* MySQLInit() { // 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 NULL; } // 3. 设置编码格式 mysql_set_character_set(mysql, "utf8"); return mysql; } static void MySQLRelease(MYSQL* mysql) { mysql_close(mysql); }

2.释放(static void MySQLRelease(MYSQL* mysql) {})
static void MySQLRelease(MYSQL* mysql) { mysql_close(mysql); }

3.类中接口的实现
3.1插入图片( bool Insert(const Json::Value& image) {})
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; }

3.2查看所有图片信息( bool SelectAll(Json::Value* images) {})
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; }

3.3.获取某张图片信息(bool SelectOne(int image_id, Json::Value* image_ptr) {})
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; }

3.4.删除某张图片(bool Delete(int image_id); )
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; }

代码如下
#pragma once #include #include #include #include namespace image_system { static MYSQL* MySQLInit() { // 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 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) {} 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["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_; }; }

服务器的基本框架 1.实现完整上传图片接口
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"); // 3. 把图片属性信息插入到数据库中 Json::Value image; image["image_name"] = file.filename; image["size"] = (int)file.length; image["upload_time"] = "2018/08/29"; // TODO image["md5"] = "aaaaaaa"; // 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; });

2.实现查看所有图片信息接口
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; });

3.实现查看指定图片的接口
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()); });

4.实现删除图片接口
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. 删除磁盘上的文件 unlink(image["path"].asCString()); // 5. 构造响应 resp_json["ok"] = true; resp.status = 200; resp.set_content(writer.write(resp_json), "application/json"); });

构造一个类,用于实现读写操作
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); file.read((char*)content->c_str(), content->size()); file.close(); return true; } };

服务端代码如下
#include #include #include #include "httplib.h" #include "db.hpp"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); 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); signal(SIGINT, [](int) { image_system::MySQLRelease(mysql); exit(0); }); Server server; 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"); // 3. 把图片属性信息插入到数据库中 Json::Value image; image["image_name"] = file.filename; image["size"] = (int)file.length; image["upload_time"] = "2018/08/29"; // TODO image["md5"] = "aaaaaaa"; // 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"); }); 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. 删除磁盘上的文件 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; }

【图酷】测试
测试相关功能是需要用到postman来构造请求

    推荐阅读