【Linux项目】图片服务器

 

目录

项目描述:

整体架构:

 模块划分:                                                                                                                         

md5运用:

数据库设计:

使用MySQL  C  API   操作数据库:

数据库插入数据:mysql_insert.cc

数据库查找数据:mysql_select.cc

服务器API设计:

1.API定义:

1.API功能:

http请求中的8种请求方法

新增图片:

查看所有图片元信息:

查看指定图片元信息 :

查看图片内容:

删除图片:

服务端实现:

封装数据库操作:

使用 JSON 作为数据交互

测试数据库操作

服务器基本框架 

测试上传图片

接口实现


项目描述:

核心需求:

  1. 存储图片,实现一个http的服务器;
  2. 用这个服务器存储图片,针对每个图片提供唯一的url,根据url将图片放在网页上

项目内容:

  1. url:有一个url来表示图片的位置,
  2. 有一个image标签,里面引用这个位置
  3. 获取某个图片的属性
  4. 删除图片

整体架构:

 模块划分:                                                                                                                         

模块划分:

  • 数据存储模块:服务器模块给前端提供一些接口
  • 数据存储模块:                                                                                                                                                                                   数据存储方式:冯诺依曼存储器(内存:易失;外存(磁盘)):持久性存储,云端:网络;数据库:存储信息)
  • 服务器模块:(给前端提供接口)

md5运用:

  • md5是一种字符串哈希算法;
  1. 无论什么字符串,他的MD5都是固定长度,md5sum 计算一个文件的md5值:                                                                      
  2. [yu@localhost ~]$ md5sum a.out
    fb465f726b23e322267626b1965e2020  a.out
  3. 如果字符串内容稍有变化,得到的MD5值差异很大;
  4. 通过字符串很容易获得MD5值,由MD5值退出字符串理论上不可能。
  5. MD5 算法还可以作为一种电子签名的方法来使用,使用 MD5算法就可以为任何文件(不管其大小、格式、数量)产生一个独一无二的“数字指纹”,借助这个“数字指纹”,通过检查文件前后 MD5 值是否发生了改变,就可以知道源文件是否被改动
  6. 我们在下载软件的时候经常会发现,软件的下载页面上除了会提供软件的下载地址以外,还会给出一串长长的字符串。这串字符串其实就是该软件的MD5 值,它的作用就在于下载该软件后,对下载得到的文件用专门的软件(如 Windows MD5 check 等)做一次 MD5 校验,以确保我们获得的文件与该站点提供的文件为同一文件。
  7. 利用 MD5 算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面  。
  • md5运用作用:
  1. 这个字段用来进行校验图片(文件))内容正确性,
  2. 上传图片后,服务器计算一个该图片的md5值,
  3. 后续用户下载图片后,也能获取到该图片的MD5值,
  4. 两次md5进行对比,确定图片内容正确性,

 

数据库设计:

数据库设计(数据库创表和表头的信息):

  1. 创建一张表:
  2. [yu@localhost ~]$ mkdir class
    [yu@localhost ~]$ cd class
    [yu@localhost class]$ mkdir image_server
    [yu@localhost class]$ cd image_server
    [yu@localhost image_server]$ mkdir mysql
    [yu@localhost image_server]$ cd mysql
    [yu@localhost mysql]$ vim db.sql
    create databases 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 int,
           upload_time varchar(50),
          md5 varchar(128),
          type varchar(1024),
          path varchar(1024)
      );
     
      insert into image_table values(null,'test.png',1025,'2019/09/04','aaabbbccc','png','data/test.png')
  3. 针对图片内容,存在磁盘上
  4. 数据库内容迁移,创建库创表语句写入文件,mysql客户端批量执行文件
  5. >     重定向,本来输出到标椎输出上,输出到文件中

    <     重定向   把标椎输入重定向到文件上

    2>   重定向  标准错误重定向到文件中

  6. mysql -uroot < db.sql
    
    MariaDB [image_system]> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | db1                |
    | image_system       |
    | performance_schema |
    +--------------------+
    4 rows in set (0.00 sec)
    
    MariaDB [image_system]> show tables;
    +------------------------+
    | Tables_in_image_system |
    +------------------------+
    | image_table            |
    +------------------------+
    1 row in set (0.00 sec)
    
    MariaDB [image_system]> desc image_table;
    +-------------+---------------+------+-----+---------+----------------+
    | Field       | Type          | Null | Key | Default | Extra          |
    +-------------+---------------+------+-----+---------+----------------+
    | image_id    | int(11)       | NO   | PRI | NULL    | auto_increment |
    | image_name  | varchar(256)  | YES  |     | NULL    |                |
    | size        | int(11)       | YES  |     | NULL    |                |
    | upload_time | varchar(50)   | YES  |     | NULL    |                |
    | md5         | varchar(128)  | YES  |     | NULL    |                |
    | type        | varchar(128)  | YES  |     | NULL    |                |
    | path        | varchar(1024) | YES  |     | NULL    |                |
    +-------------+---------------+------+-----+---------+----------------+
    7 rows in set (0.10 sec)
    
    MariaDB [image_system]> select * from image_table;
    +----------+------------+------+-------------+--------------+------+-----------------+
    | image_id | image_name | size | upload_time | md5          | type | path            |
    +----------+------------+------+-------------+--------------+------+-----------------+
    |        1 | test.png   | 1024 | 2019/08/26  | aaaabbbbcccc | png  | ./data/test.png |
    |        2 | test.png   | 1024 | 2019/08/26  | abcdef       | png  | data/test.png   |
    +----------+------------+------+-------------+--------------+------+-----------------+
    2 rows in set (0.01 sec)
    
    MariaDB [image_system]>
    
    

     

使用MySQL  C  API   操作数据库:

                                                                                                                  

  1. 将查询出数据转换为JSON格式作为输出响应
  2. JSON是一种数据组织格式,序列化与反序列化是最为关键的内容,是一种为人类容易理解的一种格式,相对来讲牺牲了部分效率。
  3. 而与之对应的protobuf是谷歌出品的一种二进制序列化协议,效率很高,自然对于人类来讲很难看懂,是给计算机读的二进制文件。
  4. 安装MySQL C API:
yum install mysql-devel

代码中使用时需要链接上 MySQL 提供的库

-L /usr/lib64/mysql -lmysqlclient

查看是否成功安装 

[yu@localhost mysql]$ ll mysql.h
-rw-r--r--. 1 root root 38500 Apr 20  2018 mysql.h
[yu@localhost mysql]$ cd /usr/include/mysql/
[yu@localhost mysql]$ ll mysql.h
-rw-r--r--. 1 root root 38500 Apr 20  2018 mysql.h
vim mysql_insert.cc

 

  1. 数据库插入数据:mysql_insert.cc

     #include<cstdio>                                                    
        #include<cstdlib>
        #include<mysql/mysql.h>
      
       int main()
     {
           //使用mysql  API来操作数据库
           //1.创建一个mysql的句柄
           MYSQL* mysql = mysql_init(NULL);
             //2.拿着句柄和数据库建立连接
                            // mysql_init 返回的指针
            // 主机地址
            // 用户名
            // 密码
            // 数据库名
            // 端口号
            // unix_socket
            // client_flag
            if(mysql_real_connect(mysql,"127.0.0.1","root",
                 "","image_system",3306,NULL,0)==NULL){
               
               //数据库连接失败
               printf("连接失败!%s\n",mysql_error(mysql));
             return 1;
               //3.设置编码格式,服务器编码格式默认不是utf8,安装时配置成utf8;
               mysql_set_character_set(mysql,"utf8");
                   //4.拼接SQL语句
                   char sql[4096]={0};
                   char image_name[] = "滑稽.jpg";  int size = 16 * 1024;
                   char upload_time[] = "2019/05/14";
                   char md5[] = "123456";
                   char content_type[] = "jpg";
                   char path[] = "./滑稽.jpg";
                   sprintf(sql,"insert into image_table values(null,'test.png',1024
                    //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;
       }              
    
       

    usr==>unix shared resoutce; unix 共享库

     

    测试写的代码能否运行,写一个Makefile编译

    [yu@localhost mysql]$ vim makefile
    
      1 mysql_insert:mysql_insert.cc
      2     gcc $^ -o $@  -L/usr/lib/mysql -lmysqlclient
      3 .PHONY:clean
      4     clean:
      5     rm mysql_insert                                                      
    ~
    
    
    
    [yu@localhost mysql]$ cd /usr/lib64/
    [yu@localhost lib64]$ cd mysql/
    [yu@localhost mysql]$ ll
    
    
    -rwxr-xr-x. 1 root root  3135712 Aug 16  2018 libmysqlclient.so.18.0.0   //.so 动态库   
    
    
    [yu@localhost ~]$ cd class
    [yu@localhost class]$ cd image_server
    [yu@localhost image_server]$ cd mysql/
    [yu@localhost mysql]$ vim makefile
    [yu@localhost mysql]$ make
    [yu@localhost mysql]$ ll
    [yu@localhost mysql]$ ./mysql_insert

     

  2. 数据库查找数据:mysql_select.cc

    // 1. 初始化句柄
    // 2. 建立连接
    // 3. 设置编码格式
    // 4. 拼装 SQL 语句
    // 5. 执行 SQL 语句
    // 6. 遍历查询结果
    // 7. 释放结果集
    // 8. 关闭句柄
    #include <cstdio>
    #include <cstdlib>
    #include <mysql/mysql.h>
    int main() {
      // 1. 初始化句柄
      MYSQL* connect_fd = mysql_init(NULL);
      // 2. 建立链接
      // mysql_init 返回的指针
      // 主机地址
      // 用户名
      // 密码
      // 数据库名
      // 端口号
      // unix_socket
      // client_flag
      if (mysql_real_connect(connect_fd, "127.0.0.1", "root", "",
      "image_system", 3306, NULL, 0) == NULL) {
        printf("连接失败! %s\n", mysql_error(connect_fd));
        return 1;
     }
      // 3. 设置编码格式
    mysql_set_character_set(connect_fd, "utf8");
      // 4. 拼装 SQL 语句
    char sql[1024 * 4] = {0};
    sprintf(sql, "select * from image_table");
      // 5. 执行 SQL 语句
      int ret = mysql_query(connect_fd, sql);
      if (ret < 0) {
        printf("执行 sql 失败! %s\n", mysql_error(connect_fd));
        return 1;
     }
      // 6. 遍历查询结果
      MYSQL_RES* result = mysql_store_result(connect_fd);
      if (result == NULL) {
        printf("获取结果失败! %s\n", mysql_error(connect_fd));
        return 1;
     }
      // a) 获取行数和列数
      int rows = mysql_num_rows(result);
      int fields = mysql_num_fields(result);
      printf("rows: %d, fields: %d\n", rows, fields);
      // b) 打印结果
      for (int i = 0; i < rows; ++i) {
        MYSQL_ROW row = mysql_fetch_row(result);
        for (int j = 0; j < fields; ++j) {
          printf("%s\t", row[j]);
       }
        printf("\n");
     }
      // 7. 释放结果集
      mysql_free_result(result);
      // 8. 关闭句柄
      mysql_close(connect_fd);
      printf("执行成功!\n");
      return 0; }
    

    连接数据库中可能会产生的问题
    数据的字符编码问题
    解决方法:
    修改编码方式
    mysql_query(_mySql, “set names ‘gbk’”);

  3. (1)修改 mysql安装根目录下的my.ini(MySQL Server Instance Configuration 文件),
    (2)设置default-character-set=gbk(注意,有2处) 		
    (3)在data目录下,打开相应数据库的文件,找到db.opt配置文件
    (4)设置default-character-set=gbk
                default-collation=gbk_chinese_ci
    重启mysql就好了
    

     

服务器API设计:

1.API定义:


1)API 是用于构建应用程序软件的一组子程序定义,协议和工具。一套明确定义的各种软件组件之间的通信方法,实现和其他软件快速交互。
2)API 应用范围很广:从操作系统中简单的 fork() 到我们接触的百度地图API,和风天气API等

1.API功能:


1)API确认所有操作都是合法的
2)在发生错误时,API会根据错误报告机制发出指示
3)API内置授权和访问控制,确保只有授权人员才能访问特殊数据
4)可以充当防火墙,防止资源被滥用,允许合法的请求

http请求中的8种请求方法


1、opions   返回服务器针对特定资源所支持的HTML请求方法   或web服务器发送*测试服务器功能(允许客户端查看服务器性能)
2、Get   向特定资源发出请求(请求指定页面信息,并返回实体主体)
3、Post   向指定资源提交数据进行处理请求(提交表单、上传文件),又可能导致新的资源的建立或原有资源的修改
4、Put   向指定资源位置上上传其最新内容(从客户端向服务器传送的数据取代指定文档的内容)
5、Head  与服务器索与get请求一致的相应,响应体不会返回,获取包含在小消息头中的原信息(与get请求类似,返回的响应中没有具体内容,用于获取报头)
6、Delete   请求服务器删除request-URL所标示的资源*(请求服务器删除页面)
7、Trace   回显服务器收到的请求,用于测试和诊断
8、Connect   HTTP/1.1协议中能够将连接改为管道方式的代理服务器
http服务器至少能实现get、head、post方法,其他都是可选的
 

  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 
    }

     

服务端实现:

  1. 封装数据库操作:

    接口设计

    db.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); 
    }; 
    }

    各个接口代码实现

    db.hpp

    // 这个文件相当于 model 层. 
    // 只进行数据的基本 CURD , 不涉及到更复杂的数据加工
    #pragma once 
    #include <cstdlib> 
    #include <cstring> 
    #include <stdint.h> 
    #include <string> 
    #include <memory> 
    #include <mysql/mysql.h> 
    #include <jsoncpp/json/json.h> 
    namespace image_system { 
    static MYSQL* MySQLInit() { 
     MYSQL* connect_fd = mysql_init(NULL); 
     if (mysql_real_connect(connect_fd, "127.0.0.1", "root", "", 
     "image_system", 3306, NULL, 0) == NULL) { 
     printf("连接失败! %s\n", mysql_error(connect_fd)); 
     return NULL; 
     } 
     mysql_set_character_set(connect_fd, "utf8"); 
     return connect_fd; 
    } 
    static void MySQLRelease(MYSQL* mysql) { 
     mysql_close(mysql); 
    } 
    class ImageTable { 
    public: 
     ImageTable(MYSQL* mysql) : mysql_(mysql) { } 
     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["name"].asCString(), image["size"].asInt(), image["upload_time"].asCString(), 
     image["md5"].asCString(), image["content_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; 
     } 
     bool SelectAll(Json::Value* images) { 
     char sql[1024 * 4] = {0}; 
     // 可以根据 tag_id 来筛选结果
     sprintf(sql, "select * from image_table"); 
     int ret = mysql_query(mysql_, sql); 
     if (ret != 0) { 
     printf("执行 sql 失败! %s\n", mysql_error(mysql_));
     return false; 
     } 
     MYSQL_RES* result = mysql_store_result(mysql_); 
     if (result == NULL) { 
     printf("获取结果失败! %s\n", mysql_error(mysql_)); 
     return false; 
     } 
     int rows = mysql_num_rows(result); 
     for (int i = 0; i < rows; ++i) { 
     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["content_type"] = row[5]; 
     image["path"] = row[6]; 
     images->append(image); 
     } 
     return true; 
     } 
     bool SelectOne(int32_t image_id, Json::Value* image) { 
     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("执行 sql 失败! %s\n", mysql_error(mysql_)); 
     return false; 
     } 
     MYSQL_RES* result = mysql_store_result(mysql_); 
     if (result == NULL) { 
     printf("获取结果失败! %s\n", mysql_error(mysql_)); 
     return false; 
     } 
     int rows = mysql_num_rows(result); 
     if (rows != 1) { 
     printf("查找结果不为 1 条. rows = %d!\n", rows); 
     return false; 
     } 
     MYSQL_ROW row = mysql_fetch_row(result); 
     (*image)["image_id"] = atoi(row[0]); 
     (*image)["name"] = row[1]; 
     (*image)["size"] = atoi(row[2]); 
     (*image)["upload_time"] = row[3]; 
     (*image)["md5"] = row[4]; 
     (*image)["content_type"] = row[5]; 
     (*image)["path"] = row[6]; 
     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("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_)); 
     return false; 
     } 
     return true; 
     } 
    private: 
     MYSQL* mysql_; 
    }; 
    } // end blog_system
    
    

     

  2. 使用 JSON 作为数据交互

    json 出自 JavaScript, 是一种非常方便的键值对数据组织格式, 目前被业界广泛使用.

    C++ 中可以使用 jsoncpp 这个库来解析和构造 json 数据

    yum install jsoncpp-devel

     

  3. 测试数据库操作

    // 实现一个数据库接口测试程序, 用来验证前面的数据库接口是否正确
    #include <iostream> 
    #include "db.hpp" 
    using namespace image_system; 
    void TestImageTable() { 
     bool ret = false; 
     // 更友好的格式化显示 Json 
     Json::StyledWriter writer; 
     MYSQL* mysql = MySQLInit(); 
     Json::Value image; 
     image["name"] = "滑稽.jpg"; 
     image["size"] = 16 * 1024; 
     image["upload_time"] = "2019/01/01"; 
     image["md5"] = "987654321"; 
     image["content_type"] = "image/jpg"; 
     image["path"] = "./滑稽.jpg"; 
     std::cout << "==============测试插入=====================" << std::endl; 
     ImageTable image_table(mysql); 
     ret = image_table.Insert(image); 
     std::cout << "Insert: " << ret << std::endl; 
     std::cout << "==============测试查找=====================" << std::endl; 
     Json::Value images; 
     ret = image_table.SelectAll(&images); 
     std::cout << "SelectAll: " << ret << std::endl 
     << writer.write(images) << std::endl; 
     Json::Value image_out; 
     ret = image_table.SelectOne(1, &image_out); 
     std::cout << "SelectOne: " << ret << std::endl 
     << writer.write(image_out) << std::endl; 
     std::cout << "==============测试删除=====================" << std::endl; 
     int image_id = 2; 
     ret = image_table.Delete(image_id); 
     std::cout << "Delete: " << ret << std::endl; 
     MySQLRelease(mysql); 
    } 
    int main() { 
     TestImageTable(); 
     return 0; 
    }

    Makefifile

    FLAGS=-std=c++11 -L/usr/lib64/mysql -lmysqlclient -ljsoncpp -lpthread -g 
    .PHONY:all 
    all:db_test 
    db_test:db_test.cc db.hpp 
     g++ db_test.cc -o db_test $(FLAGS) 
    .PHONY:clean 
    clean: 
     rm db_test

     

  4. 服务器基本框架 

     

    使用 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; 
    }

    g++ main.cc -lpthread -std=c++11

    /// 
    // 构建 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); 
    }

     

  5. 测试上传图片

    server.Post("/image_test", [](const Request& req, Response& resp) { 
     auto size = req.files.size(); 
     bool ret = req.has_file("filename"); 
     const auto& file = req.get_file_value("filename"); 
     // file.filename 
     // file.content_type 
     auto body = req.body.substr(file.offset, file.length); 
     LOG(INFO) << "size: " << size << ", ret: " << ret << ", " << file.filename << ", " 
     << file.content_type << ", " << file.offset << ", " << file.length << 
    std::endl; 
     FileUtil::WriteFile(file.filename, req.body.substr(file.offset, file.length)); 
     resp.set_content("ok", "text/html"); 
     });

    实现一个测试页面 upload.html, 放到 wwwroot 目录中

  6. 一个HTTP服务器的作用是接收到http请求,并根据请求返回相应的http响应。
    在HTTP协议中,用户自定制携带一些信息上传给服务器有多种方法,一种比较传统的方法是借助URL中query_string查询字符串来携带一些信息。
    那么我们可以直接使用浏览器构造http请求。
    我们上传图片借助一个HTML文件,在这里我们依旧使用别人封装好的简单的上传方法。
    我们的http请求都使用9090端口,在使用前我们先查看一下端口有没有被占用,具体命令:

    <html> 
    <head> 
     <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> 
    </head> 
    <body> 
     <form method="POST" enctype="multipart/form-data" 
    action="http://47.98.116.42: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>

    form标签表示这是一个form表单,form表单是一种传统的浏览器、网页和服务器交互的方式,其功能就是提供一些选项框,借助这些选项框将数据提交给服务器。

  7. 接口实现

实现完整上传图片接口

// 3. 新增图片. 
 server.Post("/image", [&image_table](const Request& req, Response& resp) { 
 Json::FastWriter writer; 
 Json::Value resp_json; 
 LOG(INFO) << "新增图片" << std::endl; 
 // 1. 进行参数校验
 bool ret = req.has_file("filename"); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "req has no filename field!"; 
 resp.status = 400; 
 resp.set_content(writer.write(resp_json), "application/json"); 
 return; 
 } 
 // 2. 构造 Json 格式数据, 并调用数据层接口插入数据
 const auto& file = req.get_file_value("filename"); 
 const std::string& image_body = req.body.substr(file.offset, file.length); 
 Json::Value image; 
 image["name"] = file.filename; 
 image["size"] = (int)file.length; 
 image["upload_time"] = TimeUtil::FormatTime(); 
 image["md5"] = StringMD5(image_body); 
 image["content_type"] = file.content_type; 
 // 为了防止重复, 用 md5 作为文件名更稳妥
 image["path"] = base_path + file.filename; 
 ret = image_table.Insert(image); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "insert db failed!"; 
 resp.status = 500; 
 resp.set_content(writer.write(resp_json), "application/json"); 
 return; 
 } 
 // 3. 保存文件实体
 FileUtil::WriteFile(image["path"].asString(), image_body); 
 // 4. 构造响应
 resp_json["ok"] = true; 
 resp.set_content(writer.write(resp_json), "text/html"); 
 });

实现查看所有图片元信息接口

 // 4. 查看所有图片的元信息
 server.Get("/image", [&image_table](const Request& req, Response& resp) { 
 (void) req; 
 Json::Reader reader; 
 Json::FastWriter writer; 
 Json::Value resp_json; 
 LOG(INFO) << "查看所有图片信息: " << std::endl; 
 // 1. 调用数据库接口查询数据
 Json::Value images; 
 bool ret = image_table.SelectAll(&images); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "SelectAll failed!\n"; 
 resp.status = 500; 
 resp.set_content(writer.write(resp_json), "application/json"); 
 return; 
 } 
 // 2. 构造响应结果
 resp.set_content(writer.write(images), "application/json"); 
 return; 
 });

实现查看单个图片元信息接口

 

 // 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) { 
 Json::Reader reader; 
 Json::FastWriter writer; 
 Json::Value resp_json; 
 // 1. 获取到图片 id 
 int image_id = std::stoi(req.matches[1]); 
 LOG(INFO) << "查看图片信息: " << image_id << std::endl;
// 2. 调用数据库接口查询数据
 Json::Value image; 
 bool ret = image_table.SelectOne(image_id, &image); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "SelectOne failed!\n"; 
 resp.status = 500; 
 resp.set_content(writer.write(resp_json), "application/json"); 
 return; 
 } 
 // 3. 构造响应结果
 resp.set_content(writer.write(image), "application/json"); 
 return; 
 });

实现查看图片内容接口

 // 6. 查看图片内容
 server.Get(R"(/image/show/(\d+))", [&image_table](const Request& req, Response& resp) { 
 Json::Reader reader; 
 Json::FastWriter writer; 
 Json::Value resp_json; 
 // 1. 获取到图片 id 
 int image_id = std::stoi(req.matches[1]); 
 LOG(INFO) << "查看图片信息: " << image_id << std::endl; 
 // 2. 调用数据库接口查询数据
 Json::Value image; 
 bool ret = image_table.SelectOne(image_id, &image); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "SelectOne failed!\n"; 
 resp.status = 500; 
 resp.set_content(writer.write(resp_json), "application/json"); 
 return; 
 } 
 std::string image_body; 
 ret = FileUtil::ReadFile(image["path"].asString(), &image_body); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "path open failed\n"; 
 resp.status = 500; 
 resp.set_content(writer.write(resp_json), "application/json"); 
 return; 
 } 
 // 3. 构造响应结果
 resp.set_content(image_body, image["content_type"].asCString()); 
 return; 
 });

实现删除图片接口

 // 7. 删除图片
 server.Delete(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) { 
 Json::Value resp_json; 
 Json::FastWriter writer; 
 // 1. 解析获取 blog_id 
 // 使用 matches[1] 就能获取到 blog_id 
 // LOG(INFO) << req.matches[0] << "," << req.matches[1] << "\n"; 
 int image_id = std::stoi(req.matches[1]); 
 LOG(INFO) << "删除指定图片: " << image_id << std::endl; 
 // 2. 调用数据库接口删除博客
 bool ret = image_table.Delete(image_id); 
 if (!ret) { 
 resp_json["ok"] = false; 
 resp_json["reason"] = "Delete failed!\n"; 
 resp.status = 500; 
 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; 
 });

 

 

 

 

 

             

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值