文档章节

jFinal开发规范

XuYuan
 XuYuan
发布于 2016/04/14 15:18
字数 1705
阅读 213
收藏 2

jFinal 开发规范

概述

本文档用于说明使用jFinal进行项目开发时,应该遵循的规范与惯用法等内容。

开发流程

虽然我们期望数据库能够在设计阶段即可冻结,但往往要到开发的后期。这就势必涉及到数据库的更新与jFinal模型更新如何同步的问题。推荐做法如下:

数据库设计 => 生成sql => 更新数据库 => 更新模型

需要说明的是.sql文件也要纳入到版本管理系统中进行管理。

代码组织

jFinal是一个标准的MVC框架,在使用其进行开发时,也应该按照MVC的结构进行组织。

按照“先分功能,后分模块”的思路,给出了推荐的一个代码组织框架:

ProjectRoot
    +----src
    |    +----package
    |         Config.java
    |         ModelGenerator.java
    |         +----package.models
    |         |    +----package.models.base
    |         |    +----packages.models.validators
    |         +----package.controllers.moduleName
    |         +----package.controllers.moduleName.admin
    +----sql
    +----web
    |    +----templates
    |    |    +----include
    |    |    +----global
    |    |    +----modelName
    |    |    +----admin
    |    |         +----include
    |    |         +----modelName
    |    |         admin_base.ftl
    |    |    base.ftl
    |    +----static
    |    |    +----css
    |    |    +----javascript
    |    |    +----zui
    |    +----resources
    |    |    db.properties
    |    |    log4j.properties
    |    |    config.properties
    |    +----WEB-INFO
    |         +----classes
  • package :项目的顶层包
  • packages.models 模型定义。模型定义包含整个项目的完整模型定义,未区分模块。
  • packages.models.base 存放自动生成的模型基类
  • packages.models.validators 模型验证定义
  • packages.controllers.moduleName 存放指定模块的前台控制器。如用户验证与鉴权的模块auth。
  • package.controllers.moduleName.admin 存放模块的后台管理功能
  • web 前端文件的根目录
  • web.templates 存放所有模板
  • web.templates.moduleName 存放指定模块的模板
  • web.templates.include 模板公用片段
  • web.templates.global 全局静态文件。比如:关于我们,版权声明等公用性内容
  • web.templates.base.ftl 前端模板的基模板
  • web.templates.admin 后台管理模板。
  • web.static 静态文件根目录
  • web.resources 配置文件根目录
  • web.resources 资源文件根目录

模型定义

虽然jFinal支持的ActiveRecord模式进行开发。但是其对ActiveRecord的模式非常基础,为了更好地规范化代码组织,我们需要更加细化的要求。

基本原则:

  • 封装与内聚原则。与模型相关的所有操作,均要封装到模型中。原则上不允许在模型之外编写SQL。
  • 模型"可导航"原则。一对多,多对多模型,需要在模型中导航到关联模型的方法,便于前台程序编写。详细内容可参阅下文的“关联”部分。

关联

一对多关联

  • 在"一"的一方,创建getChildModelList方法。
  • 在"多"的一方,创建getParent方法。

示例:

public class Column extends Model{
  public finial static Column dao = new Column();
  public List<Article> getArticleList(){
    return Article.dao.findByColumnId(getId());
  }
}

public class Article extends Model{
  public final static Article dao = new Article();
  public Column getColumn(){
    return Column.dao.fineById(getColumnId());
  }

  public List<Article> findByColumnId(int columnId){
    ....
  }
}

每个项目中都包含一个ModelGenerator类,用于根据数据库的变化更新模型。生成规则请参照上面的代码组织。

特别说明: 数据库字段名称,一定要采用驼峰形式。

参阅文献:《jFianl-2.2-manual.pdf》第0章 快速体验Model与Bean合体

模型验证

  • 原则上说,应该为每一个模型创建一个模型验证类。
  • 验证失败时,采用 “字段+Error” 作为errorKey,方便后续错误显示。参见后文。

一个验证器的例子:

/**
 * 栏目校验
 * Created by YuanXu on 2016/4/12.
 */
public class ColumnValidator extends Validator {
    @Override
    protected void validate(Controller c) {
        validateRequired("column.title", "titleError", "请输入栏目名称");
        validateRequired("column.slug", "slugError", "请输入Slug");
        String slug = c.getPara("column.slug");
        Integer id = c.getParaToInt("column.id");
        if (slug != null && !slug.isEmpty()) {
            if (id == null || id == 0) { //new
                if (Column.dao.find("select id from cms_column where slug=?", slug).size() > 0) {
                    addError("slugError", "Slug不能重复!");
                }
            } else {
                if (Column.dao.find("select id from cms_column where slug=? and id !=?", slug, id).size() > 0) {
                    addError("slugError", "Slug不能重复!");
                }
            }
        }
    }

    @Override
    protected void handleError(Controller c) {
        c.keepModel(Column.class);
        c.renderFreeMarker("/templates/cms/admin/column_form.ftl");
    }
}

Contorller

常规的数据处理控制器应该包含index、create、update、save、delete几个方法。分别用于列表、创建的空白表单渲染、更新表单渲染、数据保存和删除动作。

代码示例

public class UserController extends Controller{

  /**
  * 显示数据列表
  */
  public void index(){
      int page = getParaToInt("page", 1);
      Page<Model> page_obj = Article.dao.paginate(page, PAGE_SIZE,.....);
      setAttr("page_obj",page_obj);
      setAttr("object_list",page_obj.get_list());
      renderFreemarker("/templates/module/model_list.ftl");
  }

  /**
  * 新建数据
  */
  public void create(){
    Model obj = new Model();
    setAttr("object",obj);
    //TODO:新建模型所需其他数据
    renderFreemarker("/templates/module/model_form.ftl");
  }

  /**
  * 更新数据
  */
  public void update(){
    Model obj = Model.dao.findById(getParam("id"));
    if (obj == null){
      renderError(404);
      return;
    }
    setAttr("object",obj);
    //TODO:更新模型所需其他数据
    renderFreemarker("/templates/module/model_form.ftl");
  }

  /**
  * 保存数据
  */
  public void save(){
    Model obj = getModel(Model.class);
    if (obj.getID() == null)
      obj.save();
    else
      obj.update();
    redirect(""); #TODO:转到列表页
  }
}

模板

我们使用freemarker作为模板层,并且尽量避免在模板中书写逻辑代码。 通过定义一个基类模板,在基模板中定义可被重写的“片段”。子模版引入(include)基类模板,并且通过宏的形式重写/替换基类模板中的占位符。 为了方便区分html标记与模板标记,所有模板采用方括号形式。这就要求没个模板的第一行写入[#ftl]

base.ftl

这里给出一个基类模板的了例子:

[#ftl]
<html>
<head>
    <title>[#if title??] [@title /][/#if]</title>
    <link rel="stylesheet" href="/static/zui/css/zui.css">
    <script src="/static/zui/lib/jquery/jquery.js"></script>
    <script src="/static/zui/js/zui.js"></script>
[#if head_css??]
    [@head_css][/@head_css]
[/#if]
[#if head_js??]
    [@head_js][/@head_js]
[/#if]
</head>
<body class="[#if body_css??][@body_css][/@body_css] [/#if]">
<div class="container">
[#include '/templates/cms/admin/admin_nav.ftl' ]
[#if main??]
    [@main][/@main]
[/#if]
</div>
[#if body_js??]
    [@body_js][/@body_js]
[/#if]
</body>
</html>

子模板

同样给出一个子模版的例子。

[#ftl]
[#assign active='column' /]
[#include "/templates/admin_base.ftl" ]
[#macro body_css]column[/#macro]]
[#macro title]栏目管理[/#macro]
[#macro main]
<form action="" class="form form-horizontal" method="get">
    <a href="/admin/cms/column/create">新建栏目</a>
</form>
<table class="table table-bordered table-hovered">
    <tr>
        <th>编号</th>
        <th>slug</th>
        <th>标题</th>
        <th>创建时间</th>
        <th>更新时间</th>
        <th>动作</th>
    </tr>
    [#if column_list??]
        [#list column_list as column ]
            <tr>
                <td>${column_index+1}</td>
                <td>${column.slug!}</td>
                <td>${column.title!}</td>
                <td>${column.createAt!}</td>
                <td>${column.updateAt!}</td>
                <td><a href="/admin/cms/column/update?id=${column.id}" class="btn btn-default btn-sm"><i
                        class="icon-edit">修改</i></a>
                    <a href="javascript:void(0)" data-id="${column.id}" class="btn btn-delete btn-default btn-sm"><i
                            class="icon-remove">删除</i></a>
                </td>
            </tr>
        [/#list]
    [/#if]
</table>

[/#macro]
[#macro body_js]
<script src="/static/javascript/dibo.admin.article.js"></script>

[/#macro]

表单处理

推荐采用PRG模式进行处理。可参阅http://m.oschina.net/blog/143120 表单处理需要结合验证器中的错误处理组织。一般来说,需要采用如下模式:

...
<input type="hidden" name="column.id" value="${column.id!}">
...
<div class="form-group [#if titleError??]has-error[/#if]">
        <label for="id_title" class="col-md-3 control-label">标题</label>
        <div class="col-md-9 ">
            <input type="text" class="form-control" name="column.title" id="id_title" required maxlength="50"
                   value="${column.title!}">
            [#if titleError??]
                <p class="help-block">${titleError}</p>
            [/#if]
        </div>
</div>
...

常用基础设施

zui自定义标签[开发中]

主要目的:根据模型和校验结果,生成模板。

  • [@zui_form 表单]
  • [@zui_field 字段名 cssClass="" fieldClass="" /]
  • [@zui_pagination page_obj]

特别期望的特性

  • 更加完善的ORM。
    • 分离模型定义与操作。
    • 避免SQL. 采用Django类似的方式,函数式风格,惰性计算,尽量避免直接编写。
    • 通过模型维护数据库的scheme,模型变更纳入版本控制
  • form
  • url支持命名与命名空间。通过resolve_url函数解析地址,避免url硬编码。

© 著作权归作者所有

XuYuan
粉丝 14
博文 24
码字总数 12461
作品 0
石家庄
架构师
私信 提问
加载中

评论(2)

viqbgrg
viqbgrg
赞一个,写的很好
天赐绝尘
天赐绝尘
what
【方向讨论】以Java技术培训,辐射到各个方向

官网建设,宣传,吸引更多用户 视频教程 教学过程中,完成对社区、博客、微信的开发维护,开源吸引更多用户 JFinal周边插件开发 辐射到Spring boot、Docker教学 推荐优秀学员到牛叉的公司~ 我...

如梦技术
2015/06/26
5
0
学JFinal不迷路,JFinal优质资源列表(欢迎反馈更新)

学JFinal不迷路,记录一下JFinal相关的资源、产品、讲师等信息(所有信息排名不分先后)。 一、相关站点: 1、JFinal官网-问答、分享、文档、交流、俱乐部 http://www.jfinal.com 2 、JFina...

山东-小木
05/25
286
0
JFinal单元测试,发现和MockMVC不太一样,没有断言等方法

最近公司在推行单元测试规范,项目中用到了JFinal、SpringMVC等框架,SpringMVC中集成的MockMVC可以断言不用通过执行 System.out.println 来查看测试结果。 JFinal单元测试参照了以下文章: ...

zhulint
2017/10/25
131
0
JFinal Weixin 2.3 发布,支持微信小程序开发

jfinal weixin 项目早在五年前就发布了第一个版本,是老牌的微信公众号开发 SDK,已稳定、可靠服役多年。因为极简设计、良好的开发体验,所以深受开发者的喜爱。 五年来 jfinal weixin 一直紧...

JFinal
02/01
2.1K
6
建议以依赖的方式,而非copy的方式使用jfinal代码

Springblade 的技术组合是 spring+springmvc+beetl+beetlsql+shiro,刚看了一下源码,其中大量直接 copy 的 jfinal 源码,例如 render 模块、json 模块相当于整模块原封不动地 copy 使用。项...

JFinal
2016/09/05
1K
15

没有更多内容

加载失败,请刷新页面

加载更多

《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
今天
6
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
今天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
今天
5
0
OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
1K
11
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
40
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部