Skip to content

paozhu 框架 CRUD 教程

Huang ziquan edited this page Jan 10, 2023 · 7 revisions

paozhu 框架 CRUD 教程

paozhu 做为 c++ web framework 框架,crud是核心功能,现在我我们看看怎么使用crud

前面经过orm入门,view入门,那么我们可以正式做一个演示,学完crud你就可以写业务代码,算新手毕业,去实现自己业务。

1、创建控制器

我们到controller目录 分别在include和src创建

testcrud.h

testcrud.cpp

两个文件

controller

  • src
    • testcrud.cpp
  • include
    • testcrud.h

testcrud.h


#pragma once
#include <string>
#include <thread>
#include "httppeer.h"

namespace http
{

   std::string articlelogin(std::shared_ptr<httppeer> peer);
   std::string articleloginpost(std::shared_ptr<httppeer> peer);

   std::string articlelist(std::shared_ptr<httppeer> peer);
   std::string articleshow(std::shared_ptr<httppeer> peer);

   std::string articleedit(std::shared_ptr<httppeer> peer);
   std::string articleeditpost(std::shared_ptr<httppeer> peer);


   std::string articleadd(std::shared_ptr<httppeer> peer);
   std::string articleaddpost(std::shared_ptr<httppeer> peer);

   std::string articledelete(std::shared_ptr<httppeer> peer);
 
}

这次方法有点多,大家看名字应该也猜到功能了,

articlelogin articleloginpost是模拟后台登录

articlelist articleshow是 文章列表和详细内容显示

articleedit articleeditpost 是文章编辑和接受编辑内容提交更新

articleadd articleaddpost 是添加新文章页面和接受提交内容

articledelete 就是删除文章

一个完整crud 就是这些,可以根据自己业务添加字段或添加其它功能。

接下来我们要实现业务代码了

testcrud.cpp

   std::string articlelogin(std::shared_ptr<httppeer> peer)
   {
      // step1  show login page
      peer->view("login/login");
      return "";
   }

peer->view 就是显示登录页面 可以查看view视图教程,login/login 就是login目录下login.html文件,真实是 viewsrc/view/login/login.cpp文件。 我们使用了paozhu_cli 转化为cpp 文件了。

根目录运行

./bin/paozhu_cli

./bin/paozhu_cli model | view | viewtocpp | control

🎉 Welcome to use cli to manage your MVC files。

(m)model (v)view (f)viewtocpp or (c)control,x or q to exit[input m|v|f|c|]:

输入f

如果view目录下html文件有改动,那会显现出来,然后输入相应的数字就可以更新了,也可以输入a 全部更新

login.html 文件内容,里面有一个 include_sub("home/header",obj); 就是引用其他视图内容,home/header 表示home/header.html 文件内容,obj 就是控制器中peer->val的内容,home/header就是头部,我们每个页面可能都有,这样修改一处就可以全部修改了。

"/cms/loginpost" 就是articleloginpost 函数的urlpath挂载点,在reghttpmethod.hpp文件里面,后面会讲到。

login/login.html 文件内容

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Hugo 0.101.0">
    <title>Content Management System</title>

    <link href="/assets/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body class="text-center">
    <%c include_sub("home/header",obj); %>
    <div class="container text-center">
      <div class="row">
        <div class="col-3"></div>  
        <div class="col-6">
          <h2 id="horizontal-form">CMS Admin </h2>
          <form action="/cms/loginpost" method="post">
            <div class="row mb-3">
              <label for="username" class="col-sm-2 col-form-label">Username</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="username" name="username" value="admin">
              </div>
            </div>
            <div class="row mb-3">
              <label for="password" class="col-sm-2 col-form-label">Password</label>
              <div class="col-sm-10">
                <input type="password" class="form-control" id="password" name="password" value="123456">
              </div>
            </div>

            <button type="submit" class="btn btn-primary">Sign in</button>
          </form>
      
        </div>
 
      </div>
    </div>

  </body>
  </html>

2.1、 登录提交页面

   std::string articleloginpost(std::shared_ptr<httppeer> peer)
   {
      // step2
      // get login/login post field
      httppeer &client = peer->getpeer();
      std::string username = client.post["username"].to_string();
      std::string password = client.post["password"].to_string();

      auto users = orm::cms::User();
      std::string md5string;
 
      try
      {
         md5string = md5(password);
         users.where("name=", username).whereAnd("password=", md5string).limit(1).fetch();
         // view orm create sql
         // client<<"<p>"<<users.sqlstring<<"</p>";
         if (users.getUserid() > 0)
         {
            // save session,other page get  int userid= client.session["userid"].to_int();
            client.session["userid"] = users.getUserid();
            client.save_session();
            client.goto_url("/cms/list");
            return "";
         }
         else
         {
            client.goto_url("/cms/login",3,"用户名或密码错误!");
            return "";
         }
      }
      catch (std::exception &e)
      {
         client << "<p>" << e.what() << "</p>";
         return "";
      }

      return "";
   }

httppeer &client = peer->getpeer(); //为取得原始指针方便重载

username password 是view/login/login.html里面的表单字段

to_string 表示转为字符串

  std::string username = client.post["username"].to_string();
  std::string password = client.post["password"].to_string();

  auto users = orm::cms::User();
  std::string md5string;

orm::cms::User(); 表示 数据模型表

std::string md5string;

md5string = md5(password);

就是md5加密

users.where("name=", username).whereAnd("password=", md5string).limit(1).fetch();

数据模型表操作数据库,使用链操作where("name=", username) 和 where("name", username)一样,后面会讲怎么拼接,不过要注意安全,防sql注入。

我们可以观察 users.sqlstring 拼接好的字符串

users.data 是一个struct 数据对象,里面成员跟数据库字段名字一一对应

users.getUserid() 和users.data.userid 同样的,目前设计为public

数据库表名 users 里面必须有userid这个字段,最好每个表有一个自增字段

client.session["userid"] = users.getUserid();
client.save_session();

client.session["userid"]保存到文件里面,有一个sessionid自带的cookie,这样下一个页面就自动获取session内容了。也可以实现页面权限,如果其他页面没有取到这个值,表示没有登录或没有权限。

client.save_session();是保存动作

gotu_url是表示跳转这个页面。

2.2、文章内容列表页面

   std::string articlelist(std::shared_ptr<httppeer> peer)
   {
      // step3 content list
      httppeer &client = peer->getpeer();
      int userid = client.session["userid"].to_int();
      if (userid == 0)
      {
         // client.goto_url("/cms/login");
         client.val["msg"] = "<a href=\"/cms/login\">Please login </a>";
      }

      auto articles = orm::cms::Article();

      int page = client.get["page"].to_int();
      if (page < 0)
      {
         page = 0;
      }
      page = page * 20;
      articles.where("isopen=1").order(" aid desc ").limit(page, 20).fetch();
      // 也可以直接返回OBJ_VALUE 对象; 不过正常业务会要处理下结果集
      // You can also return the OBJ_VALUE object directly; but normal business process will need to process the result set
      client.val["list"].set_array();
      if (articles.size() > 0)
      {
         for (auto &bb : articles)
         {

            OBJ_ARRAY item;
            item["aid"] = bb.aid;
            item["title"] = bb.title;
            item["createtime"] = bb.createtime;
            item["summary"] = bb.summary;
            // client<<"<p><a href=\"/cms/show?id="<<bb.aid<<"\">"<<bb.title<<"</a>  "<<bb.createtime<<" </p>";
            client.val["list"].push(std::move(item));
         }
      }

      peer->view("cms/list");
      return "";
   }

client.session["userid"].to_int();

表示 上一个页面保存的userid 转为int类型

client.get["page"].to_int();

client.get 表示取得url地址的值 比如 /cms/list?page=2

client.get["page"].to_int();

那么就是 page等于2 表示第二页

articles.where("isopen=1").order(" aid desc ").limit(page, 20).fetch();

就是加上偏移20,每个页面20条内容标题,后面那个20表示在这个偏移上取回多少条内容

for (auto &bb : articles)

就是把 articles 取回的内容循环取出来处理,我们是显示到 视图里面。

client.val["list"].set_array();

保存到这个变量里面,是一个数组变量,视图里面再取这个值就可以了

3、crud完整内容说明

testcrud.cpp 完整代码

#include "orm.h"
#include <chrono>
#include <thread>
#include "md5.h"
#include "func.h"
#include "httppeer.h"
#include "testcrud.h"
namespace http
{

   std::string articlelogin(std::shared_ptr<httppeer> peer)
   {
      // step1  show login page
      peer->view("login/login");
      return "";
   }
   std::string articleloginpost(std::shared_ptr<httppeer> peer)
   {
      // step2
      // get login/login post field
      httppeer &client = peer->getpeer();
      std::string username = client.post["username"].to_string();
      std::string password = client.post["password"].to_string();

      auto users = orm::cms::User();
      std::string md5string;
 
      try
      {
         md5string = md5(password);
         users.where("name=", username).whereAnd("password=", md5string).limit(1).fetch();
         // view orm create sql
         // client<<"<p>"<<users.sqlstring<<"</p>";
         if (users.getUserid() > 0)
         {
            // save session,other page get  int userid= client.session["userid"].to_int();
            client.session["userid"] = users.getUserid();
            client.save_session();
            client.goto_url("/cms/list");
            return "";
         }
         else
         {
            client.goto_url("/cms/login",3,"用户名或密码错误!");
            return "";
         }
      }
      catch (std::exception &e)
      {
         client << "<p>" << e.what() << "</p>";
         return "";
      }

      return "";
   }
   std::string articlelist(std::shared_ptr<httppeer> peer)
   {
      // step3 content list
      httppeer &client = peer->getpeer();
      int userid = client.session["userid"].to_int();
      if (userid == 0)
      {
         // client.goto_url("/cms/login");
         client.val["msg"] = "<a href=\"/cms/login\">Please login </a>";
      }

      auto articles = orm::cms::Article();

      int page = client.get["page"].to_int();
      if (page < 0)
      {
         page = 0;
      }
      page = page * 20;
      articles.where("isopen=1").order(" aid desc ").limit(page, 20).fetch();
      // 也可以直接返回OBJ_VALUE 对象; 不过正常业务会要处理下结果集
      // You can also return the OBJ_VALUE object directly; but normal business process will need to process the result set
      client.val["list"].set_array();
      if (articles.size() > 0)
      {
         for (auto &bb : articles)
         {

            OBJ_ARRAY item;
            item["aid"] = bb.aid;
            item["title"] = bb.title;
            item["createtime"] = bb.createtime;
            item["summary"] = bb.summary;
            // client<<"<p><a href=\"/cms/show?id="<<bb.aid<<"\">"<<bb.title<<"</a>  "<<bb.createtime<<" </p>";
            client.val["list"].push(std::move(item));
         }
      }

      peer->view("cms/list");
      return "";
   }
   std::string articleshow(std::shared_ptr<httppeer> peer)
   {
      // step4
      httppeer &client = peer->getpeer();
      auto articles = orm::cms::Article();
      int aid = client.get["id"].to_int();

      articles.where("isopen=1").where(" aid=", aid).limit(1).fetch();

      client.val["title"] = articles.getTitle();
      client.val["content"] = articles.getContent();

      peer->view("cms/show");
      return "";
   }
   std::string articleedit(std::shared_ptr<httppeer> peer)
   {
      // same the show content
      httppeer &client = peer->getpeer();
      auto articles = orm::cms::Article();
      int aid = client.get["id"].to_int();

      articles.where("isopen=1").where(" aid=", aid).limit(1).fetch();

      client.val["title"] = articles.getTitle();
      client.val["content"] = html_encode(articles.getRefContent());
      client.val["aid"] = articles.getAid();
      peer->view("cms/edit");
      return "";
   }

   std::string articleeditpost(std::shared_ptr<httppeer> peer)
   {
      httppeer &client = peer->getpeer();
      std::string title = client.post["title"].to_string();
      std::string content = client.post["content"].to_string();
      unsigned int aid = client.post["aid"].to_int();

      auto articles = orm::cms::Article();
      // articles.where("isopen=1").where(" aid=",aid).limit(1).fetch();
      // articles.data.aid=aid;
      // articles.data.title=title;
      // articles.setAid(aid);
      articles.setTitle(title);
      // articles.setTitle("直接标题");
      articles.setContent(content);

      articles.where(" aid=", aid);
      int effectnum = 0;
      try
      {
         effectnum = articles.update("title,content");
      }
      catch (std::exception &e)
      {
         client << "<p>" << articles.sqlstring << "</p>";
         client << "<p>" << e.what() << "</p>";
         return "";
      }
      if (effectnum > 0)
      {

         client.goto_url("/cms/list", 3, "内容已经更新");
         return "";
      }
      else
      {
         client.goto_url("/cms/list", 3, "更新出错(error)");
         return "";
      }

      return "";
   }

   std::string articleadd(std::shared_ptr<httppeer> peer)
   {
      httppeer &client = peer->getpeer();
      peer->view("cms/add");
      return "";
   }
   std::string articleaddpost(std::shared_ptr<httppeer> peer)
   {
      httppeer &client = peer->getpeer();
      std::string title = client.post["title"].to_string();
      std::string content = client.post["content"].to_string();
      unsigned int aid = 0;

      auto articles = orm::cms::Article();

      // articles.data.aid=aid;
      // articles.data.title=title;
      // articles.setAid(aid);
      articles.setIsopen(1);
      articles.setCreatetime(date("%Y-%m-%d %X")); // Y-m-d H:i:s
      articles.setAddtime(timeid());               // unix timestamp
      articles.setAddip(client.client_ip);    // client ip
      articles.setTitle(title);
      // articles.setTitle("直接标题");
      articles.setContent(content);

      int effectnum = 0;
      try
      {
         effectnum = articles.save();
         aid = articles.getAid();
         client << "<p>新(new)id " << aid << " 或 新(new)id " << effectnum << "</p>";
      }
      catch (std::exception &e)
      {
         client << "<p>" << articles.sqlstring << "</p>";
         client << "<p>" << e.what() << "</p>";
         return "";
      }
      if (effectnum > 0)
      {

         client.goto_url("/cms/list", 3, "内容已经添加");
         return "";
      }
      else
      {
         client.goto_url("/cms/list", 3, "添加出错(error)");
         return "";
      }

      return "";
   }
   std::string articledelete(std::shared_ptr<httppeer> peer)
   {
      httppeer &client = peer->getpeer();
      unsigned int aid = client.get["id"].to_int();

      auto articles = orm::cms::Article();
      //  可以先查询是否存在或有权限之类
      // articles.where("isopen=1").where(" aid=",aid).limit(1).fetch();

      int effectnum = 0;
      try
      {
         effectnum = articles.remove(aid);
      }
      catch (std::exception &e)
      {
         client << "<p>" << articles.sqlstring << "</p>";
         client << "<p>" << e.what() << "</p>";
         return "";
      }
      if (effectnum > 0)
      {

         client.goto_url("/cms/list", 3, "内容已经删除");
         return "";
      }
      else
      {
         client.goto_url("/cms/list", 3, "删除出错(error)");
         return "";
      }

      return "";
   }
}

4、视图内容介绍

view/cms/list.html

视图文件内容,我们看看是怎么使用client.val["list"]的。


<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Hugo 0.101.0">
    <title>Content Management System</title>

    <link href="/assets/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body class="text-center">
    <%c include_sub("home/header",obj); %>
     <h3>内容列表 <a href="/cms/add">添加内容</a></h3>
     <p><%c echo<<obj["msg"].as_string(); %></p>
    <div class="list-group w-auto">
      <%c
      for(auto &a:obj["list"].as_array()){
      %>
      <div class="list-group-item list-group-item-action d-flex gap-3 py-3" aria-current="true">
        <img src="#" alt="twbs" class="rounded-circle flex-shrink-0" width="32" height="32">
        <div class="d-flex gap-2 w-100 justify-content-between">
          <div>
            <h6 class="mb-0"><a href="/cms/show?id=<%c echo<<a.second["aid"].to_string(); %>"><%c echo<<a.second["title"].as_string(); %></a></h6>
            <p class="mb-0 opacity-75"><%c echo<<a.second["summary"].as_string(); %></p>
          </div>
          <small class="opacity-50 text-nowrap"><%c echo<<a.second["createtime"].as_string(); %></small>

          <small class="opacity-50 text-nowrap"><a href="/cms/edit?id=<%c echo<<a.second["aid"].to_string(); %>">编辑</a></small>
          <small class="opacity-50 text-nowrap"><a href="/cms/delete?id=<%c echo<<a.second["aid"].to_string(); %>">删除</a></small>
        </div>
      </div>
      <%c
      }
      %>  
       
    </div>

  </body>
  </html>

for(auto &a:obj["list"].as_array())

视图这里也是一个循环把值拿出来。

给一个a临时变量引用,然后循环一条一条显示出来

a.second["aid"].to_string()

是把数字转为字符

其它内容看看是不是有不明白的地方,可以提问。

5、函数注册

别忘了,我们要做一个urlpath映射挂载

我们编辑挂载文件

common/reghttpmethod.hpp

#ifndef __HTTP_REGHTTPMETHOD_HPP
#define __HTTP_REGHTTPMETHOD_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "httppeer.h"
#include "testcrud.h"

namespace http
{
  void _inithttpmethodregto(std::map<std::string, regmethold_t> &methodcallback)
  {
    struct regmethold_t temp;
    temp.pre = nullptr;

    temp.regfun = articlelogin;
    methodcallback.emplace("cms/login", temp); 

    temp.regfun = articleloginpost;
    methodcallback.emplace("cms/loginpost", temp); 

    temp.regfun = articlelist;
    methodcallback.emplace("cms/list", temp); 

    temp.regfun = articleshow;
    methodcallback.emplace("cms/show", temp); 

    temp.regfun = articleedit;
    methodcallback.emplace("cms/edit", temp); 

    temp.regfun = articleeditpost;
    methodcallback.emplace("cms/editpost", temp); 

    temp.regfun = articledelete;
    methodcallback.emplace("cms/delete", temp); 

    temp.regfun = articleadd;
    methodcallback.emplace("cms/add", temp); 

    temp.regfun = articleaddpost;
    methodcallback.emplace("cms/addpost", temp); 

  }

}
#endif

记得包含文件 #include "testcrud.h"

你有其他挂载,可以在里面添加,不要跟我一样也行。

我们把所有函数都映射上去。

6、编译

一切准备就绪了,我们开始编译

回到项目根目录进入build目录

cmake ..

make

然后在回到根目录,或打开新的命令窗口

执行

./bin/paozhu

用浏览器打开

http://localhost/cms/list

可以看到视图内容了 cmspreview