ORC索引页显示的Lapis实现
作者:糖果
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的代码实现移植。