jfinal

1      jfinal cms简介

1      jfinal cms简介

         jfinal cms,采用了简洁强大的JFinal作为web框架,模板引擎用的是beetl,数据库用mysql,前端bootstrap框架。 支持多站点、oauth2认证、帐号注册、密码加密、评论及回复,消息提示,网站访问量统计,文章评论数和浏览量统计,回复管理,支持权限管理。

后台模块包含:栏目管理,栏目公告,栏目滚动图片,文章管理,回复管理,意见反馈,我的相册,相册管理,图片管理,专辑管理、视频管理、缓存更新,友情链接,访问统计,联系人管理,模板管理,组织机构管理,用户管理,角色管理,菜单管理,数据字典管理,站点管理。

         建站截图:


 

2      jfinal cms部署与配置

2.1    maven方式部署配置

maven部署方式的好处在于不依赖于任何IDE,并且跨平台。

1)        下载项目源代码,安装jdk、maven、mysql。

2)        在项目目录下运行mvn install,提示BUILD SUCCESS即可。

3)        创建mysql用户和数据库,运行/jfinal_cms/sql下对应jfinal_cms_v4.sql。

4)        数据库配置文件:/jfinal_cms/src/main/resources/conf/db.properties,修改相应配置即可。

db_type=mysql

 

mysql.jdbcUrl =jdbc:mysql://127.0.0.1:3306/jfinal_cms2?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull

mysql.user = root

mysql.password = 123456

mysql.driverClass = com.mysql.jdbc.Driver

5)        运行:mvn tomcat:run。看到“系统启动完成”说明运行成功。

6)        网站效果访问:http://127.0.0.1/jfinal_cms。

7)        管理地址:http://${ip:port}/${project_name}/admin,管理账号: admin/admin123

8)        各个模板的切换已通过系统中“站点管理”模块进行操作。站点管理是通过域名解析实现各个模板的对应。

2.2    eclipse部署配置

因为java开发,用eclipse的比较多~这里再介绍一下eclipse导入,部署和配置。

2.2.1    获取源码地址

源码:http://git.oschina.net/flyfox/jfinal_cms复制git地址。

2.2.2    下载git源码

         然后我们打开eclipse,导入git项目。(这里也可以用git工具先将源码下载下来,或者直接下载zip包)

选择git导入:

点击URI导入方式:

输入复制的git地址:

选择下载master主分支:

选择导入到本地的目录:

点击下一步获取git源代码。

2.2.3    构建maven项目

如果git获取后,我们选择了作为一个普通项目导入,那么我们需要将其转换为maven项目,项目上面邮件选择Configure->Convert to MavenProject:

如果我们通过其他git工具或者下载zip,那么我们需要导入maven项目

选择项目路径即可:

这样我们项目就导入完成了。

2.2.4    运行项目代码

         到这里我们需要安装好mysql,运行完初始化脚本,并且配置好数据库连接串以及账号密码。这些上面maven方式部署有介绍,就不再重复了。

         现在我们就可以进行maven项目构建了,运行maveninstall,安装,并下载依赖包:

如下提示说明构建成功

[INFO]------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO]------------------------------------------------------------------------

[INFO] Total time: 22.565s

[INFO] Finished at: Sat Jun 11 07:41:04 GMT 2016

[INFO] Final Memory: 6M/12M

[INFO]------------------------------------------------------------------------

然后我们运行项目,项目右键,Run As-> Maven build...

然后运行tomcat:run,点击运行按钮:

看到如下提示,并且没有报错,说明启动成功。

##################################

############系统启动完成##########

##################################

2.2.5    项目访问

网站效果访问:http://127.0.0.1/jfinal_cms/

管理地址:http://127.0.0.1/jfinal_cms/admin,管理账号: admin/admin123

         到这里jfinal cms部署就大功告成了~!~

2.3    实时编译源码配置

         上面部署方式有一个小问题就是,每次修改都需要进行maven install后,再maven tomcat:run才能看到修改后的效果。可以进行如下配置解决这个问题。

         修改pom,删除warSourceDirectory配置。

                     <plugin>

                            <groupId>org.codehaus.mojo</groupId>

                            <artifactId>tomcat-maven-plugin</artifactId>

                            <version>1.1</version>

                            <configuration>

                                   <port>80</port>

                                   <path>/${project.artifactId}</path>

                   <warSourceDirectory>${basedir}/target/${artifactId}</warSourceDirectory>

                            </configuration>

                     </plugin>

         项目右键,选择构建路径(BuildPath->Configure Build Path...):

删除源码路径,重新配置,再修改默认输出路径(Default output folder):

这是再运行就可以进行实时编辑查看了。

2.4    其他配置说明

1)        如需要oauth2的,设置src/conf/oauth.properties

2)        如果只使用单站点,可以将sites.properties文件中SITE.MULTI.FLAG = true改为false。然后通过config.properties的ATTR.PATH_PC=/template/mtg配置模板。


 

3      相关技术说明

3.1    java框架jfinal

         jfinal官网:http://www.jfinal.com/

         下面我在网上截了一张jfinal架构图,个人觉得梳理的很清楚。jfina官网上面的文档在使用方面介绍的简单明了,上手很容易,个人感觉没有必要在进行其他介绍了。

3.2    java模板beetl

beetl文档:http://ibeetl.com/guide/beetl.html

         beetl的有点和好处可以看上面文档第一章节,作者介绍的已经很清楚了。详细介绍大家可以看上面的文档,我下面制作一些简单介绍。

l  变量引用

         变量引用与其他模板相同,可以通过${name}调用。

         这里要特殊说明的是beetl支持获取map属性,即调用get(Objectkey)。如果对象既有具体属性getName(),又有get(Object key),则以具体属性优先级高。如${user.name}先进行调用getName()方法如果没有再调用get(Objectkey)方法。

l  list循环

代码片段如下:

总共 ${list.~size}

<%

for(user inlist){

%>

hello,${user.name};

 

<%}%>

当前页${pageMap['page']},总共${pageMap["total"]}

l  函数调用

         可以直接调用registerFunctionPackage(namespace,yourJavaObject),这时候yourJavaObject里的所有public方法都将注册为Beetl方法,方法名是namespace+"."+方法名

         jfinalcms中有许多方法和前台接口都是用了这种方式,所以这里也别说明一下。后续会进行详细介绍。

l  其他

         剩下的大家就参考项目代码进行理解就可以了~如果不明白可以看beetl官方文档。


 

4      jfinal cms技术

         源码依赖于作者的其他两个开源项目jflyfox_base和jflyfox_jfinal两个项目,相关介绍在jfinal cms项目依赖章节有所介绍。

4.1    项目目录结构介绍

                   首先我们先来熟悉一下项目的目录结构,以下为该项目目录结构说明,java代码:

项目文件目录:

4.2    后台模块开发

4.2.1    java代码

         首先后台代码主要分为Controller,Model和Service。Controller负责参数传递和页面跳转;Model负责数据承载;Service负责复杂业务处理。

         这里我们用联系人模块进行简单讲解。

         下面是controller示例代码,首先需要继承BaseProjectController类,这样方便项目统一扩展,现在里面已经有一些公共方法了,请自行查看源码。

       页面跳转主要包含list(列表页)、add(添加页)、view(查看页)、delete(删除数据)、edit(编辑页)和save(保存方法)。这几个是基本操作,常规增删改查均以此规范进行编写。Controller遵循jfinal设置规则,并加入了注解功能。以下列表访问地址就是/admin/contact/list;编辑页面地址就是/admin/contact/edit/${id}。其他地址不再列举。

         以下黄色背景字体需要特别注意。

packagecom.jflyfox.modules.admin.contact;

 

importcom.jfinal.plugin.activerecord.Page;

importcom.jflyfox.component.base.BaseProjectController;

importcom.jflyfox.jfinal.component.annotation.ControllerBind;

importcom.jflyfox.jfinal.component.db.SQLUtils;

importcom.jflyfox.util.StrUtils;

 

/**

 * 联系人管理

 *

 * @author flyfox 2014-2-11

 */

@ControllerBind(controllerKey= "/admin/contact")

publicclass ContactController extendsBaseProjectController {

 

       privatestaticfinal String path = "/pages/admin/contact/contact_";

 

       publicvoid list() {

              TbContactmodel = getModelByAttr(TbContact.class);

 

              // 这里是拼接查询sql并且获取前台传递的参数

              SQLUtilssql = new SQLUtils(" from tb_contact t where 1=1 ");

              if (model.getAttrValues().length != 0){

                     sql.setAlias("t");

                     sql.whereLike("name", model.getStr("name"));

                     sql.whereEquals("type", model.getStr("type"));

              }

             

              // 这里是统一做了排序功能

              StringorderBy = getBaseForm().getOrderBy();

              if (StrUtils.isEmpty(orderBy)) {

                     sql.append(" order by id desc ");

              } else {

                     sql.append(" order by ").append(orderBy);

              }

 

              Page<TbContact>page = TbContact.dao.paginate(getPaginator(), "select t.* ", //

                            sql.toString().toString());

 

              // 下拉框

              setAttr("page", page);

              setAttr("attr", model);

              render(path + "list.html");

       }

 

       publicvoid add() {

              render(path + "add.html");

       }

 

       publicvoid view() {

              TbContactmodel = TbContact.dao.findById(getParaToInt());

              setAttr("model", model);

              render(path + "view.html");

       }

 

       publicvoid delete() {

              TbContactmodel = new TbContact();

              Integeruserid= getSessionUser().getUserID();

              Stringnow = getNow();

              model.put("update_id", userid);

              model.put("update_time", now);

              model.deleteById(getParaToInt());

             

              list();

       }

 

       publicvoid edit() {

              TbContactmodel = TbContact.dao.findById(getParaToInt());

              setAttr("model", model);

              render(path + "edit.html");

       }

 

       publicvoid save() {

              Integerpid = getParaToInt();

              TbContactmodel = getModel(TbContact.class);

             

             

              Integeruserid= getSessionUser().getUserID();

              Stringnow = getNow();

              // 这两个字段设置为了日志收集

              model.put("update_id", userid);

              model.put("update_time", now);

              if (pid != null && pid >0) { // 更新

                     model.update();

              } else { // 新增

                     model.remove("id");

                     model.put("create_id", userid);

                     model.put("create_time", now);

                     model.save();

              }

              renderMessage("保存成功");

       }

}

         下面Model示例代码,Model需要继承BaseProjectModel,方便业务扩展。里面已经加入了缓存、日志等扩展功能,请自行查看源码。jfinal Model可以不添加get、set方法,这里添加主要是为了代码规范。Model遵循jfinal设置规则,并加入了注解功能。通过ModelBind可以直接设置对应的表和主键,主键默认是ID

       get、set方法可以通过作者的AutoCreate组件自动生成。这里不再做多介绍。

packagecom.jflyfox.modules.admin.contact;

 

importcom.jflyfox.component.base.BaseProjectModel;

importcom.jflyfox.jfinal.component.annotation.ModelBind;

 

@ModelBind(table= "tb_contact")

publicclass TbContact extendsBaseProjectModel<TbContact> {

 

       privatestaticfinallongserialVersionUID = 1L;

       publicstaticfinal TbContact dao = new TbContact();

 

       // columns START

       private String ID = "id"; // 主键

       private String NAME = "name"; // 姓名

 

       public TbContactsetId(java.lang.Integer value) {

              set(ID,value);

              returnthis;

       }

 

       public java.lang.Integer getId() {

              return get(ID);

       }

 

       public TbContact setName(java.lang.String value) {

              set(NAME, value);

              returnthis;

       }

 

       public java.lang.String getName() {

              return get(NAME);

       }

 

       ……………………………………

}

         Service集成BaseService,主要是Controller中复杂业务处理,将Controller中大量代码或者重复代码进行封装,统一调用。由于联系人模块相对简单,这里就没有Service代码了。

4.2.2    页面代码

         后台模板主要是增删改查,jfinalcms使用beetl模板,这里就不多介绍了。

         页面主要以${module}_名开头,包含add(添加)、edit(编辑)、list(列表)、view(查看)几个页面。各个页面页格式相对统一,可以进行参考。

         以下为add页面代码,添加页面可以与edit页面公用~只是传递参数均为空。

<%

       include("contact_edit.html"){}

%>

         以下为edit页面代码,首先edit页面采用了beetl安全模式,保证参数为空不报异常,这样能够让add很好的引用。其次后端页面都采用了公共的头部标签head.html,列表和查看页面也是如此,不在赘述。

       在编辑页面对js验证进行了封装,只需要添加validvalidname属性就可以实现js验证功能,提交时调用 validForm()方法即可。

<%

DIRECTIVE SAFE_OUTPUT_OPEN;

var headContent = {

       include("/pages/template/head.html"){}

%>

<script type="text/javascript">

var oper= {

       save:function(id){

              if(!validForm()) {

                     returnfalse;

              }

              id =id || '0';

              var url = 'admin/contact/save/'+id;

              form1.action= url;

              form1.submit();

              returntrue;

       }     

};

</script>

 

<%

};

var bodyContent = {

%>

<form name="form1"action="" method="post"class="form-horizontal" role="form">

       <input type="hidden"name="model.id" value="${model.id}"/>

       <table class="table">

              <%// 列表头部 %>

              <tr>

                     <td>姓名</td>

                     <td>

                                   <input class="form-control"type="text" name="model.name"value="${model.name}"

                                          valid="vtext" validname="名称"  />

                     </td>

              </tr>

                            ………………………………

                            <tr>

                     <td>说明</td>

                     <td>

                            <textarea class="form-control"rows="3" cols="30"name="model.remark">${model.remark}</textarea>

                     </td>

              </tr>

       </table>

      

       <div style="height: 50px;clear: both;">&nbsp;</div>

       <nav class="navbarnavbar-default navbar-fixed-bottom">

         <div class="container" style="padding: 5px 0px 5px 0px;text-align: center;">

              <button class="btnbtn-primary" onclick="returnoper.save(${model.id!'0'});">保 存</button>

              <button class="btnbtn-default" onclick="closeIframe();returnfalse;">关 闭</button>

         </div>

       </nav>

</form>

<%}; %>

 

<%layout("/pages/template/_layout.html",{head:headContent,body:bodyContent}){%>

DIRECTIVE SAFE_OUTPUT_CLOSE;

<%}%>

         下面是列表页代码, 方法调用以oper对象调用形式封装。增加、修改、查看页面以弹框形式展示,这样可以很好的保存列表页查询条件。查看页面关闭不会刷新列表,添加和修改页面关闭后均会刷新列表,均已封装为Iframe方法

paginator属性为列表分页查询调用方法。

showMenu方法为菜单选中展现,具体参数在菜单管理进行配置。

menu.html为菜单栏,各页面直接引用即可。paginator.html为分页栏,固定获取page参数进行处理。

列表头部设置设置class="sorting"为支持排序功能,name字段为传到后台的参数值。

<%

       varheadContent = {

       include("/pages/template/head.html"){}

%>

 

<script type="text/javascript">

var oper;

jQuery(function($) {

       // 页面方法

       oper = {

              width: 400,

              height: 430,

              form :document.form1,

              list :function() {

                     var url = 'admin/contact/list';

                     this.form.action = url;

                     this.form.submit();

              },

              view :function(id) {

                     var url = 'admin/contact/view/'+id;

                     var title = '查看联系人';

                     Iframe(url,this.width, this.height, title, false, false, false, EmptyFunc);

              },

              add : function() {

                     var url = 'admin/contact/add';

                     var title = '添加联系人';

                     Iframe(url,this.width, this.height, title);

              },

              edit :function(id) {

                     var url = 'admin/contact/edit/'+id;

                     var title = '修改联系人';

                     Iframe(url,this.width, this.height, title);

              },

              del : function(id) {

                     var url = 'admin/contact/delete/'+id;

                     var title = '确认要删除该联系人信息?';

                     Confirm(title,function() {

                            form1.action= url;

                            form1.submit();

                     });

              }

       };

       //显示Menu索引

       showMenu('page_contact');

});

 

//分页

varpaginator = function(page) {

       oper.list();

};

 

</script>

 

<%

};

 

var bodyContent = {

%>

       <form name="form1"action="" method="post"  class="form-inline"role="form">

              <!-- 菜单 -->

              <%include("/pages/template/menu.html"){} %>

 

              <div class="tableSearch">

                     <//查询列表 %>

                     <div class="form-group">

                                   <input class="form-control"type="text" name="attr.name"value="${attr.name!''}"

                                          placeholder="请输入姓名" required='required' />

                     </div>

        

                    

                     <button type="button"class="btn btn-default" onclick="oper.list();"name="search">

                                   <span class="glyphiconglyphicon-search"></span>查 询

                     </button>

                     <button type="button"class="btn btn-default" onclick="resetForm();">

                                   <span class="glyphiconglyphicon-refresh"></span>重 置

                     </button>

                     <button type="button"class="btn btn-default" onclick="oper.add();">

                                   <span class="glyphiconglyphicon-plus"></span>新 增

                     </button>

              </div>

             

              <!-- 数据列表 -->

              <table class="tabletable-striped table-bordered table-hover">

                     <thead>

                            <tr>

                                   <th>序号</th>

                                   <// 列表头部 %>

                                   <th name="name"class="sorting">姓名</th>

                                   <th name="phone"class="sorting">手机号</th>

                                   <th name="email"class="sorting">Email</th>

                                   <th>说明</th>

 

                                   <th>操作</th>

                            </tr>

                     </thead>

                     <tbody>

                     <%for(item in page.list){ %>

                            <tr>

                                   <td>${itemLP.index }</td>

                                   <// 列表内容 %>

                                   <td>${item.name}</td>

                                   <td>${item.phone}</td>

                                   <td>${item.email}</td>

                                   <td title="${item.remark}">

                                   <%if (strutil.length(item.remark) > 6) { %>

                                          ${strutil.subStringTo(item.remark,0, 6)}...

                                   <% } else { %>

                                          ${item.remark}

                                   <} %>

                                   </td>

        

                                   <td>

                                   <a href="javascript:void(0);"class="btn btn-sm btn-success" onclick="oper.view(${item.id});">查看</a>

                                   <a href="javascript:void(0);"class="btn btn-sm btn-primary" onclick="oper.edit(${item.id});">修改</a>

                                   <a href="javascript:void(0);"class="btn btn-sm btn-danger" onclick="oper.del(${item.id});">删除</a>

                                   </td>

                            </tr>

                     <%} %>

                     </tbody>

              </table>

              <%include("/pages/includes/paginator.html"){}%>

       </form>

<%};%>

 

 

<% layout("/pages/template/_layout.html",{head:headContent,body:bodyContent}){%>

<%}%>

         view页面相对简单,只是一个数据展示,这里就不多做介绍了。

         如果你看到了这里,有了后台代码和前台页面,联系人模块就完成了,配置一下菜单就能看到效果了~!~

       温馨提示:列表分页、菜单栏、js验证和列表排序功能封装实现这里先不做说明,有兴趣的可以查看源码。

4.3     前台模板开发

4.3.1    java代码

         前台CommonController为整个项目默认入口,项目根目录、登陆、登出等。

         TemplateService为前台模板接口,这里的TemplateService通过beetl方法注册实现接口调用。再config中进行注册,代码如下groupTemplate.registerFunctionPackage("temp",TemplateService.class);这个不明白可以看下beetl文档,这样就实现了页面调用temp.article(1)返回文章对象。

         后台service封装实现具体方法,template进行service调用提供接口。这样前台页面直接访问template接口,很好的实现了代码前后端代码分离。当然前台的java还进行了公共Controller的封装以及公共参数的传递。

         如文章、评论、消息、个人信息、注册、标签等公共Controller以及统一前台参数传递拦截器FrontInterceptor。截图如下:

         有一些比较个性化的东西,我这里就没有封装接口,直接创建了一个Controller。

4.3.2    模板代码

         前台模板现在有几个了~这里我们以最简单的website单页滚动网站为例。

         这里主要得模板页面就是home.html。项目栏目通过后端管理进行配置,默认是home目录下的${urlkey}.html文件,website配置的是home。当然也可以直接通过栏目管理配置模板路径。

         这里我们主要看顶部的接口调用,temp.articlePage返回当前站点的前20条文章列表,进行列表数据展示。temp.article返回当前站点设置的默认文章,进行一些属性展示。

<%

// 获取前20个文章

var articlePage = temp.articlePage(1, 20, session.site.model.site_folder_id);

var homeArticle =temp.article(session.site.model.site_article_id);

%>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

 

<head>

       <title>${HEAD_TITLE }</title>

       <meta http-equiv="Content-Type"content="text/html; charset=UTF-8">

       <link rel="icon"href="${BASE_PATH }favicon.ico" />

       <link rel="shortcuticon" href="${BASE_PATH }favicon.ico"/>

       <meta http-equiv="pragma"content="no-cache">

       <meta http-equiv="cache-control"content="no-cache">

       <meta http-equiv="expires"content="0">

       <meta name="keywords"content="${HEAD_KEYWORDS }">

       <meta name="description"content="${HEAD_DESCRIPTION }">

      

       <!-- IE兼容处理 -->

       <meta http-equiv="X-UA-Compatible"content="IE=edge">

    <meta name="viewport"content="width=device-width, initial-scale=1">

 

       <link rel="stylesheet"type="text/css" href="http://cdn.bootcss.com/fullPage.js/2.7.6/jquery.fullPage.min.css"/>

       <script src="http://cdn.bootcss.com/jquery/1.8.3/jquery.min.js"></script>

       <script type="text/javascript"src="http://cdn.bootcss.com/fullPage.js/2.7.6/jquery.fullPage.min.js"></script>

       <link href="http://cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css"rel="stylesheet">

 

<style>

       ……………………

</style>

 

<script type="text/javascript">

varanchorsArray = [];

varnavigationTooltipsArray = [];

<% for(item in articlePage.list){ %>

       anchorsArray.push('j${item.id}');

       navigationTooltipsArray.push('${item.title}');

<% }%>

       $(document).ready(

                     function() {

                            $('#fullpage').fullpage({

                                   anchors: anchorsArray, //[ 'j0','j1','j2','j3','j4','j5','j6'  ],

                                   navigation: true,

                                   navigationPosition: 'right',

                                   navigationTooltips:  navigationTooltipsArray // [ '英雄联盟','德玛西亚皇子', '德邦总管','黑暗之女', '皎月女神','寒冰射手','联系我们']

                                   ,afterLoad:function(anchorLink, index){

                                          $('ul.nav li').removeClass('active');

                                          $('ul.nav li#nav_'+anchorLink).addClass('active');

 

                                   }

                            });

                     });

</script>

 

</head>

<body>

       <nav class="navbarnavbar-default navbar-fixed-top">

              <div class="container-fluid">

                     <div class="navbar-header">

                            <a class="navbar-brand"href="#"><img alt="logo"src="${flyfox.getImage(homeArticle)}" width="24" height="24">

                                   ${homeArticle.title}

                            </a>

                     </div>

                     <div class="container">

                            <ul class="nav navbar-navnavbar-right">

                                   <%for(item in articlePage.list){ %>

                                          <li id="nav_j${item.id}"><a href="#j${item.id}">${item.title}</a></li>

                                   <%} %>

                            </ul>

                     </div>

              </div>

 

       </nav>

 

 

       <div id="fullpage">

              <%for(item in articlePage.list){ %>

                     <div class="section"id="section${itemLP.index}" style="background-image: url(${flyfox.getImage(item)});">

                            ${item.content!}

                     </div>

              <%} %>

             

       </div>

 

</body>

</html>

到这里简单的前台模板功能就介绍完了。

4.4    前台模板接口

4.4.1    前台默认入口

         首先上一章节提到了默认入口,这里我要详细说明下默认访问路径规则。FrontInterceptor前台拦截器会传递友情链接、关于我们、推荐文章和栏目信息数据。然后根目录会默认访问当前站点设置的栏目,如果未设置那么展示TbFolder.ROOT栏目,如果url传递了目录Id或者目录urlKey那么会访问对应的栏目。跳转规则是优先处理跳转jumpUrl,其次是配置的模板路径,如果都没有按照默认home/${urlKey}.html的规则跳转。从这里可以看出,简单的栏目规则就是每个站点下面栏目是和urlKey一一对应的。

       如我们添加一条栏目,id:101 urlKey:jflyfox那么我们可以通过“项目地址/101.html”访问,也可以通过“项目地址/jflyfox.html”访问。但是默认的栏目菜单生成规则是优先使用urlKey。具体可以看模板下includes/ header_menu.html实现。

@ControllerBind(controllerKey= "/")

publicclass CommonController extendsBaseProjectController {

 

       …………………………

 

       /**

        * 首页,菜单

        *

        * 2015年5月25日 下午11:00:28 flyfox330627517@qq.com

        */

       @Before(FrontInterceptor.class)

       publicvoid index() {

              // new FrontService().menu(this);

              int folderRoot = TbFolder.ROOT;

              SessionSitesite = getSessionSite();

              IntegersiteFolderId = site.getModel().getSiteFolderId();

              if (siteFolderId != null&& siteFolderId > 0) {

                     folderRoot= siteFolderId;

              }

 

              StringfolderStr = getPara();

              IntegerfolderId = folderRoot;

 

              if (folderStr != null) {

                     if (NumberUtils.parseInt(folderStr)> 0) {

                            folderId= NumberUtils.parseInt(folderStr);

                     }else {

                            folderId= NumberUtils.parseInt(FolderService.getMenu(folderStr));

                     }

              }

 

              if (folderId == null ||folderId <= 0) {

                     folderId= folderRoot;

              }

              // 活动目录

              setAttr("folders_selected", folderId);

 

              TbFolderfolder = new FolderService().getFolder(folderId);

              setAttr("folder", folder);

 

              setAttr("paginator", getPaginator());

 

              // seo:title优化

              StringfolderName = (folder == null ? "" : folder.getStr("name") + "- ");

              setAttr(JFlyFoxUtils.TITLE_ATTR, folderName + getAttr(JFlyFoxUtils.TITLE_ATTR));

 

              // 栏目跳转规则

              StringjumpUrl = folder.getJumpUrl();

              Stringpath = folder.getPath();

              StringurlKey = folder.getKey();

              if (StrUtils.isNotEmpty(jumpUrl)) {

                     redirect(jumpUrl);

              } elseif (StrUtils.isNotEmpty(path)) {

                     renderAuto(path);

              } else {

                     renderAuto(Home.PATH + urlKey + ".html");

              }

 

       }

 

……………………………………

}

         其次是FrontAlbumImageController和FrontAlbumVideoController分别是图片和视频模块统一入口,现在功能相对简单,这里就不详细介绍了。

4.4.2    前台接口

         前台接口主要是TemplateService这个类,提供方式是利用beetl函数注册方式,如groupTemplate.registerFunctionPackage("temp",TemplateService.class);代码,这样我们就可以通过temp.${method}进行调用了。

         下面是接口代码,例如通过temp.countView(100),我们就能获取到文章id为100的浏览量,temp. articlePage(23,1,20)我们就能够获取到栏目id为23下面的前20条文章。

       接口主要提供了文章列表、文章内容、浏览量、评论量,栏目标签,栏目、滚屏图片列表和提醒列表等信息。

packagecom.jflyfox.modules.front.template;

 

……………………

 

/**

 * 模板方法接口

 *

 * 2016年1月18日 下午6:05:54 flyfox 330627517@qq.com

 */

publicclass TemplateService extendsBaseService {

 

       privatefinalstatic FrontCacheService service = new FrontCacheService();

 

       privatefinalstatic ArticleLikeCache articleLikeservice = newArticleLikeCache();

 

       /**

        * 获取浏览数

        *

        * 2015年6月2日 下午6:30:56 flyfox330627517@qq.com

        *

        * @paramarticleId

        * @return

        */

       publicstaticint countView(int articleId) {

              TbArticlearticle = service.getArticleCount(articleId);

              return article == null ? 0 :article.getCountView();

       }

 

       /**

        * 获取评论数

        *

        * 2015年6月2日 下午6:30:56 flyfox330627517@qq.com

        *

        * @param articleId

        * @return

        */

       publicstaticint countComment(int articleId) {

              TbArticlearticle = service.getArticleCount(articleId);

              return article == null ? 0 :article.getCountComment();

       }

 

       /**

        * 获取浏览数

        *

        * 2015年6月2日 下午6:30:56 flyfox330627517@qq.com

        *

        * @param articleId

        * @return

        */

       publicstaticboolean isLike(int userId, int articleId) {

              returnarticleLikeservice.isLike(userId, articleId);

       }

 

       /**

        * 获取标签信息

        *

        * 2015年5月25日 下午11:49:58 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        *           目录

        * @return

        */

       public List<TbTags> tagsListByArticle(int articleId) {

              returnservice.getTagsByArticle(articleId);

       }

 

       /**

        * 获取标签信息

        *

        * 2015年5月25日 下午11:49:58 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        *           目录

        * @return

        */

       public Page<TbTags> tagsPageByFolder(int pageNo, int pageSize, int folderId) {

              returnservice.getTagsByFolder(newPaginator(pageNo, pageSize), folderId);

       }

 

       /**

        * 获取标签信息

        *

        * 2015年5月25日 下午11:49:03 flyfox330627517@qq.com

        *

        * @param paginator

        * @return

        */

       public Page<TbTags> tagsPage(intpageNo, int pageSize, intsiteId) {

              returnservice.getTags(newPaginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 查询文章,展示的和类型为11,12的推荐文件,前10个

        *

        * 2015年4月29日 下午4:48:24 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folder_id

        * @return

        */

       public Page<TbArticle> articlePageRecommend(int pageNo, int pageSize, int siteId) {

              returnservice.getRecommendArticle(new Paginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 返回最新文章

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @return

        */

       public Page<TbArticle> articlePageTop(int pageNo, int pageSize, int siteId) {

              returnservice.getNewArticle(newPaginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 返回文章列表

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public Page<TbArticle> articlePageSite(int pageNo, int pageSize, int siteId) {

              returnservice.getArticleBySiteId(new Paginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 返回文章列表

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public Page<TbArticle> articlePage(int pageNo, int pageSize, int folderId) {

              returnservice.getArticle(newPaginator(pageNo, pageSize), folderId);

       }

 

       /**

        * 返回文章列表不走缓存

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public Page<TbArticle> articlePageNoCache(int pageNo, int pageSize, int folderId) {

              returnservice.getArticleByNoCache(new Paginator(pageNo, pageSize), folderId);

       }

 

       /**

        * 返回对应文章

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public TbArticle article(intarticleId) {

              returnservice.getArticle(articleId);

       }

 

       /**

        * 获取栏目滚动图片

        *

        * 2016年1月28日 下午5:28:25 flyfox330627517@qq.com

        *

        * @param folderId

        * @return

        */

       public List<TbFolderRollPicture> rollPicture(int folderId) {

              returnservice.getRollPicture(folderId);

       }

 

       /**

        * 获取公告信息

        *

        * 2016年1月28日 下午5:29:47 flyfox330627517@qq.com

        *

        * @param folderId

        * @return

        */

       public List<TbFolderNotice> notice(int folderId) {

              returnservice.getNotice(folderId);

       }

 

}

         除了默认出口和上述接口外,我们还提供了一些公共Controller和个性Controller,如下图所示:

         这些Controller大致实现了关于我们、建议、文章查看、评论、消息、Oauth2登陆、个人信息、注册信息和标签功能。

4.5    项目工具类

         base下包含了Model和Controller的父类:Model实现了增删改统一日志,自定义缓存查询(类似jfinal自带缓存查询)以及查询方法扩展,部分实现在BaseModel中。Controller实现了获取和设置用户session、站点session获取、自动识别移动和PC端跳转、带提示跳转,获取分页、获取form等功能,部分实现在BaseController中。

         beetl下包含了beetl公共方法封装:数据字典获取、图片路径转换、菜单地址获取以及常规字符串处理方法。

         config下是jfinal配置文件入口,里面有大量的jfinal初始化配置,这里就不一一说明了。

         controller下是ueditor和umeditor的后台实现代码。

         interceptor是通用拦截器:包含SEO属性设置,页面访问量统计,站点Session处理、缓存更新以及用户Session唯一键设置。在jflyfox_jfinal中还包含登陆session拦截、Session属性设置、form获取统一处理。

         util下是一些公共配合及方法:包含文章访问量和评论量缓存,验证码生成、图片属性获取、缓存管理统一入口、上传组件封装以及常量类等。

         项目内部工具类大致如下图所示:


 

5      jfinal cms项目依赖

jfinal cms采用maven结构,依赖与作者的其他两个项目。

jflyfox_base项目

jfinal_base提供了java的工具类,如StrUtils,NumberUtils,DateUtils 等

github:https://github.com/jflyfox/jflyfox_base

osc@git:http://git.oschina.net/flyfox/jflyfox_base

l  jflyfox_jfinal项目

jflyfox_jfinal是对Jfinal等组件的封装。

         包含controller,model,form,service基础类封装。

         对分页进行了后台和前台的实现。

         加入了自动扫描model和controller以及注解支持。

         实现了ueditor后台Controller代码。

         加入了页面增删改查代码自动生成功能,可通过beetl模板进行配置。

         实现了SessionAttrInterceptor、页面和手机设备判断拦截器以及BasePathHandler。

github:https://github.com/jflyfox/jflyfox_jfinal

osc@git:http://git.oschina.net/flyfox/jflyfox_jfinal

6      联系作者

作者:FLY的狐狸

jfinal cms交流群:568909653

QQ:369191470

邮件:369191470@qq.com

源码地址:http://git.oschina.net/flyfox/jfinal_cms


 

7      开源赞助

支付宝支付

微信支付

 

         jfinal cms,采用了简洁强大的JFinal作为web框架,模板引擎用的是beetl,数据库用mysql,前端bootstrap框架。 支持多站点、oauth2认证、帐号注册、密码加密、评论及回复,消息提示,网站访问量统计,文章评论数和浏览量统计,回复管理,支持权限管理。

后台模块包含:栏目管理,栏目公告,栏目滚动图片,文章管理,回复管理,意见反馈,我的相册,相册管理,图片管理,专辑管理、视频管理、缓存更新,友情链接,访问统计,联系人管理,模板管理,组织机构管理,用户管理,角色管理,菜单管理,数据字典管理,站点管理。

         建站截图:


 

2      jfinal cms部署与配置

2.1    maven方式部署配置

maven部署方式的好处在于不依赖于任何IDE,并且跨平台。

1)        下载项目源代码,安装jdk、maven、mysql。

2)        在项目目录下运行mvn install,提示BUILD SUCCESS即可。

3)        创建mysql用户和数据库,运行/jfinal_cms/sql下对应jfinal_cms_v4.sql。

4)        数据库配置文件:/jfinal_cms/src/main/resources/conf/db.properties,修改相应配置即可。

db_type=mysql

 

mysql.jdbcUrl =jdbc:mysql://127.0.0.1:3306/jfinal_cms2?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull

mysql.user = root

mysql.password = 123456

mysql.driverClass = com.mysql.jdbc.Driver

5)        运行:mvn tomcat:run。看到“系统启动完成”说明运行成功。

6)        网站效果访问:http://127.0.0.1/jfinal_cms。

7)        管理地址:http://${ip:port}/${project_name}/admin,管理账号: admin/admin123

8)        各个模板的切换已通过系统中“站点管理”模块进行操作。站点管理是通过域名解析实现各个模板的对应。

2.2    eclipse部署配置

因为java开发,用eclipse的比较多~这里再介绍一下eclipse导入,部署和配置。

2.2.1    获取源码地址

源码:http://git.oschina.net/flyfox/jfinal_cms复制git地址。

2.2.2    下载git源码

         然后我们打开eclipse,导入git项目。(这里也可以用git工具先将源码下载下来,或者直接下载zip包)

选择git导入:

点击URI导入方式:

输入复制的git地址:

选择下载master主分支:

选择导入到本地的目录:

点击下一步获取git源代码。

2.2.3    构建maven项目

如果git获取后,我们选择了作为一个普通项目导入,那么我们需要将其转换为maven项目,项目上面邮件选择Configure->Convert to MavenProject:

如果我们通过其他git工具或者下载zip,那么我们需要导入maven项目

选择项目路径即可:

这样我们项目就导入完成了。

2.2.4    运行项目代码

         到这里我们需要安装好mysql,运行完初始化脚本,并且配置好数据库连接串以及账号密码。这些上面maven方式部署有介绍,就不再重复了。

         现在我们就可以进行maven项目构建了,运行maveninstall,安装,并下载依赖包:

如下提示说明构建成功

[INFO]------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO]------------------------------------------------------------------------

[INFO] Total time: 22.565s

[INFO] Finished at: Sat Jun 11 07:41:04 GMT 2016

[INFO] Final Memory: 6M/12M

[INFO]------------------------------------------------------------------------

然后我们运行项目,项目右键,Run As-> Maven build...

然后运行tomcat:run,点击运行按钮:

看到如下提示,并且没有报错,说明启动成功。

##################################

############系统启动完成##########

##################################

2.2.5    项目访问

网站效果访问:http://127.0.0.1/jfinal_cms/

管理地址:http://127.0.0.1/jfinal_cms/admin,管理账号: admin/admin123

         到这里jfinal cms部署就大功告成了~!~

2.3    实时编译源码配置

         上面部署方式有一个小问题就是,每次修改都需要进行maven install后,再maven tomcat:run才能看到修改后的效果。可以进行如下配置解决这个问题。

         修改pom,删除warSourceDirectory配置。

                     <plugin>

                            <groupId>org.codehaus.mojo</groupId>

                            <artifactId>tomcat-maven-plugin</artifactId>

                            <version>1.1</version>

                            <configuration>

                                   <port>80</port>

                                   <path>/${project.artifactId}</path>

                   <warSourceDirectory>${basedir}/target/${artifactId}</warSourceDirectory>

                            </configuration>

                     </plugin>

         项目右键,选择构建路径(BuildPath->Configure Build Path...):

删除源码路径,重新配置,再修改默认输出路径(Default output folder):

这是再运行就可以进行实时编辑查看了。

2.4    其他配置说明

1)        如需要oauth2的,设置src/conf/oauth.properties

2)        如果只使用单站点,可以将sites.properties文件中SITE.MULTI.FLAG = true改为false。然后通过config.properties的ATTR.PATH_PC=/template/mtg配置模板。


 

3      相关技术说明

3.1    java框架jfinal

         jfinal官网:http://www.jfinal.com/

         下面我在网上截了一张jfinal架构图,个人觉得梳理的很清楚。jfina官网上面的文档在使用方面介绍的简单明了,上手很容易,个人感觉没有必要在进行其他介绍了。

3.2    java模板beetl

beetl文档:http://ibeetl.com/guide/beetl.html

         beetl的有点和好处可以看上面文档第一章节,作者介绍的已经很清楚了。详细介绍大家可以看上面的文档,我下面制作一些简单介绍。

l  变量引用

         变量引用与其他模板相同,可以通过${name}调用。

         这里要特殊说明的是beetl支持获取map属性,即调用get(Objectkey)。如果对象既有具体属性getName(),又有get(Object key),则以具体属性优先级高。如${user.name}先进行调用getName()方法如果没有再调用get(Objectkey)方法。

l  list循环

代码片段如下:

总共 ${list.~size}

<%

for(user inlist){

%>

hello,${user.name};

 

<%}%>

当前页${pageMap['page']},总共${pageMap["total"]}

l  函数调用

         可以直接调用registerFunctionPackage(namespace,yourJavaObject),这时候yourJavaObject里的所有public方法都将注册为Beetl方法,方法名是namespace+"."+方法名

         jfinalcms中有许多方法和前台接口都是用了这种方式,所以这里也别说明一下。后续会进行详细介绍。

l  其他

         剩下的大家就参考项目代码进行理解就可以了~如果不明白可以看beetl官方文档。


 

4      jfinal cms技术

         源码依赖于作者的其他两个开源项目jflyfox_base和jflyfox_jfinal两个项目,相关介绍在jfinal cms项目依赖章节有所介绍。

4.1    项目目录结构介绍

                   首先我们先来熟悉一下项目的目录结构,以下为该项目目录结构说明,java代码:

项目文件目录:

4.2    后台模块开发

4.2.1    java代码

         首先后台代码主要分为Controller,Model和Service。Controller负责参数传递和页面跳转;Model负责数据承载;Service负责复杂业务处理。

         这里我们用联系人模块进行简单讲解。

         下面是controller示例代码,首先需要继承BaseProjectController类,这样方便项目统一扩展,现在里面已经有一些公共方法了,请自行查看源码。

       页面跳转主要包含list(列表页)、add(添加页)、view(查看页)、delete(删除数据)、edit(编辑页)和save(保存方法)。这几个是基本操作,常规增删改查均以此规范进行编写。Controller遵循jfinal设置规则,并加入了注解功能。以下列表访问地址就是/admin/contact/list;编辑页面地址就是/admin/contact/edit/${id}。其他地址不再列举。

         以下黄色背景字体需要特别注意。

packagecom.jflyfox.modules.admin.contact;

 

importcom.jfinal.plugin.activerecord.Page;

importcom.jflyfox.component.base.BaseProjectController;

importcom.jflyfox.jfinal.component.annotation.ControllerBind;

importcom.jflyfox.jfinal.component.db.SQLUtils;

importcom.jflyfox.util.StrUtils;

 

/**

 * 联系人管理

 *

 * @author flyfox 2014-2-11

 */

@ControllerBind(controllerKey= "/admin/contact")

publicclass ContactController extendsBaseProjectController {

 

       privatestaticfinal String path = "/pages/admin/contact/contact_";

 

       publicvoid list() {

              TbContactmodel = getModelByAttr(TbContact.class);

 

              // 这里是拼接查询sql并且获取前台传递的参数

              SQLUtilssql = new SQLUtils(" from tb_contact t where 1=1 ");

              if (model.getAttrValues().length != 0){

                     sql.setAlias("t");

                     sql.whereLike("name", model.getStr("name"));

                     sql.whereEquals("type", model.getStr("type"));

              }

             

              // 这里是统一做了排序功能

              StringorderBy = getBaseForm().getOrderBy();

              if (StrUtils.isEmpty(orderBy)) {

                     sql.append(" order by id desc ");

              } else {

                     sql.append(" order by ").append(orderBy);

              }

 

              Page<TbContact>page = TbContact.dao.paginate(getPaginator(), "select t.* ", //

                            sql.toString().toString());

 

              // 下拉框

              setAttr("page", page);

              setAttr("attr", model);

              render(path + "list.html");

       }

 

       publicvoid add() {

              render(path + "add.html");

       }

 

       publicvoid view() {

              TbContactmodel = TbContact.dao.findById(getParaToInt());

              setAttr("model", model);

              render(path + "view.html");

       }

 

       publicvoid delete() {

              TbContactmodel = new TbContact();

              Integeruserid= getSessionUser().getUserID();

              Stringnow = getNow();

              model.put("update_id", userid);

              model.put("update_time", now);

              model.deleteById(getParaToInt());

             

              list();

       }

 

       publicvoid edit() {

              TbContactmodel = TbContact.dao.findById(getParaToInt());

              setAttr("model", model);

              render(path + "edit.html");

       }

 

       publicvoid save() {

              Integerpid = getParaToInt();

              TbContactmodel = getModel(TbContact.class);

             

             

              Integeruserid= getSessionUser().getUserID();

              Stringnow = getNow();

              // 这两个字段设置为了日志收集

              model.put("update_id", userid);

              model.put("update_time", now);

              if (pid != null && pid >0) { // 更新

                     model.update();

              } else { // 新增

                     model.remove("id");

                     model.put("create_id", userid);

                     model.put("create_time", now);

                     model.save();

              }

              renderMessage("保存成功");

       }

}

         下面Model示例代码,Model需要继承BaseProjectModel,方便业务扩展。里面已经加入了缓存、日志等扩展功能,请自行查看源码。jfinal Model可以不添加get、set方法,这里添加主要是为了代码规范。Model遵循jfinal设置规则,并加入了注解功能。通过ModelBind可以直接设置对应的表和主键,主键默认是ID

       get、set方法可以通过作者的AutoCreate组件自动生成。这里不再做多介绍。

packagecom.jflyfox.modules.admin.contact;

 

importcom.jflyfox.component.base.BaseProjectModel;

importcom.jflyfox.jfinal.component.annotation.ModelBind;

 

@ModelBind(table= "tb_contact")

publicclass TbContact extendsBaseProjectModel<TbContact> {

 

       privatestaticfinallongserialVersionUID = 1L;

       publicstaticfinal TbContact dao = new TbContact();

 

       // columns START

       private String ID = "id"; // 主键

       private String NAME = "name"; // 姓名

 

       public TbContactsetId(java.lang.Integer value) {

              set(ID,value);

              returnthis;

       }

 

       public java.lang.Integer getId() {

              return get(ID);

       }

 

       public TbContact setName(java.lang.String value) {

              set(NAME, value);

              returnthis;

       }

 

       public java.lang.String getName() {

              return get(NAME);

       }

 

       ……………………………………

}

         Service集成BaseService,主要是Controller中复杂业务处理,将Controller中大量代码或者重复代码进行封装,统一调用。由于联系人模块相对简单,这里就没有Service代码了。

4.2.2    页面代码

         后台模板主要是增删改查,jfinalcms使用beetl模板,这里就不多介绍了。

         页面主要以${module}_名开头,包含add(添加)、edit(编辑)、list(列表)、view(查看)几个页面。各个页面页格式相对统一,可以进行参考。

         以下为add页面代码,添加页面可以与edit页面公用~只是传递参数均为空。

<%

       include("contact_edit.html"){}

%>

         以下为edit页面代码,首先edit页面采用了beetl安全模式,保证参数为空不报异常,这样能够让add很好的引用。其次后端页面都采用了公共的头部标签head.html,列表和查看页面也是如此,不在赘述。

       在编辑页面对js验证进行了封装,只需要添加validvalidname属性就可以实现js验证功能,提交时调用 validForm()方法即可。

<%

DIRECTIVE SAFE_OUTPUT_OPEN;

var headContent = {

       include("/pages/template/head.html"){}

%>

<script type="text/javascript">

var oper= {

       save:function(id){

              if(!validForm()) {

                     returnfalse;

              }

              id =id || '0';

              var url = 'admin/contact/save/'+id;

              form1.action= url;

              form1.submit();

              returntrue;

       }     

};

</script>

 

<%

};

var bodyContent = {

%>

<form name="form1"action="" method="post"class="form-horizontal" role="form">

       <input type="hidden"name="model.id" value="${model.id}"/>

       <table class="table">

              <%// 列表头部 %>

              <tr>

                     <td>姓名</td>

                     <td>

                                   <input class="form-control"type="text" name="model.name"value="${model.name}"

                                          valid="vtext" validname="名称"  />

                     </td>

              </tr>

                            ………………………………

                            <tr>

                     <td>说明</td>

                     <td>

                            <textarea class="form-control"rows="3" cols="30"name="model.remark">${model.remark}</textarea>

                     </td>

              </tr>

       </table>

      

       <div style="height: 50px;clear: both;">&nbsp;</div>

       <nav class="navbarnavbar-default navbar-fixed-bottom">

         <div class="container" style="padding: 5px 0px 5px 0px;text-align: center;">

              <button class="btnbtn-primary" onclick="returnoper.save(${model.id!'0'});">保 存</button>

              <button class="btnbtn-default" onclick="closeIframe();returnfalse;">关 闭</button>

         </div>

       </nav>

</form>

<%}; %>

 

<%layout("/pages/template/_layout.html",{head:headContent,body:bodyContent}){%>

DIRECTIVE SAFE_OUTPUT_CLOSE;

<%}%>

         下面是列表页代码, 方法调用以oper对象调用形式封装。增加、修改、查看页面以弹框形式展示,这样可以很好的保存列表页查询条件。查看页面关闭不会刷新列表,添加和修改页面关闭后均会刷新列表,均已封装为Iframe方法

paginator属性为列表分页查询调用方法。

showMenu方法为菜单选中展现,具体参数在菜单管理进行配置。

menu.html为菜单栏,各页面直接引用即可。paginator.html为分页栏,固定获取page参数进行处理。

列表头部设置设置class="sorting"为支持排序功能,name字段为传到后台的参数值。

<%

       varheadContent = {

       include("/pages/template/head.html"){}

%>

 

<script type="text/javascript">

var oper;

jQuery(function($) {

       // 页面方法

       oper = {

              width: 400,

              height: 430,

              form :document.form1,

              list :function() {

                     var url = 'admin/contact/list';

                     this.form.action = url;

                     this.form.submit();

              },

              view :function(id) {

                     var url = 'admin/contact/view/'+id;

                     var title = '查看联系人';

                     Iframe(url,this.width, this.height, title, false, false, false, EmptyFunc);

              },

              add : function() {

                     var url = 'admin/contact/add';

                     var title = '添加联系人';

                     Iframe(url,this.width, this.height, title);

              },

              edit :function(id) {

                     var url = 'admin/contact/edit/'+id;

                     var title = '修改联系人';

                     Iframe(url,this.width, this.height, title);

              },

              del : function(id) {

                     var url = 'admin/contact/delete/'+id;

                     var title = '确认要删除该联系人信息?';

                     Confirm(title,function() {

                            form1.action= url;

                            form1.submit();

                     });

              }

       };

       //显示Menu索引

       showMenu('page_contact');

});

 

//分页

varpaginator = function(page) {

       oper.list();

};

 

</script>

 

<%

};

 

var bodyContent = {

%>

       <form name="form1"action="" method="post"  class="form-inline"role="form">

              <!-- 菜单 -->

              <%include("/pages/template/menu.html"){} %>

 

              <div class="tableSearch">

                     <//查询列表 %>

                     <div class="form-group">

                                   <input class="form-control"type="text" name="attr.name"value="${attr.name!''}"

                                          placeholder="请输入姓名" required='required' />

                     </div>

        

                    

                     <button type="button"class="btn btn-default" onclick="oper.list();"name="search">

                                   <span class="glyphiconglyphicon-search"></span>查 询

                     </button>

                     <button type="button"class="btn btn-default" onclick="resetForm();">

                                   <span class="glyphiconglyphicon-refresh"></span>重 置

                     </button>

                     <button type="button"class="btn btn-default" onclick="oper.add();">

                                   <span class="glyphiconglyphicon-plus"></span>新 增

                     </button>

              </div>

             

              <!-- 数据列表 -->

              <table class="tabletable-striped table-bordered table-hover">

                     <thead>

                            <tr>

                                   <th>序号</th>

                                   <// 列表头部 %>

                                   <th name="name"class="sorting">姓名</th>

                                   <th name="phone"class="sorting">手机号</th>

                                   <th name="email"class="sorting">Email</th>

                                   <th>说明</th>

 

                                   <th>操作</th>

                            </tr>

                     </thead>

                     <tbody>

                     <%for(item in page.list){ %>

                            <tr>

                                   <td>${itemLP.index }</td>

                                   <// 列表内容 %>

                                   <td>${item.name}</td>

                                   <td>${item.phone}</td>

                                   <td>${item.email}</td>

                                   <td title="${item.remark}">

                                   <%if (strutil.length(item.remark) > 6) { %>

                                          ${strutil.subStringTo(item.remark,0, 6)}...

                                   <% } else { %>

                                          ${item.remark}

                                   <} %>

                                   </td>

        

                                   <td>

                                   <a href="javascript:void(0);"class="btn btn-sm btn-success" onclick="oper.view(${item.id});">查看</a>

                                   <a href="javascript:void(0);"class="btn btn-sm btn-primary" onclick="oper.edit(${item.id});">修改</a>

                                   <a href="javascript:void(0);"class="btn btn-sm btn-danger" onclick="oper.del(${item.id});">删除</a>

                                   </td>

                            </tr>

                     <%} %>

                     </tbody>

              </table>

              <%include("/pages/includes/paginator.html"){}%>

       </form>

<%};%>

 

 

<% layout("/pages/template/_layout.html",{head:headContent,body:bodyContent}){%>

<%}%>

         view页面相对简单,只是一个数据展示,这里就不多做介绍了。

         如果你看到了这里,有了后台代码和前台页面,联系人模块就完成了,配置一下菜单就能看到效果了~!~

       温馨提示:列表分页、菜单栏、js验证和列表排序功能封装实现这里先不做说明,有兴趣的可以查看源码。

4.3     前台模板开发

4.3.1    java代码

         前台CommonController为整个项目默认入口,项目根目录、登陆、登出等。

         TemplateService为前台模板接口,这里的TemplateService通过beetl方法注册实现接口调用。再config中进行注册,代码如下groupTemplate.registerFunctionPackage("temp",TemplateService.class);这个不明白可以看下beetl文档,这样就实现了页面调用temp.article(1)返回文章对象。

         后台service封装实现具体方法,template进行service调用提供接口。这样前台页面直接访问template接口,很好的实现了代码前后端代码分离。当然前台的java还进行了公共Controller的封装以及公共参数的传递。

         如文章、评论、消息、个人信息、注册、标签等公共Controller以及统一前台参数传递拦截器FrontInterceptor。截图如下:

         有一些比较个性化的东西,我这里就没有封装接口,直接创建了一个Controller。

4.3.2    模板代码

         前台模板现在有几个了~这里我们以最简单的website单页滚动网站为例。

         这里主要得模板页面就是home.html。项目栏目通过后端管理进行配置,默认是home目录下的${urlkey}.html文件,website配置的是home。当然也可以直接通过栏目管理配置模板路径。

         这里我们主要看顶部的接口调用,temp.articlePage返回当前站点的前20条文章列表,进行列表数据展示。temp.article返回当前站点设置的默认文章,进行一些属性展示。

<%

// 获取前20个文章

var articlePage = temp.articlePage(1, 20, session.site.model.site_folder_id);

var homeArticle =temp.article(session.site.model.site_article_id);

%>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

 

<head>

       <title>${HEAD_TITLE }</title>

       <meta http-equiv="Content-Type"content="text/html; charset=UTF-8">

       <link rel="icon"href="${BASE_PATH }favicon.ico" />

       <link rel="shortcuticon" href="${BASE_PATH }favicon.ico"/>

       <meta http-equiv="pragma"content="no-cache">

       <meta http-equiv="cache-control"content="no-cache">

       <meta http-equiv="expires"content="0">

       <meta name="keywords"content="${HEAD_KEYWORDS }">

       <meta name="description"content="${HEAD_DESCRIPTION }">

      

       <!-- IE兼容处理 -->

       <meta http-equiv="X-UA-Compatible"content="IE=edge">

    <meta name="viewport"content="width=device-width, initial-scale=1">

 

       <link rel="stylesheet"type="text/css" href="http://cdn.bootcss.com/fullPage.js/2.7.6/jquery.fullPage.min.css"/>

       <script src="http://cdn.bootcss.com/jquery/1.8.3/jquery.min.js"></script>

       <script type="text/javascript"src="http://cdn.bootcss.com/fullPage.js/2.7.6/jquery.fullPage.min.js"></script>

       <link href="http://cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css"rel="stylesheet">

 

<style>

       ……………………

</style>

 

<script type="text/javascript">

varanchorsArray = [];

varnavigationTooltipsArray = [];

<% for(item in articlePage.list){ %>

       anchorsArray.push('j${item.id}');

       navigationTooltipsArray.push('${item.title}');

<% }%>

       $(document).ready(

                     function() {

                            $('#fullpage').fullpage({

                                   anchors: anchorsArray, //[ 'j0','j1','j2','j3','j4','j5','j6'  ],

                                   navigation: true,

                                   navigationPosition: 'right',

                                   navigationTooltips:  navigationTooltipsArray // [ '英雄联盟','德玛西亚皇子', '德邦总管','黑暗之女', '皎月女神','寒冰射手','联系我们']

                                   ,afterLoad:function(anchorLink, index){

                                          $('ul.nav li').removeClass('active');

                                          $('ul.nav li#nav_'+anchorLink).addClass('active');

 

                                   }

                            });

                     });

</script>

 

</head>

<body>

       <nav class="navbarnavbar-default navbar-fixed-top">

              <div class="container-fluid">

                     <div class="navbar-header">

                            <a class="navbar-brand"href="#"><img alt="logo"src="${flyfox.getImage(homeArticle)}" width="24" height="24">

                                   ${homeArticle.title}

                            </a>

                     </div>

                     <div class="container">

                            <ul class="nav navbar-navnavbar-right">

                                   <%for(item in articlePage.list){ %>

                                          <li id="nav_j${item.id}"><a href="#j${item.id}">${item.title}</a></li>

                                   <%} %>

                            </ul>

                     </div>

              </div>

 

       </nav>

 

 

       <div id="fullpage">

              <%for(item in articlePage.list){ %>

                     <div class="section"id="section${itemLP.index}" style="background-image: url(${flyfox.getImage(item)});">

                            ${item.content!}

                     </div>

              <%} %>

             

       </div>

 

</body>

</html>

到这里简单的前台模板功能就介绍完了。

4.4    前台模板接口

4.4.1    前台默认入口

         首先上一章节提到了默认入口,这里我要详细说明下默认访问路径规则。FrontInterceptor前台拦截器会传递友情链接、关于我们、推荐文章和栏目信息数据。然后根目录会默认访问当前站点设置的栏目,如果未设置那么展示TbFolder.ROOT栏目,如果url传递了目录Id或者目录urlKey那么会访问对应的栏目。跳转规则是优先处理跳转jumpUrl,其次是配置的模板路径,如果都没有按照默认home/${urlKey}.html的规则跳转。从这里可以看出,简单的栏目规则就是每个站点下面栏目是和urlKey一一对应的。

       如我们添加一条栏目,id:101 urlKey:jflyfox那么我们可以通过“项目地址/101.html”访问,也可以通过“项目地址/jflyfox.html”访问。但是默认的栏目菜单生成规则是优先使用urlKey。具体可以看模板下includes/ header_menu.html实现。

@ControllerBind(controllerKey= "/")

publicclass CommonController extendsBaseProjectController {

 

       …………………………

 

       /**

        * 首页,菜单

        *

        * 2015年5月25日 下午11:00:28 flyfox330627517@qq.com

        */

       @Before(FrontInterceptor.class)

       publicvoid index() {

              // new FrontService().menu(this);

              int folderRoot = TbFolder.ROOT;

              SessionSitesite = getSessionSite();

              IntegersiteFolderId = site.getModel().getSiteFolderId();

              if (siteFolderId != null&& siteFolderId > 0) {

                     folderRoot= siteFolderId;

              }

 

              StringfolderStr = getPara();

              IntegerfolderId = folderRoot;

 

              if (folderStr != null) {

                     if (NumberUtils.parseInt(folderStr)> 0) {

                            folderId= NumberUtils.parseInt(folderStr);

                     }else {

                            folderId= NumberUtils.parseInt(FolderService.getMenu(folderStr));

                     }

              }

 

              if (folderId == null ||folderId <= 0) {

                     folderId= folderRoot;

              }

              // 活动目录

              setAttr("folders_selected", folderId);

 

              TbFolderfolder = new FolderService().getFolder(folderId);

              setAttr("folder", folder);

 

              setAttr("paginator", getPaginator());

 

              // seo:title优化

              StringfolderName = (folder == null ? "" : folder.getStr("name") + "- ");

              setAttr(JFlyFoxUtils.TITLE_ATTR, folderName + getAttr(JFlyFoxUtils.TITLE_ATTR));

 

              // 栏目跳转规则

              StringjumpUrl = folder.getJumpUrl();

              Stringpath = folder.getPath();

              StringurlKey = folder.getKey();

              if (StrUtils.isNotEmpty(jumpUrl)) {

                     redirect(jumpUrl);

              } elseif (StrUtils.isNotEmpty(path)) {

                     renderAuto(path);

              } else {

                     renderAuto(Home.PATH + urlKey + ".html");

              }

 

       }

 

……………………………………

}

         其次是FrontAlbumImageController和FrontAlbumVideoController分别是图片和视频模块统一入口,现在功能相对简单,这里就不详细介绍了。

4.4.2    前台接口

         前台接口主要是TemplateService这个类,提供方式是利用beetl函数注册方式,如groupTemplate.registerFunctionPackage("temp",TemplateService.class);代码,这样我们就可以通过temp.${method}进行调用了。

         下面是接口代码,例如通过temp.countView(100),我们就能获取到文章id为100的浏览量,temp. articlePage(23,1,20)我们就能够获取到栏目id为23下面的前20条文章。

       接口主要提供了文章列表、文章内容、浏览量、评论量,栏目标签,栏目、滚屏图片列表和提醒列表等信息。

packagecom.jflyfox.modules.front.template;

 

……………………

 

/**

 * 模板方法接口

 *

 * 2016年1月18日 下午6:05:54 flyfox 330627517@qq.com

 */

publicclass TemplateService extendsBaseService {

 

       privatefinalstatic FrontCacheService service = new FrontCacheService();

 

       privatefinalstatic ArticleLikeCache articleLikeservice = newArticleLikeCache();

 

       /**

        * 获取浏览数

        *

        * 2015年6月2日 下午6:30:56 flyfox330627517@qq.com

        *

        * @paramarticleId

        * @return

        */

       publicstaticint countView(int articleId) {

              TbArticlearticle = service.getArticleCount(articleId);

              return article == null ? 0 :article.getCountView();

       }

 

       /**

        * 获取评论数

        *

        * 2015年6月2日 下午6:30:56 flyfox330627517@qq.com

        *

        * @param articleId

        * @return

        */

       publicstaticint countComment(int articleId) {

              TbArticlearticle = service.getArticleCount(articleId);

              return article == null ? 0 :article.getCountComment();

       }

 

       /**

        * 获取浏览数

        *

        * 2015年6月2日 下午6:30:56 flyfox330627517@qq.com

        *

        * @param articleId

        * @return

        */

       publicstaticboolean isLike(int userId, int articleId) {

              returnarticleLikeservice.isLike(userId, articleId);

       }

 

       /**

        * 获取标签信息

        *

        * 2015年5月25日 下午11:49:58 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        *           目录

        * @return

        */

       public List<TbTags> tagsListByArticle(int articleId) {

              returnservice.getTagsByArticle(articleId);

       }

 

       /**

        * 获取标签信息

        *

        * 2015年5月25日 下午11:49:58 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        *           目录

        * @return

        */

       public Page<TbTags> tagsPageByFolder(int pageNo, int pageSize, int folderId) {

              returnservice.getTagsByFolder(newPaginator(pageNo, pageSize), folderId);

       }

 

       /**

        * 获取标签信息

        *

        * 2015年5月25日 下午11:49:03 flyfox330627517@qq.com

        *

        * @param paginator

        * @return

        */

       public Page<TbTags> tagsPage(intpageNo, int pageSize, intsiteId) {

              returnservice.getTags(newPaginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 查询文章,展示的和类型为11,12的推荐文件,前10个

        *

        * 2015年4月29日 下午4:48:24 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folder_id

        * @return

        */

       public Page<TbArticle> articlePageRecommend(int pageNo, int pageSize, int siteId) {

              returnservice.getRecommendArticle(new Paginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 返回最新文章

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @return

        */

       public Page<TbArticle> articlePageTop(int pageNo, int pageSize, int siteId) {

              returnservice.getNewArticle(newPaginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 返回文章列表

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public Page<TbArticle> articlePageSite(int pageNo, int pageSize, int siteId) {

              returnservice.getArticleBySiteId(new Paginator(pageNo, pageSize), siteId);

       }

 

       /**

        * 返回文章列表

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public Page<TbArticle> articlePage(int pageNo, int pageSize, int folderId) {

              returnservice.getArticle(newPaginator(pageNo, pageSize), folderId);

       }

 

       /**

        * 返回文章列表不走缓存

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public Page<TbArticle> articlePageNoCache(int pageNo, int pageSize, int folderId) {

              returnservice.getArticleByNoCache(new Paginator(pageNo, pageSize), folderId);

       }

 

       /**

        * 返回对应文章

        *

        * 2015年5月24日 下午10:52:05 flyfox330627517@qq.com

        *

        * @param paginator

        * @param folderId

        * @return

        */

       public TbArticle article(intarticleId) {

              returnservice.getArticle(articleId);

       }

 

       /**

        * 获取栏目滚动图片

        *

        * 2016年1月28日 下午5:28:25 flyfox330627517@qq.com

        *

        * @param folderId

        * @return

        */

       public List<TbFolderRollPicture> rollPicture(int folderId) {

              returnservice.getRollPicture(folderId);

       }

 

       /**

        * 获取公告信息

        *

        * 2016年1月28日 下午5:29:47 flyfox330627517@qq.com

        *

        * @param folderId

        * @return

        */

       public List<TbFolderNotice> notice(int folderId) {

              returnservice.getNotice(folderId);

       }

 

}

         除了默认出口和上述接口外,我们还提供了一些公共Controller和个性Controller,如下图所示:

         这些Controller大致实现了关于我们、建议、文章查看、评论、消息、Oauth2登陆、个人信息、注册信息和标签功能。

4.5    项目工具类

         base下包含了Model和Controller的父类:Model实现了增删改统一日志,自定义缓存查询(类似jfinal自带缓存查询)以及查询方法扩展,部分实现在BaseModel中。Controller实现了获取和设置用户session、站点session获取、自动识别移动和PC端跳转、带提示跳转,获取分页、获取form等功能,部分实现在BaseController中。

         beetl下包含了beetl公共方法封装:数据字典获取、图片路径转换、菜单地址获取以及常规字符串处理方法。

         config下是jfinal配置文件入口,里面有大量的jfinal初始化配置,这里就不一一说明了。

         controller下是ueditor和umeditor的后台实现代码。

         interceptor是通用拦截器:包含SEO属性设置,页面访问量统计,站点Session处理、缓存更新以及用户Session唯一键设置。在jflyfox_jfinal中还包含登陆session拦截、Session属性设置、form获取统一处理。

         util下是一些公共配合及方法:包含文章访问量和评论量缓存,验证码生成、图片属性获取、缓存管理统一入口、上传组件封装以及常量类等。

         项目内部工具类大致如下图所示:


 

5      jfinal cms项目依赖

jfinal cms采用maven结构,依赖与作者的其他两个项目。

jflyfox_base项目

jfinal_base提供了java的工具类,如StrUtils,NumberUtils,DateUtils 等

github:https://github.com/jflyfox/jflyfox_base

osc@git:http://git.oschina.net/flyfox/jflyfox_base

l  jflyfox_jfinal项目

jflyfox_jfinal是对Jfinal等组件的封装。

         包含controller,model,form,service基础类封装。

         对分页进行了后台和前台的实现。

         加入了自动扫描model和controller以及注解支持。

         实现了ueditor后台Controller代码。

         加入了页面增删改查代码自动生成功能,可通过beetl模板进行配置。

         实现了SessionAttrInterceptor、页面和手机设备判断拦截器以及BasePathHandler。

github:https://github.com/jflyfox/jflyfox_jfinal

osc@git:http://git.oschina.net/flyfox/jflyfox_jfinal

6      联系作者

作者:FLY的狐狸

jfinal cms交流群:568909653

QQ:369191470

邮件:369191470@qq.com

源码地址:http://git.oschina.net/flyfox/jfinal_cms


 

7      开源赞助

支付宝支付

微信支付

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值