快速开始

Giiwa 如何安装和开发,Model、View示例, ...

概要介绍

Giiwa 是一个Java Web的快速开发框架。你会发现使用Giiwa框架开发您的应用会比以前更加容易和简单。

简化MVC模式为更加简单的MV模式, 框架在运行期的模块管理功能使得模块的重用和扩展变得更加容易。

让我们从一个简单示例模块开始。

目录

MV开发

相比MVC,Model-View开发更加简单简洁,约定优于配置, Model 负责处理请求和输出, Web请求URL直接对应Model类名和Model的方法,/[class name]/[path], View 是一系列的包含Velocity模版的HTML。

Browser访问Model时, 事实上是Giiwa框架先接收到访问请求, 根据请求的URL在模块中去寻找与之匹配的Model类,然后调用对应的处理方法, 这过程对开发透明。如下示例, 访问:/demo, 会直接调用到 demo.onGet() 方法, 访问:/demo/hello会调用到 demo.hello() 方法。

注意:没有使用@Path注明的方法是不可以被访问的。

public class demo extends Model {

  @Path()
  public void onGet() {
    // TODO do something
    this.show("/demo.html");
  }

  @Path(path = "hello")
  public void hello() {
    // TODO do something
    this.set("time", lang.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"));
    this.show("/demo.html");
  }
}

安装

介绍如何安装、设置和管理Giiwa和相关的软件。

安装Giiwa

强烈推荐你使用Linux作为Giiwa安装服务器, 下载最新Giiwa运行包文件, 或下载 历史归档.

依赖软件包

  • JDK 1.8+
  • Mongo 1.6+

选装软件包

  • Postgresql 8.4+, Mysql 5.4+, Oracle 10i
  • Memcached 1.4+
  • Ningx 1.2+

安装 JDK

需要JDK 1.8 或更高版本, 请参考 Oracle Java 下载和安装JDK。

#设置环境变 JAVA_HOME /etc/profile

export JAVA_HOME=JDK install path
export PATH=$PATH:$JAVA_HOME/bin

安装 Mongo

Giiwa 使用 Mongo 1.6以上 作为基础数据库, 请参考 Mongo Manual 去安装和设置Mongo数据库.

安装 Postgresql

Giiwa支持Postgresql 9.2以上关系数据库,请参考 Postgresql Manual 去下载安装和配置Postgresql数据库。

centos/redhat

yum install postgresql

ubuntos

apt-get install postgresql

mac

brew install postgresql

Install Mysql

Giiwa支持Mysql 5.4以上关系数据库,请参考 Mysql Manual 去下载安装和配置Mysql数据库。

centos/redhat

yum install mysql

ubuntos

apt-get install mysql

mac

brew install mysql

Install giiwa

下载Giiwa运行包文件,解压启动。

cd /opt
wget -c http://giiwa.org/docs/giiwa-1.0.tar.gz
tar xzf giiwa-1.0.tar.gz
./giiwa/bin/startup.sh

Giiwa 使用 tomcat 8 作为内置Web服务器,您可以根据你的需要,修改 conf/server.xml

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <Service name="Catalina">

    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
      maxThreads="1000" minSpareThreads="4"/>

    <Connector host="localhost" executor="tomcatThreadPool"
      port="8080" protocol="HTTP/1.1"
      connectionTimeout="20000"
      URIEncoding="UTF-8"
      redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="${catalina.home}/giiwa" reloadable="false" useHttpOnly="true"/>
        </Host>
    </Engine>
  </Service>
</Server>

安装 appdog

在Linux 环境中,Giiwa 会自动安装设置 appdog, 你可以根据你的需要,修改 /etc/appdog/apps.conf

[global]
log=/var/log/appdog.log
pid=/var/run/appdog.pid
port=19001

[app:giiwa]
start=/opt/giiwa/bin/startup.sh
pattern=/opt/giiwa/
path=/opt/giiwa/bin
user=giiwa
check=0.5
enabled=1

安装 nginx

Nginx 是最流行的http/web服务器软件之一, 请参考 Nginx Manual 下载安装和设置Nginx。

下面是一些最重要的配置参数,但不是必须的,尽可能添加到 /etc/nginx/nginx.conf 的 http{...}部分

  gzip    on;
  gzip_buffers 	4 16k;
  gzip_http_version 1.0;
  gzip_comp_level 5;
  gzip_types    text/plain text/css text/javascript application/x-javascript application/json application/javascript;
  gzip_proxied 	any;
	
  client_max_body_size 35m;
  proxy_connect_timeout 600;
  proxy_send_timeout 600;
  limit_conn_zone $binary_remote_addr zone=perip:10m;

使用如下内容创建 /etc/nginx/conf.d/demo.conf ,

upstream demo {
	server 127.0.0.1:8080;
}
server {
    listen       80;
    server_name  giiwa.org;

	location / {
        proxy_set_header Port 80;
        proxy_set_header X-Real-IP $remote_addr;

        root    /opt/giiwa/html;
        try_files /docs/index.html @g1;
    }

    location @g1 {
        proxy_set_header Port 80;
        proxy_set_header X-Real-IP $remote_addr;

        proxy_pass http://demo;
    }
}

注:giiwa在运行期间,会自动把访问的静态资源文件输出到 {giiwa.home}/html 目录中,为了加速返回,可以直接设置 nginx 从 {giiwa.home}/html 下查找文件。

设置 Giiwa

第一次访问Giiwa的时候,框架会自动检测是否配置数据库,如果没有配置数据库,则会自动导向 /setup 配置基本信息, 跟随提示完成基本配置,注意如果错误配置可能导致下次无法启动或进入系统,则需要手动修改 {giiwa}/giiwa.properties

设置

http://{host:port}/admin , 进入管理页面/系统配置/系统设置

系统

系统基本配置信息, 语言和运行级别。

SMTP

邮件发送配置,在你的模块中使用 Email.send(...)

模块管理

你可以上传一个新的模块,开启,关闭、或删除一个模块。

日志管理

除了在 logs/giiwa.log 纪录运行日志, Giiwa 还根据运行级别纪录额外的3中日志信息。

操作日志

操作日志是通过 OpLog.warn/info/error APIs纪录的。

访问纪录

运行级别为 debug , Giiwa 会自动纪录访问信息,用于做性能分析。如果运行级别为 production 则不会纪录。

索引纪录

如果运行级别在 debug ,Giiwa会自动根据你的查询条件,提取应该创建索引以优化查询性能的关键字段。

文件仓库

提供一种在分布式系统中,通过相同的标示,访问相同的文件的能力, 请参考 文件仓库 APIs 去了解更多信息, 文件仓库中的文件是通过ID访问的,文件的名称可以相同。在这里,你可以查看或删除文件。

备份

备份整个数据库到 /opt/backup 中,此功能需要你的关系数据库,Mongo,Giiwa安装在相同的服务器上。

用户管理

Giiwa 管理整个授权和认证。授权通过定义用户、角色和权限来实现的。认证是通过检查用户是否具有设定门限的Access Key。

权限门限可以通过 Path 注释定义,也可以通过 user.hasAccess("access.gorup.name") 检查。

@Path(path="something", login=false,access="access.group.name", method=Model.METHOD_GET|Model.METHOD_POST)
public void something(){
	
}

角色管理

创建,修改,删除角色, 角色可以包含多个权限名称,权限名称的翻译在 i18n 的对应语言中可以定义。

快速开始

从一个简单的模块开始了解Model,View和Bean.

第一个模块

在Admin/模块管理/创建模块,创建一个新的模块,输入必要的模块名称,包名等,框架会自动生成一个Eclipse的工程的zip文件包。

下载并解压zip文件包到你的Eclipse的 workspace, 在Eclipse中导入项目,选择刚才解压的项目。

build.xml

ant 编译脚本,用于编译模块,并生成模块包zip文件,能在模块管理中自动识别并安装的。

depends

包含了模块开发的必要的依赖jar文件,这些jar文件不会被打包到模块包文件中。

src/i18n

语言翻译包文件, 包括了 en_us.lang and zh_cn.lang .

src/model/java

Model原代码文件。

src/module.ini

模块配置文件。

src/view

包含所有的HTML和Resource文件,使用 show("/demo.html") or module.getFile("/demo.html") 可以直接使用和定位到这些文件。

src/view/install/meno.json

菜单文件, 你可以参照demo定义你的后台管理菜单、权限和入口,也可以创建缺省的角色。

src/WEB-INF/lib

你可以放置你的特设依赖jar文件,该目录中的所有文件会被打包到模块文件包中,并发布到 giiwa/WEB-INF/lib/

Model 设计

Model 负责处理、返回处理结果, 请参考Model了解更多.

public class demo extends Model {

  @Path()
  public void onGet() {
    // TODO do something
    this.show("/demo.html");
  }

  @Path(path = "hello")
  public void hello() {
    // TODO do something
    this.set("time", lang.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"));
    this.show("/demo.html");
  }

  @Path(path = "json")
  public void json() {
    // TODO do something
    JSONObject jo = new JSONObject();
    jo.put(X.STATE, 200);
    jo.put("result", "ok");
    this.response(jo);
  }

}

@Path

Java注释类,用户说明输出Model的Web接口, 下面是 @Path 的每一个域说明

path=X.NONE (输出的方法名称)
method=Model.METHOD_GET|MOdel.METHOD_POST(处理的请求)
login=false (是否需要登录)
access=X.NONE (权限令牌)
accesslog=true (在debug运行级别,是否纪录访问日志)

Request

Giiwa 框架封装GET、POST、multipart/form-data 请求参数到Model中,请提供统一方法获取请求参数数据。

String this.getString("name") 获取名称为“name”的请求参数,并自动转义HTML的Tag标签,以防止直接输出这些参数时,破坏HTML的完整性, 请参考 Model 以了解更多。

Response

this.show(String view) 解析view页面并且输出, view为HTML的文件名称。

View 设计

View 是一组 Velocity 模版或资源文件, 直接存放在 view 目录, 框架在模块包中查找view的时候,以模块的ID,从大到小, 请参考 View 以了解更多。

Bean

所有数据库访问都需要从Bean继承, 并使用 @DBMapping 说明Mapping的数据表名称。

@DBMapping(collection = "tbldemo")
public class Demo extends Bean {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public String getId() {
		return this.getString(X._ID);
	}

	public String getName() {
		return this.getString("name");
	}

	public String getContent() {
		return this.getString("content");
	}

	// ------------

	public static String create(V v) {
		/**
		 * generate a unique id in distribute system
		 */
		String id = "d" + UID.next("demo.id");

		while (exists(id)) {
			id = "d" + UID.next("demo.id");
		}
		Bean.insert(v.set(X._ID, id), Demo.class);
		return id;
	}

	public static boolean exists(String id) {
		return Bean.exists(new BasicDBObject(X._ID, id), Demo.class);
	}

	public static int update(String id, V v) {
		return Bean.updateCollection(id, v, Demo.class);
	}

	public static Beans load(BasicDBObject q, int s, int n) {
		return Bean.load(q, new BasicDBObject(X._ID, 1), s, n, Demo.class);
	}

	public static Demo load(String id) {
		return Bean.load(new BasicDBObject(X._ID, id), Demo.class);
	}

	public static void delete(String id) {
		Bean.delete(new BasicDBObject(X._ID, id), Demo.class);
	}

}

请参考 Bean 以了解更多。

发布模块

build

在模块工程的根目录,运行 ant , 如果编译成功打包模块zip包文件 {module.name}_{version}_{buildno}.zip

发布

在Giiwa管理后台,系统配置->模块管理, 上传模块zip包文件, 如果需要重启服务器。

高级开发

这一节, 我们将介绍关于Giiwa更多的开发功能。

介绍

Model

Model 负责处理所有请求,并返回处理结果, 请参考 APIdocs Model 以了解更多。

public class demo extends Model {

  @Path()
  public void onGet() {
    // TODO do something
    this.show("/demo.html");
  }

  @Path(path = "hello")
  public void hello() {
    // TODO do something
    this.set("time", lang.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"));
    this.show("/demo.html");
  }

  @Path(path = "json")
  public void json() {
    // TODO do something
    JSONObject jo = new JSONObject();
    jo.put(X.STATE, 200);
    jo.put("result", "ok");
    this.response(jo);
  }

}

@Path

Model中方法的Web输出说明类,用于输出Model中的方法,能被Web直接访问, 下面是关于 @Path 的说明。

path=X.NONE (no path defined)
method=Model.METHOD_GET|MOdel.METHOD_POST(handle all request method)
login=false (no required login)
access=X.NONE (not required access key name)
accesslog=true (record the accesslog if the run level is debug)

Request

Giiwa 框架封装GET,POST,multipart/form-data 的请求参数到Model中,通过下面方法可以很容易的访问请求参数。

String this.getString("name") 获取参数名称为“name”的参数,请转义HTML中Tag信息,以防止直接输出的时候破坏原有HTML的完整性。

int this.getInt("name") 获取 int 类型的参数值。

long this.getLong("name") 获取 long 类型的参数值。

String[] this.getStrings("name") 获取所有的名称为“name”的参数组, 所有参数中的HTML Tag都会被转义。

FileItem this.getFile("name") 获取文件参数。

JSONObject this.getJSON() 封装所有参数到JSON中,并转义参数中的HTML Tag。

String this.getHtml("name") 获取名称为“name”的参数的原始输入值,不做任何转义,在使用该方法的时候,获取的参数中可能包含HTML Tag, JS等代码。

Response

this.show(String view) 解析并输出view文件, view为相对View的路径名称, 框架会根据模块的ID, 从大到小寻找view的文件。

this.response(JSONObject) 直接输出JSON数据,并设置ContentType为"application/json;charset=UTF-8", 这个主要应用到RestAPI 或 AJAX APIs.

this.getOutputStream() 获取返回的OutputStream,可以直接向OutputStream中写入数据。

this.println(String) 直接打印输出到返回结果中。

Model如何传递数据到?

在Model总直接使用name设置数据 this.set(String name, Object data) , 在View中直接使用 $name 获取Model传递的数据。

this.set(name, object) 设置一个与名称name绑定的数据data。

this.set(Map<String, Object> map) 把map中的所有数据都设置到Model中。

Override

通常,如果我们想重用以前工程的功能,会直接从那个工程中拷贝过来,然后修改。

然后,在Giiwa中,你不再需要拷贝那个工程,只需要新建一个模块,实现需要修改的Web接口对应的方法,然后发布,新的Web APIs会自动替换老(小)的模块的方法,就这么简单。

重载资源文件更加简单,只需要在View中防止相同的文件名称就可以了。

View

View 是一组 Velocity 模版或资源文件,在 view 目录下, 框架查找资源文件时候,以模块ID, 由大到小。

Bean

所有的数据库存取必须继承 Bean, 并且使用 @DBMapping 说明Mapping的数据集, 请参考APIdocs Bean 以了解更多。

@DBMapping(collection = "tbldemo")
public class Demo extends Bean {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public String getId() {
		return this.getString(X._ID);
	}

	public String getName() {
		return this.getString("name");
	}

	public String getContent() {
		return this.getString("content");
	}

	// ------------

	public static String create(V v) {
		/**
		 * generate a unique id in distribute system
		 */
		String id = "d" + UID.next("demo.id");

		while (exists(id)) {
			id = "d" + UID.next("demo.id");
		}
		Bean.insert(v.set(X._ID, id), Demo.class);
		return id;
	}

	public static boolean exists(String id) {
		return Bean.exists(new BasicDBObject(X._ID, id), Demo.class);
	}

	public static int update(String id, V v) {
		return Bean.updateCollection(id, v, Demo.class);
	}

	public static Beans load(BasicDBObject q, int s, int n) {
		return Bean.load(q, new BasicDBObject(X._ID, 1), s, n, Demo.class);
	}

	public static Demo load(String id) {
		return Bean.load(new BasicDBObject(X._ID, id), Demo.class);
	}

	public static void delete(String id) {
		Bean.delete(new BasicDBObject(X._ID, id), Demo.class);
	}

}

@DBMapping 有两个域: collection 和 table。

collection="some" 用于说明数据存储在Mongo数据库中的那个数据表。

table="tablename" 用于说明数据存储在关系数据库中的表,该表需要被预先创建。

建议实现Bean中的 getter访问为你的变量名称,缺省的变量都存储在Bean的Map中。

User

框架实现了所有的会话管理,用户鉴权认证,你不能使用你自己的方式,但是你可以使用重载功能,重载框架中的 /user/login 以实现你的逻辑,或在你的模块中覆盖 /view/user/user.login.html 。 请参考 APIdocsUser 以了解更多信息。

如果用户在1小时内、在相同的IP地址,登录失败3次,用户会被锁定,如果你想解锁用户,你需要删除数据集 gi_userlock , 请参考APIdocs User.Lock 以了解更多信息。

Listener & Filter

Listener 用于模块处理模块启动或停止的事件,Filter用于挂载在某些URI上的特殊处理,比如:/user/login。 当模块启动,停止的时候,框架会自动根据模块配置文件 module.xml 中定义的的Listener,调用相应的APIs,module.xsd

<?xml version="1.0" encoding="UTF-8"?>
<module>
	<id>0</id>
	<name>default</name>
	<package>org.giiwa.app.web</package>
	<screenshot>/images/giiwa_128.png</screenshot>
	<version>1.0.2</version>
	<build>0</build>
	<enabled>true</enabled>
	<readme>Giiwa default module which managed the giiwa framework</readme>
	<listener>
		<class>org.giiwa.app.web.DefaultListener</class>
	</listener>
	<setting>
		<param name="fff" value="1"/>
	</setting>
	<filter>
		<pattern>/user/login</pattern>
		<class>org.giiwa.demo.Loginfilter</class>
	</filter>
</module>

下面是DemoListener,一个模块只能又一个Listener。

public class DemoListener implements IListener {

  static Log log = LogFactory.getLog(DemoListener.class);

  @Override
  public void onStart(Configuration conf, Module m) {
    // TODO Auto-generated method stub
    log.info("demo is starting ...");

    //do something
    Demo.cleanup();

  }

  @Override
  public void onStop() {
    // TODO Auto-generated method stub

  }

  @Override
  public void uninstall(Configuration conf, Module m) {
    // TODO Auto-generated method stub

  }

  @Override
  public void upgrade(Configuration conf, Module m) {
    // TODO Auto-generated method stub

  }

}

下面是Login的一些样例Filter,你可以正对不同的URI,拥有很多Filter .

public class LoginFilter implements IFilter{

  @Override
  public boolean after(Model m) {
    // TODO Auto-generated method stub
    User u = m.getUser();
    if(u != null) {
      //a user login
    }
    return true;
  }

  @Override
  public boolean before(Model m) {
    // TODO Auto-generated method stub
    return true;
  }

}

Configuration

有两种类型的配置, 本地的和全局的。

Config

本地配置文件是本节点的配置信息,参考 giiwa/giiwa.properties , 请参考APIdocs Config 以了解更多信息。

Global

在分布式环境中,访问全局的配置信息,配置信息存储在Mongon的 gi_config 数据集中,请参考APIdocs Global 以了解更多信息。

Cache

Giiwa 提供Cache APIs, 请参考APIdocs Cache 以了解更多。

支持 Memcached, Redis 或 本地文件。 关于Cache的配置信息在 giiwa/giiwa.properties

#cache.url=memcached://127.0.0.1:11211
#cache.url=redis://g01:6379
cache.group=demo

cache.url the cache 服务器的URL。

cache.group 用于区分不同系统使用相同Cache服务器时候的标示。

Task

Giiwa 使用线程池管理所有的任务,线程池的大小定义在 giiwa/giiwa.properties 中。

下面是创建一个自动任务。

    new Task() {

      @Override
      public void onExecute() {
        // TODO Auto-generated method stub
        // do something
      }

      @Override
      public String getName() {
        // TODO Auto-generated method stub
        return super.getName();
      }

      @Override
      public void onFinish() {
        // TODO Auto-generated method stub
        this.schedule(X.AMINUTE);
      }

    }.schedule(10);

任务名称 name 必须唯一,否则不会启动。

Repository

文件仓库,通过ID,分布式环境中能访问到相同的文件, 请参考APIdocs Repo 以了解更多信息。

文件仓库中文件支持断点续传, 请参考 Repo.store() 方法。

通过ID,使用 Repo.load(id) 获取文件仓库中文件的 inpustream。

文件仓库中的文件可以被Web 浏览器通过 /repo/{id}

如果文件仓库中文件不完整(在断点续传时),框架会在一天后自动清理。

Temporary

临时文件与文件仓库中文件一样,通过ID访问到相同的文件,不同的是,访问API不同, 请参考APIdocs Temp 以了解更多。

    File f = Temp.get(fid, name + ".zip");
    if (f.exists()) {
      f.delete();
    } else {
      f.getParentFile().mkdirs();
    }
    return "/temp/" + fid + "/" + name + ".zip";

临时文件也可以被Web浏览器通过 /temp/{id} , 临时文件会在一天后被清除。

其他

其他非常游泳的 APIs.

UID

用于在分布式系统中生成全局唯一号、序列号,或随机串,请参考 APIdocs UID 以了解更多信息。

VEngine

Velocity 引擎, 执行一段Velocity语句,可以应用在“可配置”的条件判断中,请参考APIdocs VEngine 以了解更多信息。

Notification

消息通知。 如果使用Email, 你需要在 系统配置->系统设置->SMTP,中配置SMTP信息; 如果你使用SMS,你需要实现 Sms.ISender 接口,并且注册 Sms.register();

Email

使用Email发送消息通知, 请参考APIdocs Email 以了解更多信息, 参考 SMTP 以了解配置。

SMS

使用SMS发送消息通知,请参考APIdocs SMS 以了解更多信息。