jFinal開發規範

jFinal 開發規範

概述

本文檔用於說明使用jFinal進行項目開發時,應該遵循的規範與慣用法等內容。javascript

開發流程

雖然咱們指望數據庫可以在設計階段便可凍結,但每每要到開發的後期。這就勢必涉及到數據庫的更新與jFinal模型更新如何同步的問題。推薦作法以下:css

數據庫設計 => 生成sql => 更新數據庫 => 更新模型html

須要說明的是.sql文件也要歸入到版本管理系統中進行管理。前端

代碼組織

jFinal是一個標準的MVC框架,在使用其進行開發時,也應該按照MVC的結構進行組織。java

按照「先分功能,後分模塊」的思路,給出了推薦的一個代碼組織框架:jquery

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的模式很是基礎,爲了更好地規範化代碼組織,咱們須要更加細化的要求。web

基本原則:sql

  • 封裝與內聚原則。與模型相關的全部操做,均要封裝到模型中。原則上不容許在模型以外編寫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類,用於根據數據庫的變化更新模型。生成規則請參照上面的代碼組織。bash

特別說明: 數據庫字段名稱,必定要採用駝峯形式。

參閱文獻:《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硬編碼。
相關文章
相關標籤/搜索