图片服务器项目----My own photo world

前言

1 什么是图床?
简单来说,图床就是在云端的一块网络空间,我们可以将照片上传至云端,同时也可以随时访问云端的图片,我们在需要使用图片的时候,只需要拿到图片的链接 就可以将图片放在任何网页或工具中.这样一来我们就可以高效、便捷的管理和使用我们的图片。我们的私人图床就像是我们个人专属的私有图片空间。在这个图片世界只属于你。

2 为什么需要图床
对于一些内容输出者、文案创作者来说,他们的工作就是内容创作,俗称码字。同时呢,在码字的时候经常需要配图,就是在文字中插入图片,然而传统插图的方式很不方便,而且图片的复用率和移植性比较低,比如说,我们写的文字作品需要在多个平台发布,如果我们的图片存储在本地,或者某一个具体的平台上,这个时候由于平台不同,我们就需要重复机械的多次上传到不同平台,这是非常低效的,而如果我们使用图床将图片存储在云端的话,我们只需要将图片链接贴上去,这时我们的图片资源将可以被不同平台和工具使用,大大提高的图片的复用率、移植性、共享性,同时将文字创作者从繁琐的图片操作中解放出来,让他们把精力放在内容创作本身上去,这样可以大大提高他们的工作效率和生活的幸福感。

技术设计实现

1 整体的架构

我们图床项目的整体架构主要由两部分组成。

(1)第一部分是数据库的设计,这一部分主要设计表,来描述我们的图片。实现图片的存储管理。

(2)第二部分是服务器设计,这一部分主要实现具体的功能(对照片的增删改查)。

2 具体代码实现

数据库端设计(使用mysql)

设计一张表来描述图片的属性

设计一张表来描述图片的属性
create database if not exists image_system2;
use image_system2;

drop table if exists image_table;
create table image_table(
  image_id int not null primary key auto_increment,// 图片ID
  image_name varchar(256),//图片名
  size int,//图片大小
  upload_time varchar(50),//图片上传时间
  md5 varchar(128),// 图片的md5值
  type varchar(128),//图片格式
  path varchar(1024)//图片存储路径
);

insert into image_table values(null, 'test.png', 1024, '2019/08/26', 'aaaabbbbbccccc', 'png', 'data/test.png')

模拟数据库插入数据mysql_insert.cc

#include<cstdio>
#include<cstdlib>
#include<mysql/mysql.h>

// #include<mysql/mysql.h>  提供了操作函数 API


int main(){


  // 用mysql  API 来 操作数据库 
  //  1 先创建一个mysql 的句柄 
  MYSQL* mysql=mysql_init(NULL);   // 拿到遥控器
  // nclude<mysql/mysql.h>
  // 2  拿着句柄和数据库建立连接  

  if(mysql_real_connect(mysql,"127.0.0.1","root","1","image_system",3306,NULL,0)==NULL){
    //  数据库连接失败
    printf("数据库连接失败!%s\n",mysql_error(mysql));
    return 1;
  }

  // 3  设置编码格式(客户端)  服务端和客户端的编码格式必须匹配  
  mysql_set_character_set(mysql,"utf8"); // 不设置会出现数据乱码
  // 4 拼接SQL语句  (组织mysql 操作命令)
  char sql[4096]={0};
  sprintf(sql,"insert into image_table values(null,'test.png',1024,'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;// 1 为进程的退出码
  }
  // 6 关闭句柄
  mysql_close(mysql);

  return 0;
}

模拟实现数据库查询数据mysql_select.cc

#include<cstdio>
#include<cstdlib>
#include<mysql/mysql.h>

// #include<mysql/mysql.h>  提供了操作函数 API


int main(){


  // 用mysql  API 来 操作数据库 
  //  1 先创建一个mysql 的句柄 
  MYSQL* mysql=mysql_init(NULL);   // 拿到遥控器
  // nclude<mysql/mysql.h>
  // 2  拿着句柄和数据库建立连接  

  if(mysql_real_connect(mysql,"127.0.0.1","root","1","image_system",3306,NULL,0)==NULL){
    //  数据库连接失败
    printf("数据库连接失败!%s\n",mysql_error(mysql));
    return 1;
  }

  // 3  设置编码格式(客户端)  服务端和客户端的编码格式必须匹配  
  mysql_set_character_set(mysql,"utf8"); // 不设置会出现数据乱码
  // 4 拼接SQL语句  (组织mysql 操作命令)
  char sql[4096]={0};
  sprintf(sql," select * from image_table");
  // 5 执行SQL 语句   ,负责客户端给服务器 发送数据的过程

  int  ret=mysql_query(mysql,sql);
  if(ret !=0){
    printf("执行 sql 失败! %s\n",mysql_error(mysql));
    return 1;// 1 为进程的退出码
  }

  // 6 获取结果集合
  MYSQL_RES* result=  mysql_store_result(mysql);
  int rows=mysql_num_rows(result); // 获取行号
  int cols=mysql_num_fields(result);

  for(int i=0;i<rows;++i){
    MYSQL_ROW row=mysql_fetch_row(result);// 获取一行内容
    for(int j =0;j<cols;++j){
      printf("%s\t",row[j]); // 获取一列内容
    }
    printf("\n");
  }

  //7  释放结果集合
  mysql_free_result(result);

  
  // 8  关闭句柄
  mysql_close(mysql);

  return 0;
}

在这里插入代码片

服务器端设计

封装数据库 db.hpp

#pragma once
#include<cstdlib>
#include<cstdio>
#include<mysql/mysql.h>
#include<jsoncpp/json/json.h> // 借助第三方库json  操作和解析json
// 一个类  json ::Value  非常类似于 std::map
//   两个重要方法   1   Reader::parse  把一个json格式 字符转成 json ::Value 对象 -----反序列化
//   两个重要方法   2   Write::write 把一个json::Value对象转成字符串-----序列化

namespace image_system{



  static MYSQL *MySQLInit(){

    // 1  创建句柄  
    MYSQL*mysql=mysql_init(NULL);
    // 2 拿着句柄和数据库建立连接
    if(mysql_real_connect(mysql,"127.0.0.1","root","1","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);
  }

  //   这个类是用来操作数据库 的image_table 这个表.
  //   此处使用json   参数  (为了 参数太多)
  class ImageTable{
    public:
      //构造函数
      ImageTable(MYSQL*mysql):mysql_(mysql){}
      // image 就形如一下形势:
      //{
      //image_name: "test.png",
      //size: 1024,
      //upload_time:"2019/9/1"
      //.....
      //.....
      //path:"data/test.png"
      //}
      //  使用json的原因: 1 扩展方便 2 方便和服务器接收到的数据打通
      //    上传图片---输入数据
      bool Insert(const Json::Value& image ){
        // 拼装字符串(mysql语句)
        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);// 打印出来看看  可视化 利于纠错

        // 执行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};
        // 拼接SQL语句
        sprintf(sql,"select * from image_table where image_id=%d",image_id);
        int ret=mysql_query(mysql_,sql);
        if(ret!=0){
          printf("SelectOne  执行失败! %s\n",mysql_error(mysql_));
          return false;
        }
        // 走到这里说明SQL语句执行成功
        // 遍历结果集合
        MYSQL_RES* result=mysql_store_result(mysql_);// 获取结果集
        int rows=mysql_num_rows(result);// 获取一行数据 将行数保存在rows
        // 进行结果校验  rows  预期结果应该为1 
        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[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  执行失败! %s\n",mysql_error(mysql_));
          return false;
        }
        return true;
      }

    private: 
      MYSQL*mysql_;
  };
}// end image_system

测试数据库

include"db.hpp"
//  单元测试--测试功能点  接口
void testImageTable(){
  // 创建一个ImageTable 类  去调用其中的方法  验证结果
  Json::StyledWriter writer;
  MYSQL* mysql=image_system::MySQLInit(); // 初始化数据库 拿到操作句柄
    image_system::ImageTable image_table(mysql);// 构造函数
    bool ret =false;
    // 1  插入数据
   // Json::Value image;
   // image["image_name"]="test.png";
   // image["size"]=1024;
   // image["upload_time"]="2019/08/31";
   // image["md5"]="abcdef";
   // image["type"]="png";
   // image["path"]="data/test.png";
   //1 ret =image_table.Insert(image);
    //printf("ret=%d\n",ret);

  // 2 .查找所有图片的信息
 // Json:: Value images;
 // ret=image_table.SelectAll(&images);
  //printf("ret=%d\n",ret);
  //printf("%s\n",writer.write(images).c_str());

// 3 查看指定图片信息
  //Json::Value image;
 // ret=image_table.SelectOne(1,&image);
 // printf("ret=%d\n",ret);
 // printf("%s\n",writer.write(image).c_str());
 
//  4 删除指定图片
    ret=image_table.Delete(1);
  printf("ret=%d\n",ret);


   image_system::MySQLRelease(mysql);
}

int main(){
  testImageTable();
  return 0;
}

服务器API具体代码实现

#include<fstream>  //c++ 提供文件读写操作
#include"httplib.h"
#include"db.hpp"
#include<signal.h>
#include<sys/stat.h>

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

    // 将文件中的内容读取到字符串content 中
    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 字符串大小设置为和文件大小一样
      content->resize(st.st_size);
      // char* 缓冲区长度
      // // int 读取多长

      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);  //  创建一个数据库对象(操作数据库)

  // 由于程序泡在服务器上,因此  不能在程序中释放,可通过 Ctrl  C  手动释放
  signal(SIGINT,[](int){
      image_system:: MySQLRelease(mysql);
      exit(0);
      });

  Server server; //  创建一个对象
  // 客户端请求 /hello 路径的时候 ,执行一个特定的函数
  // 指定不同的路径对应到不同的函数上  这个过程叫做  设置路由
  //  服务器中有两个特别重要的概念:
  //  1 requst :请求
  //  2 response: 响应
  //  [&image_table]  这是lambda 的重要特性  用来捕获变量
  //  本来 lambda 内部是不能直接访问image_table  的
  // 捕获之后 就在lambda 内部就可以访问了 (相当于强制捕获传参), 其中&  的含义是 相当于按引用捕获
  server.Post("/image",[&image_table](const Request& req,Response& resp){
      Json::FastWriter writer;
      Json::Value resp_json;//搞一个json对象
      printf("上传图片\n");
      // 1 先对参数进行校验
      // auto size = req.files.size();// 图片个数
      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.content_type;
      // body : 图片内容

      //  auto body = req.body.substr(file.offset, file.length);//offset  起始位置  length截取长度   提取图片内容    通过将图片内容写入文件来校验图片内容
      //  3将图片属性信息插入到数据库中 (以json格式组织数据)

      Json::Value image;
      image["image_name"]=file.filename;
      image["size"]=(int)file.length;
      image["upload_time"]="208/08/29"; //  暂时写死(此时是固定时间) //   to do (这里还没有写完呢)  !!!
      image["md5"]="aaaaaaa" ;  // 同上  暂时写死
      image["type"]=file.content_type;
      image["path"]="./data/"+file.filename;

      ret= image_table.Insert(image);// 以json数据包插入到数据库中
      if(!ret){
        printf("image_table Insert faile!\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){    
      printf("获取所有图片的信息");
      (void)req;   // 没有实际的效果  显示告诉编译器  这个你不要管
      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");
      });




  // 正则表达式
  // 原始字符串(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["reson"]="数据库查询出错";
      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["reson"]="数据库查询出错";
      resp.status=404;
      resp.set_content(writer.write(resp_json),"application/json");
      return ;

      }
      // 2 根据目录找到文件内容,读取文件内容
      std::string image_body ; // 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["reson"]="读取文件失败";
        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["reson"]="删除图片取文件失败";                     
        resp.status=404;                                             
        resp.set_content(writer.write(resp_json),"application/json");
        }

      //3 调用数据库操作进行删除
       ret=image_table.Delete(image_id);
      if(!ret){                                                      
      printf("删除图片文件失败!\n");
      resp_json["ok"]=false;
      resp_json["reson"]="删除图片取文件失败";
      resp.status=404;
      resp.set_content(writer.write(resp_json),"application/json");
      return ;
      }
      // 4  删除磁盘上文件
      // C++标准库中,没有删除文件的方法
      //  可以使用操作系统提供的方法
      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;

}




发布了61 篇原创文章 · 获赞 7 · 访问量 2623
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览