C++网站开发MVC框架TreeFrog Framework教程——3.代码解析

上篇博客的地址

1.控制器

让我们来看看自动生成的控制器的代码内容,这是根据URL调用对应的处理函数(Routing)的必要组件。首先看一下头文件,主要内容就是在public slots这一部分,写下你的URL分发函数。顺带一提,slots关键字属于QT的扩展功能,有兴趣的同学可以详细了解。

class T_CONTROLLER_EXPORT BlogController : public ApplicationController
{
    Q_OBJECT
public:
    Q_INVOKABLE
    BlogController() { }
    BlogController(const BlogController &other);

public slots:
    void index();// 展示全部文章
    void show(const QString &id);// 展示指定的一篇文章
    void create();// 新建一篇文章
    void save(const QString &id);// 更新一篇博客
    void remove(const QString &id);// 删除一篇文章
};

T_DECLARE_CONTROLLER(BlogController, blogcontroller)//一个比较难懂的宏定义,先别深究

接着我们看源文件的内容,这个稍微有点长,大概100行的样子。

#include "blogcontroller.h"
#include "blog.h"

BlogController::BlogController(const BlogController &)
    : ApplicationController()
{ }

void BlogController::index()
{
    auto blogList = Blog::getAll();//取得一个存放有所有Blog模型对象的列表
    texport(blogList);//把这个列表传到View层
    render();//重新生成View层(模板)
}

void BlogController::show(const QString &id)
{
    auto blog = Blog::get(id.toInt());//根据主键取得对应的Blog模型对象
    texport(blog);
    render();
}

void BlogController::create()
{
    switch (httpRequest().method()) {  //httpRequest方法的类型
    case Tf::Get:
        render();
        break;

    case Tf::Post: {
        auto blog = httpRequest().formItems("blog");//将传过来的名为“blog”的表单的参数保存在blog(QVariantMap类型)
        auto model = Blog::create(blog);//根据blog这个QVariantMap创建Blog模型对象
        if (!model.isNull()) {
            QString notice = "Created successfully.";
            tflash(notice);//设置flash消息
            redirect(urla("show", model.id()));//转发给show这个处理函数
        } else {
            QString error = "Failed to create.";//对象生成失败
            texport(error);
            texport(blog);
            render();
        }
        break; 
    }

    default:
        renderErrorResponse(Tf::NotFound);// 生成错误信息页面
        break;
    }
}

void BlogController::save(const QString &pk)
{
    switch (httpRequest().method()) {
    case Tf::Get: {
        auto model = Blog::get(id.toInt());//根据主键取得对应的Blog模型对象
        if (!model.isNull()) {
            session().insert("blog_lockRevision", model.lockRevision());//设置乐观锁的版本号
            auto blog = model.toVariantMap();
            texport(blog);
            render();
        }
        break; 
    }

    case Tf::Post: {
        QString error;
        int rev = session().value("blog_lockRevision").toInt();//取得乐观锁的版本号
        auto model = Blog::get(id.toInt(), rev);//根据主键和乐观锁的版本号取得对应的blog模型对象
        
        if (model.isNull()) {
            error = "Original data not found. It may have been updated/removed by another transaction.";
            tflash(error);
            redirect(urla("save", id));
            break;
        }

        auto blog = httpRequest().formItems("blog");
        model.setProperties(blog);//设置被请求的数据
        if (model.save()) {//保存对象(持久化)
            QString notice = "Updated successfully.";
            tflash(notice);
            redirect(urla("show", model.id()));//转发给show这个处理函数
        } else {
            error = "Failed to update.";
            texport(error);
            texport(blog);
            render();
        }
        break; 
    }

    default:
        renderErrorResponse(Tf::NotFound);
        break;
    }
}

void BlogController::remove(const QString &id)
{
    if (httpRequest().method() != Tf::Post) {
        renderErrorResponse(Tf::NotFound);
        return;
    }

    auto blog = Blog::get(id.toInt());//根据主键取得对应的Blog模型对象
    blog.remove();//删除
    redirect(urla("index"));
}

// Don't remove below this line
T_REGISTER_CONTROLLER(blogcontroller)//一个比较难懂的宏定义,先别深究

2.视图

TreeFrog中,有两种模板系统可供选择:ERB和独有的Otama。我们可以在配置文件development.ini中通过设置TemplateSystem参数的值来切换这两个模板系统。

TemplateSystem=ERB
或者是
TemplateSystem=Otama

2.1.ERB模板系统

如果你用过Ruby Rails,那么肯定对ERB有所了解,它可以在HTML文件里面嵌入逻辑代码。
框架默认生成的是ERB文件,让我们先看一下index.erb的内容。上面控制器中index函数调用的render方法就是返回index.erb文件的内容作为响应。<%...%>里面的代码就是C++的。(第一次看到C++还能像脚本文件那样写)

<!DOCTYPE HTML>
<%#include "blog.h" %>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title><%= controller()->name() + ": " + controller()->activeAction() %></title>
</head>
<body>
<h1>Listing Blog</h1>

<%== linkTo("New entry", urla("entry")) %><br />
<br />
<table border="1" cellpadding="5" style="border: 1px #d0d0d0 solid; border-collapse: collapse;">
  <tr>
    <th>ID</th>
    <th>Title</th>
    <th>Body</th>
  </tr>
<% tfetch(QList<Blog>, blogList); %>
<% for (const auto &i : blogList) { %>
  <tr>
    <td><%= i.id() %></td>
    <td><%= i.title() %></td>
    <td><%= i.body() %></td>
    <td>
      <%== linkTo("Show", urla("show", i.id())) %>
      <%== linkTo("Edit", urla("save", i.id())) %>
      <%== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')") %>
    </td>
  </tr>
<% } %>
</table>

2.2.Otama模板源文件

接着,我们看下Otama模板系统中HTML模板的代码,它有个明显的特点:只有纯粹的HTML元素,没有嵌入其他类似于<%...%>的逻辑代码,取而代之的是往各个网页元素标签添加新的属性data-tf作为otm文件识别的标记。

<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title data-tf="@head_title"></title>
</head>
<body>
<h1>Listing Blog</h1>
<a href="#" data-tf="@link_to_entry">New entry</a><br />
<br />
<table border="1" cellpadding="5" style="border: 1px #d0d0d0 solid; border-collapse: collapse;">
  <tr>
    <th>ID</th>
    <th>Title</th>
    <th>Body</th>
    <th></th>
  </tr>
  <tr data-tf="@for">                  ←声明一个名为for的标记
    <td data-tf="@id"></td>
    <td data-tf="@title"></td>
    <td data-tf="@body"></td>
    <td>
      <a data-tf="@linkToShow">Show</a>
      <a data-tf="@linkToEdit">Edit</a>
      <a data-tf="@linkToRemove">Remove</a>
    </td>
  </tr>
</table>
</html>

2.3.otm文件

然后我们看一下index.otm是如何把HTML模板和逻辑代码联系起来的。

#include "blog.h"← 加载blog.h这个头文件
@head_title ~= controller()->controllerName() + ": " + controller()->actionName()←将参数从控制器传到指定标记的元素

@for :
tfetch(QList<Blog>, blogList);//接收在控制器中用texport()方法传过来的同名变量
for (QListIterator<Blog> it(blogList); it.hasNext(); ) {
    const Blog &i = it.next();//i就是Blog对象的引用
    %%//开始循环,循环范围为带有for标记元素的所有包含子元素
    }

@id ~= i.id()//往带有@id 标记的元素中插入 i.id()的内容

@title ~= i.title()//同上

@body ~= i.body()//同上

@linkToShow :== linkTo("Show", urla("show", i.id()))//将带有@linkToShow 标记的元素整体替换为linkTo()函数的返回值

@linkToEdit :== linkTo("Edit", urla("edit", i.id()))//同上

@linkToRemove :== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')")//同上

上面有几个运算符需要说明一下:

  1. ~:设置被标记容器的内容;
  2. =:进行HTML转义;
  3. ~=:先进行HTML转义,然后再赋值为被标记元素的内容;
  4. ~==:不进行转义,直接设置被标记容器的内容;
  5. ::直接将标记元素整体替换;
  6. :==:不进行转义,将标记元素整体替换;

2.4.如何传参

实现起来很简答,代码大概就是像下面这样子的:

#在控制器中:
	int foo;
	foo = ...
	texport(foo);
#在otm文件中:
	tfetch(int, foo);

TreeFrog框架会把HTML模板文件和otm文件都转换成C++代码然后编译,这样整个模板就是一个库文件,执行起来相当高效。

3.模型和ORM

可能在其他框架中模型对象和ORM对象是同一个概念,但在TreeFrog中,它们两者有些细微的区别,不妨理解为模型对象是ORM对象的一个小小的包装器。

3.1.ORM

TreeFrog中,内嵌有一个名为SqlObjectO/R映射器。下面我们来看看框架自动生成的SqlObject文件blogobject.h的内容,里面声明了一个名为BlogObjectORM对象,我们重点关注与数据库中的各数据列相对应的各个成员变量被声明为什么类型。

class T_MODEL_EXPORT BlogObject : public TSqlObject, public QSharedData
{
public:
    int id {0};
    QString title;
    QString body;
    QDateTime created_at;
    QDateTime updated_at;
    int lock_revision {0};

    //声明各个数据列的枚举值
    enum PropertyIndex {
        Id = 0,
        Title,
        Body,
        CreatedAt,
        UpdatedAt,
        LockRevision,
    };
    //返回主键对应数据列的枚举值
    int primaryKeyIndex() const override { return Id; }
    int autoValueIndex() const override { return Id; }
    QString tableName() const override { return QLatin1String("blog"); }

private:    /*** Don't modify below this line ***/      // 下面都是些比较难懂的宏定义
    Q_OBJECT
    Q_PROPERTY(int id READ getid WRITE setid)
    T_DEFINE_PROPERTY(int, id)
    Q_PROPERTY(QString title READ gettitle WRITE settitle)
    T_DEFINE_PROPERTY(QString, title)
    Q_PROPERTY(QString body READ getbody WRITE setbody)
    T_DEFINE_PROPERTY(QString, body)
    Q_PROPERTY(QDateTime created_at READ getcreated_at WRITE setcreated_at)
    T_DEFINE_PROPERTY(QDateTime, created_at)
    Q_PROPERTY(QDateTime updated_at READ getupdated_at WRITE setupdated_at)
    T_DEFINE_PROPERTY(QDateTime, updated_at)
    Q_PROPERTY(int lock_revision READ getlock_revision WRITE setlock_revision)
    T_DEFINE_PROPERTY(int, lock_revision)
};

3.2.模型

Blog模型的代码,声明了一个名为Blog的模型对象,里面其实就是各个成员属性的gettersetter,还有增删查改对应的接口函数。注意理解“模型对象是ORM对象的一个小小的包装器”这句话的含义。

class T_MODEL_EXPORT Blog : public TAbstractModel
{
public:
    Blog();
    Blog(const Blog &other);
    Blog(const BlogObject &object);  //封装ORM对象,生成对应的模型对象
    ~Blog();

    int id() const;     //下面就是一堆getter和setter
    QString title() const;
    void setTitle(const QString &title);
    QString body() const;
    void setBody(const QString &body);
    QDateTime createdAt() const;
    QDateTime updatedAt() const;
    int lockRevision() const;
    Blog &operator=(const Blog &other);

    bool create() { return TAbstractModel::create(); }
    bool update() { return TAbstractModel::update(); }
    bool save()   { return TAbstractModel::save(); }
    bool remove() { return TAbstractModel::remove(); }

    static Blog create(const QString &title, const QString &body);//增
    static Blog create(const QVariantMap &values);// values这个哈希表中存有属性名——属性值的键值对
    static Blog get(int id);//根据主键id查
    static Blog get(int id, int lockRevision);//根据主键id和乐观锁版本号查
    static int count();//一共有多少篇博客文章
    static QList<Blog> getAll();//返回所有博客文章(一个个原生对象)
    static QJsonArray getAllJson(); //返回所有博客文章(一个个JSON字符串)

private:
    QSharedDataPointer<BlogObject> d;   //对应ORM对象的指针,所以说模型对象是ORM对象的一个小小的包装器
    TModelObject *modelData();
    const TModelObject *modelData() const;
};

Q_DECLARE_METATYPE(Blog)    //一个比较难懂的宏定义,不必深究
Q_DECLARE_METATYPE(QList<Blog>)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值