作者:糖果

ORC是是用LOR框架完成的,这次尝试实现用Lapis来实现,ORC索引页面的显示, 国为ORC是后端前端分开的, 实际上后端只要根据用输入返回按接口定义的JSON数据就好。

这个实验会涉及到几个最学用的点:

1.简单的用SQL对业务表进行left join,这个和语言平台无关。
2.OR的LUA框架返回JSON, JSON数据的编解码。
3.常规不使用ORM访问数据库。
4.Lapis模板,在模板中进行子模板渲染。
local lapis = require "lapis"
local app = lapis.Application()
local db = require("lapis.db")
app:enable("etlua")


app:get("/topics/all", function(self)
    page_no = 1
    page_size = 10
    category = 3  
    local sql = "select t.*, c.name as category_name, u.avatar as avatar from topic t " ..
                                "left join user u on t.user_id=u.id " ..
                                "left join category c on t.category_id=c.id " ..
                                "where t.category_id=1 "

    local res, err = db.query(sql)
    return { json = { data = {totalCount="54", currentPage="1",topics = res, totalPage=2}, success=true }}
end)

return app

值的注意的是,db.query(sql)返回的结果res本身就是JSON形式,不需要进入JSON编解码。 因为LORj框架是不提供ORM的,所以ORC所有返回接口几乎都是纯SQL。 因为Lapis的底层用的也是resty-mysql
,他们之间执行SQL是兼容的。

下面是节选的Lapis的Mysql库的源码:

    local mysql = require("resty.mysql")
    return function(q)
      if logger then
        logger.query(q)
      end
      local db = ngx and ngx.ctx.resty_mysql_db
      if not (db) then
        local err
        db, err = assert(mysql:new())
        db:set_timeout(timeout)
        local options = {
          database = database,
          user = user,
          password = password,
          ssl = ssl,
          ssl_verify = ssl_verify
        }
        if path then
          options.path = path
        else
          options.host = host
          options.port = port
        end
        assert(db:connect(options))
        if ngx then
          ngx.ctx.resty_mysql_db = db
          after_dispatch(function()
            return db:set_keepalive(max_idle_timeout, pool_size)
          end)
        end
      end
      local start_time
      if ngx and config.measure_performance then
        ngx.update_time()
        start_time = ngx.now()
      end
      local res, err, errcode, sqlstate = assert(db:query(q))
      local result
      if err == 'again' then
        result = {
          res
        }
        while err == 'again' do
          res, err, errcode, sqlstate = assert(db:read_result())
          table.insert(result, res)
        end
      else
        result = res
      end
      if start_time then
        ngx.update_time()
        increment_perf("db_time", ngx.now() - start_time)
        increment_perf("db_count", 1)
      end
      return result
    end
  end
}

ORC的前端代码,除了其它和这个实验不相关的Layout,主要的代码如下:

meta.etlua

<script src="/static/js/juicer.js"></script>

图像说明文字


<% render("views.meta") %>

<script src="/static/js/index.js"></script>

这里的index.js的代码也不是很多:

(function (L) {
    var _this = null;
    L.Index = L.Index || {};
    _this = L.Index = {
        data: {
            current_category: "0"
        },

        init: function (current_category) {
            _this.data.current_category = current_category || "0";
            _this.loadTopics("default");
            //_this.loadTopics("ir-black");
            _this.initEvents();
        },

/*
        scrollTop: function () {
            $('html, body').animate({scrollTop: 0}, 0);
        },
*/

        initEvents: function () {
            $(document).on("click", "#topic-type-tab a", function () {
                $("#topic-type-tab a").each(function () {
                    $(this).removeClass("active");
                });

                $(this).addClass("active");
            });

            $("#default-topics-btn").click(function () {
                _this.loadTopics("default");
            });

            $("#recent-reply-topics-btn").click(function () {
                _this.loadTopics("recent-reply");
            });

            $("#good-topics-btn").click(function () {
                _this.loadTopics("good");
            });
            
            
            $("#noreply-topics-btn").click(function () {
                _this.loadTopics("noreply");
            });
        },

        loadTopics: function (type, pageNo) {
            pageNo = pageNo || 1;
            $.ajax({
                url: '/topics/all',
                type: 'get',
                cache: false,
                data: {
                    page_no: 1,
                    type: type,
                    category: _this.data.current_category
                },
                dataType: 'json',
                success: function (result) {
                    if (result.success) {
                        if (!result.data || (result.data && result.data.topics.length <= 0)) {
                            $("#topics-body").html('<div class="alert alert-info" role="alert">此分类下没有任何内容</div>');
                        } else {
                            _this.page(result, type, 1);
                        }
                    } else {
                        $("#topics-body").html('<div class="alert alert-danger" role="alert">' + result.msg + '</div>');
                    }
                },
                error: function () {
                    $("#topics-body").html('<div class="alert alert-danger" role="alert">error to send request.</div>');
                }
            });
        },
        page: function (result, type, pageNo) {
            var data = result.data || {};
            var $container = $("#topics-body");
            $container.empty();

            var tpl = $("#topic-item-tpl").html();
            var html = juicer(tpl, data);
            $container.html(html);

            var currentPage = data.currentPage;
            var totalPage = data.totalPage;
            var totalCount = data.totalCount;

            if (totalPage > 1) {
                $("#pagebar").show();
                $.fn.jpagebar({
                    renderTo: $("#pagebar"),
                    totalpage: totalPage,
                    totalcount: totalCount,
                    pagebarCssName: 'pagination2',
                    currentPage: currentPage,
                    onClickPage: function (pageNo) {
                        $.fn.setCurrentPage(this, pageNo);
                        $.ajax({
                            url: '/topics/all',
                            type: 'get',
                            cache: false,
                            data: {
                                page_no: pageNo,
                                type: type,
                                category: _this.data.current_category
                            },

                            dataType: 'json',
                            success: function (result) {
                                var data = result.data || {};
                                var $container = $("#topics-body");
                                $container.empty();

                                var tpl = $("#topic-item-tpl").html();
                                var html = juicer(tpl, data);
                                $container.html(html);
                               // _this.scrollTop();
                            },
                            error: function () {
                                $("#topics-body").html('<div class="alert alert-danger" role="alert">error to find topics page.</div>');
                            }
                        });
                    }
                });
            } else {
                $("#pagebar").hide();
            }
        }
    };
}(APP));

ORC并没有使用传统的框架分页器对象,结合框架模板语言来实现分页,LOR框架也暂时不提供这些功能。用的是Juicer前端模板来实现
把后端返回的JSON数据的字段,渲染到前端的Layout标签中。

var tpl = $("#topic-item-tpl").html();
var html = juicer(tpl, data);
$container.html(html);

juicer把data数据提供给了topic-item-tpl这个标签类,并在前端页面嵌入了 {@each topics as t}语法,取得JSON数据的值, t.title, t.avatar等。

上面这段代码,把后端返回JSON数据返回在前端渲染,主要靠的是juicer。按目前ORC这种写法, 前后端都是通过JSON数据建立联系,ORM,Etlua标记,分页器这种中间模块都没用到。下面的实验就是把这些功能,应用到ORC上。

PS:
ORC=Openrest China

下一篇实验是对ORC的内容页进行Lapis的代码实现移植。