Liferay 6.1开发学习(一):环境搭建

一、资源准备

以下资源内容可以在Liferay的官方网站下载,1-3在http://www.liferay.com/downloads/liferay-portal/available-releases下载,Liferay IDE在http://www.liferay.com/downloads/liferay-projects/liferay-ide处下载。

  1. liferay-plugins-sdk-6.1.1(Plugins SDK)
  2. liferay-portal-6.1.1-ce-ga2(Bundled with Tomcat)
  3. liferay-portal-src-6.1.1-ce-ga2(Portal Source,可选)
  4. liferay IDE 1.6
  5. Java SDK(开发Java程序当然要安装,请配置好环境变量)

请将以上资源解压,推荐在某一个盘下面建立一个Liferay的目录,将IDE、SDK、Liferay-portal-6.1.1-ce-ga2和源码后解压后放到这个目录下面。

二、配置SDK

打开Liferay IDE,Window-->Preferences-->Liferay-->Installed Plugin SDK,点击右边的Add…按钮,选择Liferay plugins SDK的解压目录,如下图所示,点击OK,完成SDK的配置。

安装LiferaySDK

三、配置Liferay运行环境(以Tomcat为例)

打开Liferay IDE,Window-->Preferences-->Server-->Runtime Environment,点击右边Add…,在弹出的服务器选择里面选择Liferay,Inc-->Liferay v6.1 CE (Tomcat 7),请在“Create a new location server”打上勾。

点击Next,选择liferay-portal-6.1.1-ce-ga2的解压目录,如下图:

配置liferay运行环境

点击Finish完成Tomcat的配置;或者点击“Next”,此步骤可以在Liferay source location处选择Liferay的源码,或者跳过,推荐关联一下Liferay的源码,其他的两个可选,点击Finish完成Tomca的配置。

四、运行Liferay

经过上面的三步,在Liferay IDE(Eclipse 4.2)的下方的server面板处,可以看到Liferay 6.1 CE Server,现在点击运行。稍等片刻,待Tomcat启动完成后,打开浏览器输入http://localhost:8080/,在Liferay第一次运行的时候会出现一个配置向导(此功能为Liferay 6.1开始新增)。

Liferay配置向导

可以在此配置Liferay门户名称、使用语言、管理员的姓名、电子邮件,数据库的连接信息(先在数据库里面建立数据库lportal)等内容。如上图所示,如果出现“数据库连接不能建立。请检查您的连接设置。”请检查数据库是否已经建立,或者数据库名、帐号、密码等信息是否正确。

静心等待Liferay的初始化,可能需要几分钟时间,当Liferay初始化完成后,会自动跳转到配置保存成功的页面。

根据Liferay的提示进行密码设置、密码提示问题设置等,然后跳转到Liferay的主页。

五、Liferay IDE的其他配置

设置工作区编码

Liferay的开发环境我们需要工程的编码为UTF-8,新安装的Eclipse的默认编码为GBK,修改方式为WindowàPreferencesàGeneralàWorkspace,在这下面的Text file encoding处选择other,UTF-8。点击OK保存。

代码提示、控制台缓冲区的大小

可以参考前面的一篇博客:MyEclipse/Eclipse的一些技巧

中文字体偏小

可以参考之前一篇博客:解决eclipse3.7中文字体小

六、Liferay的大概使用介绍

在四的基础上,在右上角可以看到有一个访问,点击可以看到有控制面板、我的公共主页、我的个人主页、Liferay(这里显示的是在配置向导处设置的门户名称)。这几个内容是什么意思呢?

控制面板:这个算是liferay的后台管理。

我的公共主页:其他人可以看到的自己的页面内容,一般放置的如个人博客等展示给其他人看的内容。

我的个人主页:个人主页的东西是只有自己可以看到,其他人看不到的,一般放置自己的日程,收集的站点,常用的小工具等等内容,是自己的私有空间。

在控制面板里面主要分为个人信息、站点设置、门户设置、服务器信息等。如果是一般权限的用户只能看到个人信息。一般开发的管理功能的页面均放置在此控制面板处,后面介绍如何将自己开发的Portlet,放置在这里。

现在回到“我的公共页面”或者“我的个人主页”。依次介绍一下顶部左边各按钮的作用。

编辑控制:打上勾之后显示portlet的控制按钮,可以勾上和反选看一下效果。如果勾上仍然没有出现portlet的控制按钮,而说明当前登录的用户没有此portlet的控制权限。

管理:管理里面有页面、页面布局、站点页面、站点内容四个菜单。页面和站点页点功能类似,差别只在于点进去后的默认位置不同;站点内容和后台的控制面板处站点的内容一样。页面布局是调整当前页面使用什么样的布局,如一栏,两栏,三栏等这些内容。

添加:点击添加显示的是可以添加的portlet,默认的只有几个,点击更多,可以看到所有的有权限的portlet(不同的登录用户,根据权限的不同看到的portlet不同)。可以添加一些看看效果。

上页内容只是Liferay的一些大概介绍,不熟悉的可以随便点点,熟悉一下相关内容。

七、其他

1、可以尝试修改一个Liferay里面的eclipse.ini的里面的JVM内存配置,可以提高eclipse的响应,我将里面的-Xms和-Xmx均调到了768M。

2、可以修改一下Eclipse的代码提示等,参看http://www.huqiwen.com/2012/06/25/some-skill-about-myeclipse-eclipse/

3、Liferay的学习可以多看看Liferay官方网站的文档和WIKI,虽然质量不是很可,但也可以学到不少内容。

http://www.liferay.com/documentation/liferay-portal/6.1/development

http://www.liferay.com/community/wiki

Liferay 6.1开发学习(二):创建一个Portlet工程

使用Liferay的SDK创建一个简单的Portlet,此Portlet不包括业务逻辑、不包括数据库,只有简单的页面展现,用以说明Portlet的开发过程。

一、创建Portlet工程

1、打开Liferay IDE,File-->New-->Liferay Project

创建一个Portlet示例

2、为Portlet工程取名为Study,点击完成。或者点击下一步,默认选择Liferay MVC。

补充说明:

Portlet:这个是一般做Liferay开发选择的项目,一般说Liferay插件工程,通常都是指的Portlet工程。他的结构和内容和普通的WEB工程区别不大。

Hook:Liferay的Hook是什么东西呢?这个单词中文可以翻译成钩子。是用于重写或覆盖Liferay的一些默认方法或页面。为开发提供了一种在不直修改Liferay核心源码的情况下修改Liferay核心功能的方法。

Ext:扩展工程的开发方法是Liferay早期版本推荐的开发方法,在6.x版本之后对ext开发模式逐渐不再推荐,主要使用Portlet的开发方法。扩展开发可以继承Liferay的Portal的大部分接口方法,而Portlet里面可以使用的接口方法均是Services包里面暴露的API。但是和Liferay的核心工程耦合太大,当Liferay的版本升级时对Ext工程的影响太大,基本需要重新修改。所以一般不推荐使用Ext模式。

Layout:布局模板,Liferay的布局是可以定制的,如一个页面中是两栏式还是三栏式,每个栏里面又有几行等等,可以使用此模式进行快速开发。

Theme:主题包。Liferay IDE提供的可以帮助开发人员、设计人员快速开发Liferay主题包的功能,通过此模式提供的向导工具等,为主题包的开发大大的提供了便利。

Portlet部署

Liferay的开发大量依赖Ant(也可以使用maven),在Liferay的开过程中,编译、代码生成、打包、部署等都是基于Ant完成的。

1、Liferay 的IDE在Liferay的portlet开发模式下,ant面板默认是可见的,如果找不到可以通过Window-->Show View-->Ant(如果没有可以在other里面找到)

2、在ant页板里面,点击Add-buildfiles将Build.xml文件添加进来。

添加ant文件

3、点击Study-portlet前面的小三角,在出现的下拉菜单里面,双击deploy,等待Liferay完成部署操作。

如果在此时出现如下错误:

Task cannot continue because ECJ is not installed.

ECJ was automatically installed. Please rerun your task.

原因是ECJ包没有找到,ECJ是什么?ECJ, the Eclipse Compiler for Java, is an open source incremental compiler used by the Eclipse JDT. It is an option for Liferay builds and is in many cases faster than Javac or Jikes. The jar for ECJ is included in Liferay release 4.4.0 and later.http://www.liferay.com/community/wiki/-/wiki/Main/ECJ这是官方解释。大概意思是ECJ是一个编译优化包,可以提升比Javac和JIKES更好的编译速度。要想使编译通过,有两个两个方法:

方法一:禁用ECJ。在Liferay的SDK下面找到build.Administrator.properties,此处中间的Administrator名称不一定是这样的,具体体系名称是根据当前系统的用户名生成的。在里面添加如下内容

javac.compiler=modern
#javac.compiler=org.eclipse.jdt.core.JDTCompilerAdapter

方法二:将ECJ的包添加到ant的路径里面。ecj.jar包可以在Liferay的工程的源码包\lib\development下面找到。将此包添加到ant的路径里面。Windows-->preferences-->Ant-->Runtime,在右边的Classpath-->Ant Home Entries(Default) -->Add External JARs,将ecj包添加进来。

4、启动Tomcat。此portlet工程会自动部署。

5、打开http://localhost:8080/,登录系统。点击左上角的添加-->更多-->示例,在这里面可以看到我们刚建立的study portlet,点击添加,可以将此portlet添加到页面上。

添加portlet

Portlet工程结构

一个Portlet工程的大概结构如下:

portlet结构

1、src:这里存储Java相关文件包,后面会看到还有一个services包。

2、web.xml:此web.xml和普通的web工程的web.xml文件一样。

3、build.xml,此文件为ant的构建文件,一般不需要修改继承自SDK。

4、Liferay-plugin-package.properties,此文件为包工程的元数据信息文件,一般开发不需要关注。

5、portlet.xml:portlet定义描述文件,这个文件是标准的portlet 2.0(JSR 268)规范的文件。在这里定义的信息为portlet的名称、初始化参数、模式类型、portlet的相关信息、权限等。在此文件里面我们可以看到view-template对应的的是/view.jsp,当我们在Liferay里面添加此portlet后,看到的页面内容就是view.jsp里面的内容。下面的security-rol-ref定义的是哪些角色拥有此portlet的权限。

6、Liferay-portlet.xml:此文件是liferay扩展的portlet的内容,portlet.xml文件里面是标准的Portlet内容,所有的Portal容器的portlet.xml文件描述、结构都是一样的。但liferay基于自身平台的需求,又添加了一个Liferay-portlet.xml来扩展portlet的信息。在这里可以看到角色映射,以及Liferay的一些个性化信息,后面有需要的时候详细说明。

7、liferay-display.xml:在上一个步骤里面我们在添加portlet的时候,看到study这个portlet是位于示例这个目录下面的,此文件就是定义相关的portlet是放置在哪个目录下面显示的。

说明:

在Liferay里面,portlet的名称一般不要重复,portlet.xml,liferay-portlet.xml,lifray-display.xml这三个文件之间的关联就是通过portlet 名称做标识进行关联。

Liferay 6.1开发学习(三):Portlet简述

在上一篇文章里面介绍的那个Portlet是在创建工程时默认创建的Portlet页面,可以对Portlet有一个大概的认识,但是很难全面。本篇文章对于Liferay中基于MVC的Portlet创建做一个相对详细的描述。

一、Portlet是什么?

Portlet是基于java的web组件,由portlet容器管理,并由容器处理请求,生产动态内容。Portals使用portlets作为可插拔用户接口组件,提供信息系统的表示层。作为利用servlets进行web应用编程的下一步,portlets实现了web应用的模块化和用户中心化。 portlet规范,即jsr(Java Standardization Request )168/268,是为了实现portal和portlet的互操作。它定义了portlet和portlet容器之间的和约,让portlet实现个性化、表示和安全的api集。规范还定义了怎样在portlets应用中打包portlets。(引自百度百科:http://baike.baidu.com/view/58961.htm)

Liferay是portlet规范的实现,对于portlet规范实现的主流产品还有IBM、Oracle等的Portal产品,主流产品的详细可以参看:http://www.huqiwen.com/2012/06/11/what-is-liferay/ 此篇文章中关于Liferay市场地位处的图片。很以很多时候我们将Liferay称为一个Portal容器,也就是一个提供Portlet运行的环境。

二、创建一个Portlet

1、基于上一节的Study这个portlet工程的基础。点击Liferay IDE上的liferay工具栏,有三个按钮,点击中间按钮,New Liferay Portlet。如下图,点击下一步。

liferay-portlet-demo

Portlet plugin project:这里选择要创建的portlet属于哪个插件工程。

Source folder:类的放置位置,一般默认。

Portlet Class:要创建的Portlet的控制类的名称,可以将此类看作Struts中的Action类。

Java package:包名。

Superclass:选择要继承哪个类。一般默认,也就是MVCPortlet。下面这几个类的关系如下:MVCPortlet是LiferayPortlet的子类,LiferayPortlet是GenericPortlet的子类。在MVCPortlet中封装了一些方便开发的方法,所以一般使用MVCPortlet,如果有特殊需求可以使用他们的父类。

2、在此步骤看到的是Portlet的相关信息,如模式,JSP位置等,此步骤是portlet.xml文件的可视化编辑,此步骤的所有选项都可以通过编辑portlet.xml完成。点击下一步。

创建portlet的信息页

Porltet Info:这里显示的是Porltet的名称,显示名称,标题等。一般默认,不需要修改。

Portlet Modes:这里是portlet的模式,View、Edit、Help这三种模式,是portlet规范里面定义的。

Liferay Portlet Modes:看名知义,Liferay Portlet模式。一般默认即可。Portlet默认的三种模式,Liferay认为不能满足实际的需求,所以又新增了这几种模式。如果选择多个模式,就可以在portlet的设置里面看到,可以通过设置进行切换,方便进行一些特殊需求,如:config可以用来开发可配置Porltet,管理员可以为指定的Portlet定制相关的参数等。

JSP folder:JSP的存放位置,一般默认的命名是html/porltet名称,一般默认即可。html是相对于docroot的,完整路径是docroot/html/demo/view.jsp。

Create resource bundle file:绑定资源文件,主要是国际化。如果想要porltet的名称是中文的,测必须使用资源文件,liferay推荐工程中的所有文字描述类的内容都使用资源文件来定义,这样方便国际化,也能避免出现一些可能的乱码问题。这里先不选,后面详讲国际化。

3、此步骤是Liferay-portlet.xml和liferay-display.xml的可视化编辑。点击完成即可。

liferay-portlet信息

Icon:此portlet的图标。

Allow mutiple instaces:是否允许在同一个页面中有多个porltet的实例,默认为否。

CSS:当前porltet的自定义CSS。一般默认。

JavaScript:当前portlet自定义JS。一般默认。

CSS classname:当前portlet的命名空间,防止CSS和其他porltet冲突。

Category:当前的portlet,显示在哪个分类下面,这里是Liferay-display.xml文件的可视化编辑。

三、MVCPortlet的简单使用

可以看到在工程的com.huqiwen.study包下面生成了一个Demo的java文件,如果只是让portlet显示/html/demo/view.jsp里面的内容,则不需要在Demo.java文件里面添加内容。如果需要让view.jsp后从台初始化一些信息,则需要重写doView文法。view模式显示时调用doView方法,edite模式显示时调用doEdite方法,config模式显示时调用doConfig方法,依此类推。

在此大部分情况下可以将renderRequest当然HttpServletRequest使用。如果要转换可以通过PortalUtil.getHttpServletResponse(portletResponse)来进行转换。

可以使用renderRequest.setAttribute(arg0, arg1)方法,在前台页面通过JSTL等进行取值。

这里不详述,和普通的WEB程序基本一样。

四、其他

在二里面创建的内容都可以通过修改portlet.xml,liferay-portlet.xml,liferay-display.xml等来进行修改调整。创建向导包含的只是一些最通用的内容,一些高级的参数还需要通过修改porltet.xml和liferay-portlet.xml来完成。

通过向导默认porltet的view展现的JSP命名为view.jsp。此名称可以在docroot下面修改成相应的内容,实际开发中建议命名中和业务相关的,方便后期查找识别,如用户的可以命名成userView等,修改名称的同时需要修改Porltet.xml里面init-param对应的名称,否则会出现找不到页面的情况。

Liferay 6.1开发学习(四):Service Builder

一、什么是Service Builder?

Service Builder是Liferay IDE(SDK)提供的一种代码生成方案,开发人员只需要编辑一个数据库的实体描述文件,即可根据本XML文件生成Spring层代码、Hibernate层代码、SQL、SQL索引创建文件、Spring和hibernate的配置文件等,可以大提高开发人员的效率。简单说就是根据数据库描述文件,生成service层和持久化层的代码,开发人员只需要关注控制层即可。

二、Service Builder的使用

1、首先需要有一个Portlet的插件工程,创建方法,参考http://www.huqiwen.com/2012/09/01/liferay-6-1-development-studey-2-create-portlet-project/

2、打开Liferay IDE,点击工具栏中Liferay工具的中间按钮,New Liferay Service Builder,弹出的向导如下,填写相关信息,点击完成。

Liferay Buider

Plugin Project:将用于哪个工程,如果有多个工程,请看仔细。

Service file:数据库描述文件的文件名,使用向导不可更改(可以手动改,后面详述)

Package path:包名路径,这里是定义基础的包名路径,建议一般规则为:公司域名+portlet+功能模块名

Namespace:命名空间,如果没有指定数据库的表名,则会在前面添加上此命名空间。

Author:作者名,默认读取系统变量。

Include sample entiry in new file:在自动生成的xml文件中是否包含示例,如果是新手,建议打勾。

3、编辑service.xml文件。在liferay IDE里面提供了service的开视化编辑。简单介绍一下主要属性的含义。

service XML文件

Name:实例名称,就是通常的JavaBean的名称。

Local Service:本地服务,一般选中。

Remote Service:远程服务,用于为生成Web Service做支持,如果不需要不要选中。

Human Name:人性化名称。一般留空即可,此处的名字用于生成文档时使用,如果留空使用Name。

Table:数据库名称,如果不填写,使用Name。

Uuid:是否生成一个uuid的字段,具体的根据数据库设计情况而定。可以使用也可以不使用。推荐使用。

Uuid accessor:是否使用uuid存取实体,根据情况而定。可以使用也可以不使用。推荐使用。

Persistence Class:持久化类的名称,推荐留空,让自动生成。

Data source:数据源,一般留空。。

Session Factory:数据库会话工厂,一般留空。

TX Manager:事务管理,一般留空。

Cache enabled:开启缓存,推荐选中。

JSON enabled:开启JSON,生成的代码中包含实体的JSON序列化和反序列化,根据情况而定。如果不使用JSON,请不要勾选。

4、在Columns里面添加字段,填写字段名称,类型等。如果要使用特殊的数据库名称,或者字段有其他需求,请点开Columns,在下面逐个字段上修改。Db Name对应的为数据库的字段名称,其他一般留空即可。

5、Order,查询结果根据哪个字段排序列。

6、Finders:查询方法。service builder默认会生成基本的增删改查方法,如果有一些根据字段查询某些数据,返回的可以是一个实体也可以是一个List。

<finder name="userId" return-type="user"><finder-column name="userId" /></finder>

这个的意思是生成一个方法叫findyByUserId的方法,传入的参数是userId,返回的类型是User。(这个例子只是为说明,一般如果userId是主键,service builder生成的方法里面,不写此finder,已经包含此方法)

finder-column这里可以填写多个参数,return-type,如果是Collection则返回类型是List。

Finders方法,可以根据实际情况进行填写,对于使用finders仍不能满足的方法,后面详解。

7、点击ant里面servicer-builder按钮,等待Liferay进行代码生成。或者将可视化编辑器切换到Diagram,点击右键,service builder。

三、Service Builder生成代码的使用。

如果是第一次使用Service Builder,请按如下步骤操作。

1、WEB-INF目录下面找到service目录,点击右键Build Path-->Use as Source Folder。service包里面主要包括生成的代码里面的接口方法,方便我们进行跨工程的代码调用。src里面都 实现包。

2、Service Builder的使用,一般有两种方法。一种是获取一个Service。

CmsArticleLocalService cmsArticleServie = CmsArticleLocalServiceUtil.getService();

然后使用此xxService调用相关方法

一种是直接使用xxLocalServiceUtil调用相关方法。大部分情况下使用xxLocalServiceUtil进行方法调用。

3、创建一个modle实例一般使用如,xxLocalServiceUtil.createXX()方法。

四、自定义方法

虽然liferay自动生成的代码里面包含了常用方法,足以应付一般场景,但是有些特殊场景需要一些特殊的方法,如何添加自定义方法呢?

1、找到src下面和此service builder相关的的xxx.service.impl包,找到xxxLocalServiceImpl的文件,打开在此类里添加相关的自定义方法。

2、如果是要为modle添加自定义方法,请在xxx.modle.impl包里面的,xxImpl文件里面添加自定义方法。

3、修改完成后,再次执行Service Builder,会将这些方法自动的添加到其他相关接口、类里面。Liferay的Service Builder方法一般只允许修改上述两个类,其他的类一般不要修改,因为修改后如果再次执行service builder会被覆盖掉。

五、修改字段长度

Liferay自动生成的代,如String字段,默认长度是75。如果有特殊的需求,可以在src/META-INF/portlet-model-hints.xml里面修改相应的字段映射长度。找到相应的实体,字段,如下:

<field name="InfoId" type="String" ><hint name="max-length">300</hint></field>

这样就可以将InfoId的数据库字段长度定义为300。修改完成重新执行service-builder进行文件生成。

六、其他技巧

1、一个service.xml文件里面可以有多个实体名。

2、如果要建立多个service.xml文件,可以在WEB-IN目录下面建立一个文件夹,比如叫service-builder用来存储service.xml文件,不同的service.xml执行不同的命名,如User-Service.xml等。手动建立,不必使用向导。

3、如果按照步骤2操作,有多个service.xml文件,则不能执行ant里面的service-builder文件,因为ant只会寻找web-inf目录下面的services.xml文件。所以要在xx-service.xml文件里面切换到Diagram编辑模式,点击右键,build Service,这样会对当前XML文件进行构建。

4、即使使用了2、3的方法,也不要删除web-inf目录下面的service.xml文件,可以保留一份,不然最终部署后的web.xml描述文件里会少生成一些内容,导致插件工程不能正常运行。

5、同一个插件工程中,不同的xx-services.xml文件必须使用不同的包名,一个services.xml里面可以有多个实体,但不同的services.xml里面必须使用不同的包名,否则会导致生成的的spring配置文件出现覆盖情况。

Liferay 6.1开发学习(五):编译调试修改源码

Liferay是一个开源的项目,开源项目的好处有两个,一方面我们可以通过阅读源码提高水平,了解一些技术的实现原理,另一方面是如果开源的产品不能满足我们的实际需求,可以通过修改源码实现。Liferay CE版可以免费获取源码,EE版需要购买过产品才能获取源码。

以下文件虽然是基于Liferay 6.1.1所写,但Liferay 6.2.0版本同样适用,方法、过程、需要注意事项等均一致

一、导入源码

源码可以从Liferay的官方网站上下载。http://www.liferay.com/downloads/liferay-portal/available-releases

下载后解压源码,可以使用Eclipse的File-->Import-->General-->Existing Projects in to Workspace。将解压的源码导入到Eclipse中。

导入源码后,如果是6.1.1的源码,可能会出现如下的错误:Project 'portal-trunk' is missing required source folder: 'portal-web/test'。此错误是说在build path下面找不到test目录。解决方法有两个(6.2.0中导入源码后,也有类似问题,解决方法同下面两个原理一样):

1、在portal-web下面建立一个名为test的目录。

2、打开源码工程目录下面的.classpath文件(使用文本编辑器打开,editplus或notepad++),在其中找到<classpathentry excluding="**/.svn/**|.svn/" kind="src" path="portal-web/test"/>(大概在11行),将此行注释掉或删除。然后回到Eclipse中刷新工程(选中工程按F5,或在右键菜单中选择刷新)。

二、编译源码

既然要调试源码,首先是需要能够对源码进行编译。Liferay的源码不是一个普通的Web工程,不能使用普通的方法进行编译部署。但liferay官方已经提供了相应的ant脚本进行编译部署,但首先还需要一些简单的设置。

1、打开liferay的源码目录,找到app.server.properties文件。

2、将些文件复制一份,重命名为app.server.{username}.properties的文件,其中的{username}为当前系统的帐号名称。如我的系统帐号是huqiwen,则将其命名为app.server.huqiwen.properties即可。具体的可以打开CMD,以里面显示用户名为准。如下图:

系统用户名

3、打开此文件,找到app.server.parent.dir=${project.dir}/../bundles。将后面的${project.dir}/../bundles替换为下载的绑定Liferay的Tomcat的路径,如我的为E:/code/liferay6.1/liferay-portal-6.1.1-ce-ga2,则修改后的地址为: app.server.parent.dir=E:/code/liferay6.1/liferay-portal-6.1.1-ce-ga2。注意:从地址栏里面复制的地址为正斜杠,请修改成反斜杠。

4、现在Eclipse的ant面板里面点击,add buildfiles,将portal源码里面的build.xml文件添加到ant面板里面。

5、点开此build文件,先点击compile,再点击deploy,即可将源码编译部署到tomcat中。

三、debug源码

在二的基础上deploy后,即可对liferay的相关源码进行编译调试。如果是第一次进行debug,可能会跳转到显示Source not found,此时,点击下面的“edit source lookup path”,在弹出的对话框里面点击Add -->Java Project,在这里将我们的Portal-trunk工程勾选上,点击OK等,即可开始Liferay的源码调试。

四、常见问题

1、在编译的时候出现如下错误

Task cannot continue because ECJ is not installed.

ECJ was automatically installed. Please rerun your task.

这个问题在前面的博客里面提到过,http://www.huqiwen.com/2012/09/01/liferay-6-1-development-study-2-create-portlet-project/

解决方法为:

原因是ECJ包没有找到,ECJ是什么?ECJ, the Eclipse Compiler for Java, is an open source incremental compiler used by the Eclipse JDT. It is an option for Liferay builds and is in many cases faster than Javac or Jikes. The jar for ECJ is included in Liferay release 4.4.0 and later.http://www.liferay.com/community/wiki/-/wiki/Main/ECJ这是官方解释。大概意思是ECJ是一个编译优化包,可以提升比Javac和JIKES更好的编译速度。要想使编译通过,有两个两个方法:

方法一:禁用ECJ。在Liferay的SDK下面找到build.Administrator.properties,此处中间的Administrator名称不一定是这样的,具体体系名称是根据当前系统的用户名生成的。在里面添加如下内容

javac.compiler=modern
#javac.compiler=org.eclipse.jdt.core.JDTCompilerAdapter

方法二:将ECJ的包添加到ant的路径里面。ecj.jar包可以在Liferay的工程的源码包\lib\development下面找到。将此包添加到ant的路径里面。Windows-->preferences-->Ant-->Runtime,在右边的Classpath-->Ant Home Entries(Default) -->Add External JARs,将ecj包添加进来。

2、在编译源码的时候出现:

Please set the environment variable ANT_OPTS to the recommended value of
"-Xmx1024m -XX:MaxPermSize=512m".

这个提示的意思是说当前的JVM参数设置的太小了,不够liferay编译使用,需要增加JVM内存的分配,并建议将Xmx参数设置成1024m,MaxPermSize设置成512m。这是因为liferay的源码比较大,内容比较多,使用了大量的classloader等,需要比较大的内存。

解决方法上面已经给出提示了将环境变量(其实就是JVM的参数)推荐设置成上面的提示。

1)在Liferay工程的源码里面的build.xml上点击右键-->Run as-->External Tool Configurations

2)在tab标签里面找到Environment,点击new,在name里面输入ANT_OPTS,在value里面输入-Xmx1024m -XX:MaxPermSize=512m,然后点击apply。

3)再次运行ant的编译,即可正常编译。

3、如果出现类似如下错误,请参考上文的“二、编译源码”

Tomcat is not installed in E:/liferay/6.2/bundles/tomcat-7.0.42. If you already have Tomcat
installed, make sure the property "${app.server.tomcat.dir}" points to your
Tomcat installation. If you wish to automatically install Tomcat into

Liferay 6.1开发学习(六):国际化

Liferay的开发不建议直接在代码中使用中文等内容,建议使用国际化的方式,从资源文件中读取语言等信息,Liferay在平台对封装了许多操作资源文件的类,我们只需要按照此规则既可方便的实现国际化。

一、Portlet属性的国际化

在前面创建的Portlet的时候,portlet的名称等都是英文的,在添加portlet的时候,显示的也是英文的,如何将这些信息显示成中文呢?如将下面Study显示成中文。

添加portlet

1、检查portelt.xml的XML文件中的此portlet的信息,是否包含下面的代码,这个是关键,如果没有手动的添加到portlet-info标签的上面。如查在portlet的创建向导里面勾选了“Create resource bundle file”,则会自动生成。

<resource-bundle>content/Language</resource-bundle>

2、工程的src目录下面找到content包,如果没有则创建,如果有则打开。在里面新建Language_zh_CN.properties文件,我们的portlet中文信息写在此文件里面。

3、在此文件中写入“javax.portlet.title=此处写相应的标题文字”,可以直接写中文,eclipse的properties编辑器,会自动的转换成unicode代码。一般我们只写title就可以,如果有特别需求,可以加上javax.portlet.keywords,javax.portlet.short-title等信息。

4、现在重新打包部署此工程,再次添加portlet,即可看到portlet的名称变成了中文。

注意:<resource-bundle>content/Language</resource-bundle>这里的内容定义了,我们存放语言文件的目录在content目录下面的Language文件,这里也可以定义成自己喜欢的,只要按照此规则即可。

二、内容的国际化

上面的国际化是portlet属性信息的国际化,如果是我们想在代码中使用这些国际化信息呢?比如提示语、按钮名称等等。这个地方的方法就和普通的java代码的国际化一样,在上面的语言文件中写入相应的key,在代码中读取即可。

1、如我们要将“参数名称”这个词国际化,则在Language_zh_CN.properties中写入:

propertyName=\u53c2\u6570\u540d\u79f0

后面的是参数名称的unicode码,我们在编辑器中直接写入中文,编辑器会自动转换,如果不能转换可以使用JDK的命令如:

native2ascii -encoding UTF-8 Language_zh_CN.properties.native Language_zh_CN.properties

进行中文编码的转换。

2、在代码中调用:

JSP中:LanguageUtil.get(pageContext,"propertyName")

JAVA代码中:LanguageUtil.get(portletRequest.getLocale(), "propertyName")。其中的第一个参数为Local,也可以使用Locale.CHINESE

三、Liferay Portlet属性国际化的修改

在一中提到的portlet属性的国际化存在一个问题,也就是我们一个语言文件中只能定义一个portlet的属性信息,如果我们在一个插件工程中需要国际化多个portlet的属性信息怎么办?在Liferay的官方论坛、Wiki里面也有不少人讨论此问题,但Liferay的解释就是不支持!在一个资源文件里面只能写一个portlet的属性信息。

个人觉得这一点非常不好,如我的一个插件工程中有十几个portlet,那么难道需要定义十几个语言资源文件或者拆分成十几个Portlet?如果支持下面这样:

javax.portlet.title.MyPortletTitle = My Portlet Title

如果是这样的才是很合理的,Liferay平台里面的Portlet的信息就是这样的形式国际化的,但是插件里面不支持。Liferay平台本身是使用的Strtus resource bundle,所以和我们的不太一样。

既然官方不支持,我们就自己动手改造了,好在Liferay是开源的,我们修改他的源码自己实现就可以了。

方法如下:

1、参考上一篇文章,http://www.huqiwen.com/2012/09/21/liferay-6-1-development-study-5-compile-debug-deploy/,如果编译部署关联Liferay的源码

2、在源码里面找到PortletResourceBundle.java这个类(小提示:可以在eclipse中使用快捷键ctrl+shift+r来快速定位此文件)。找到其中的handleGetObject方法,修改成如下,下面有注释,显示了哪里修改的:

@Overrideprotected Object handleGetObject(String key) {if (key == null) {throw new NullPointerException();}
String value = null;if (parent != null) {try {value = parent.getString(key);}catch (MissingResourceException mre) {//hqw 2012.09.09 add beginString titlekey = key.concat(StringPool.PERIOD).concat(_portletInfo.getTitle());if (Validator.isNotNull(titlekey)) {titlekey = titlekey.toLowerCase();}try {value = parent.getString(titlekey);} catch (MissingResourceException mre2) {}//end}}
if ((value == null) || (value == ResourceBundleUtil.NULL_VALUE)) {value = _getJavaxPortletString(key);}
if ((value == null) && ResourceBundleThreadLocal.isReplace()) {value = ResourceBundleUtil.NULL_VALUE;}
return value;}

在我debug时,发现liferay只是通过javax.portlet.tiltle这个key值寻找,那我们在给他加上具体的title值就可以了。现在修改后的不但兼容他原来的,而且又支持javax.portlet.title.MyPortletTitle = My Portlet Title。这样的多好

3、现在使用ant进行deploy源码,再次启动tomcat就发现,现在的国际化已经支持我们需要的形式了。

Liferay 6.1开发学习(七):Layout布局模板开发

Liferay提供了layout功能,可以方便的自由布局,用户或者管理员可以根据选用的布局模板对页面进行自由布局。Liferay默认中提供了几种常用的layout模板,如单栏目、双栏目(20%/80%,30%/70%)等几种常用的,但是这些布局模板并不能满足我们实际的需求,一些复杂的布局需要自己开发。

LayoutTPL开发

布局模板是一个tpl文件,本身结构非常简单。基于Liferay IDE可以进行可视化的、拖动的形式进行布局TPL的开发。方法如下 :

1、在Eclipse的工具条上,点击New Liferay Project,在plugin type里面选择Layout,点击finish。则完成了Layouttpl工程的建立。

2、工程里面对于开发有用的文件主要有四个:

*.tpl:这个文件是定义PC访问时布局模板的文件。

*.wap.tpl:定义手机访问时的而已模板文件。

*.png:这个图片是用于显示布局模板的缩略图,方便在选择模板的时候预览此模板的样式。

liferay-layout-templates.xml:此文件位于WEB-INF目录下面,用于定义此布局模板的元数据信息。

3、通常情况下,我们只需要编辑*.tpl文件即可。如下图,为我拖出来的一个TPL文件。

liferay-layouttpl

4、可以通过鼠标可视化的形式,添加column(列)、row(行)等,通过控制点来显示列宽的百分比等。

布局模板的发布

主题创建修改完成后,点击ant中的deploy进行发布即可。

其他说明:

layoutTPL的开发非常简单。通过Liferay IDE通够实现快速开发。有一些小技巧

1、可以在一个TPL工程里面建立多个布局模板,只需要在liferay-layout-templates.xml里面对这些文件进行定义描述即可。

2、如果在拖动中不能拖出自己想要的宽度百分比,可以进行source模式,手动的修改这些百分比,为 aui-w24,w后面的数值即是百分比。

3、如果不需要进行移动访问,*.wap.tpl的内容可以不用管。

4、建议添加*.png这个缩略多,方便在添加切换的时候预览。

Liferay 6.1开发学习(八):主题开发

Liferay可以为不同的页面、社区、组织等提供不同的主题。方便实现用户的个性化配置,同时Liferay提供的IDE方便开发人员进行主题的个性化开发。

Liferay主题的创建

1、在Eclipse的工具条上,点击New Liferay Project,在plugin type里面选择Theme,点击finish。则完成了theme工程的建立。

2、Liferay的主题开发是在他提供的一个模板的基础上进行修改。我们将个性化的内容放置于_diffs目录下面,在deploy的时候,liferay会自动的将此文件的内容合并到主题中,并覆盖原先相应的内容。如果没有覆盖的则采用默认的样式等。

3、liferay的主题主要有四部分组成,CSS、images、js、template。这几部分中前三部分,不需要特别说明。template里面是使用Velocity编写的模板,方便我们修改他相应的模板内容。

4、依次说明:

CSS

CSS里面的文件是分类的,如forms.css里面是定义的表单相关的,navigation.css里面是定义的导航相关的,layout.css是定义的布局相关的等等。比如我们需要修改导航的样式,则在_diffs目录下面建立css目录,并将上面的navigation.css复制到_diffs/css/下面。我们在此文件上修改,liferay IDE会自动的编译覆盖原先的内容。

如果我们需要定义的CSS内容,不是liferay本身提供的,而是我们自己新建立的样式,则推荐将此样式写在_diffs/css/custom.css里面。

JS

js的内容一般不做修改,也没有必要在主题包里面定义太多的JS相关的内容。

images

将和自己编辑的主题相关的图片存于images目录下面。如果需要在代码中引用,可以使用

String imgpath = themeDisplay.getPathThemeImages();

这里取到的imgpath,即为images目录。

templates

模板文件,用于定义常用的portlet、导航、通用的等模板文件。可以根据自己的实际需求进行修改。和普通的Velocity一样,如果对于Velocity不熟悉的,可以大概了解一下,使用起来和普通的HTML没有太大的区别。

主题的发布

主题创建修改完成后,点击ant中的deploy进行发布即可。

其他说明

在_diffs目录里面只需要复制自己要修改的内容,不需要修改的则不需要复制过来,如下图:

liferay主题包

1、我需要修改layout.css,则只需要从上级的CSS目录里面将此文件复制过来,在此文件上进行修改。不需要修改navigation.css则不用复制。其他的也类似。

2、图片,我需要将自定义的图片保存到common目录下面,则需要要创建一个comon目录,在引下面存放自己的文件即可。

3、tumbnail.png文件是存放的主题的缩略图,方便在切换主题时预览。

Liferay 6.1开发学习(九):Hook开发简介

一、Hook是什么

hook是什么在http://www.huqiwen.com/2012/09/01/liferay-6-1-development-study-2-create-portlet-project/里面有一个大概的介绍。Liferay提供hook的目的是减少二次开发的代码与liferay核心工程的耦合,通过hook我们主要可以主要做以下六件事情:

1、覆盖事件处理程序:如程序启动事件(application.startup.events)、登录注销事件(login.events.pre、login.events.post)、session的创建销毁事件等。

2、语言包:比如对liferay的某些翻译不满意,需要自定义,则可以使用hook覆盖liferay的语言包。

3、liferay工程的portlet JSP内容:比如想在顶部的top_bar里面添加内容、想修改liferay的用户编辑界面、想修改用户登录界面等等。

4、覆盖portal.properties里面的属性配置:可以在hook工程里面覆盖一些liferay的portal.properties里面属性配置,但是请注意,在hook工程里面只能覆盖部分的属性设置。不能覆盖的如果需要可以在root/WEB-INF/classes里面添加portal-ext.properties,这个同可以覆盖所有的。

5、覆盖liferay的services里面的方法,如UserLocalService、GroupLocalService、OrganizationLocalService等等。

6、Liferay的Model监听程序,比如user、group、blog等的创建、更新、查看;插件的部署;集群的消息传递等等监听程序。

二、使用Liferay IDE进行hook开发

1、在liferay IDE中,New Liferay Project-->选择hook-->输入project Name,点击完成,即可创建一个hook工程。

2、在IDE的工具条中,选择New Liferay hook,选择我们要创建的hook类型,如下图:

Liferay Hook定义

custom JSp:为自定义的JSP文件

portal properties:覆盖portal属性

Services:这个可以可以做两件事情一是覆盖事件处理程序;二是覆盖service类

Lanuage properties:这个覆盖或者新增国际化的语言文件。

3、以Custom JSPs为例,点击下一步,如下图

创建自定义JSP

尽量的使用 Add from Liferay。这样我们可以在弹出的界面中选择相应的我们要覆盖的JSP路径。点击完成。

4、现在demo-hook工程目录下的docroot/custom_jsps下面将出现我们上面选择的路径的JSP文件,此文件下的JSP路径一定要和Liferay root下的待覆盖的JSP路径相符。现在就可以编辑此JSP来覆盖我们想覆盖的内容,打开此JSP可以看到这里面的内容和portal下面的JSP内容完全一样,修改想修改的部,点击ant的deploy即可发布。

5、对于示例中的top_js.jspf文件,可以做什么呢?top_js.jspf此文件是portal的头部JS的加载程序。比如我要在我的portlet工程里面引用jQuery库,则可以在top_js.jsp里面将jQuery的库引进来,这样在portlet的工程里面不用每个页面都需要引用jQuery,可以避免一个页面中多个portlet引用jQuery引起的冲突。引用方法如下

在top_js.jspf的最后部分添加:

<script type="text/javascript" src="/html/js/jquery-1.8.0.min.js"></script>

同时,在custom_jsps/html下面建立js目录,并将jquery-1.8.0.min.js库复制到此目录下面即可。

6、对于portal.properties的覆盖和语言包的覆盖类似。Services的覆盖略有不同,在下一篇文章里面详述。

Liferay 6.1开发学习(十):在Liferay中使用Ajax

在现在的Web开发中,Ajax的使用非常频繁,合理的使用Ajax也是提高用户体验的一个重要手段,在Liferay中使用Ajax其实非常方便,但是和普通的web程序稍有差异。

一、简单示例

客户端代码编写

1、在Liferay中创建一个简单的Portlet页面,可以取名为ajaxPortlet,具体的创建过程可以参考;http://www.huqiwen.com/2012/09/03/liferay-6-1-development-study-3-portlet-explicate/

2、在liferay中ajax的请求地址要使用portlet:resourceURL,a这样定义:<portlet:resourceURL var="ajaxUrl"/>,而不是普通请求的portlet:actionURL。

3、编辑view.jsp页面,在页面中添加<portlet:resourceURL var="ajaxUrl"/>,引入Jquery的包(使用jquery做ajax请求),然后编写一个简单的ajax请求代码。

function ajaxTest(){$.post('<%=ajaxUrl%>',{p_p_resource_id:'test1'},function(data){$('#message').html(data);})}

这个代码的意思就是请求一下服务端,然后将得到的结果显示到id为message的html元素中。

4、这样客户端的代码就是编写完成。具体的详细代码参看下面中的附件。

服务端代码编写

1、在portlet的控制类中重写serveResource方法。

2、在此方法中添加相应的处理类,此处的处理方法和普通的servlet一样,可以使用下面的代码。

@Overridepublic void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) throws IOException, PortletException {response.setContentType("text/html;charset=UTF-8");PrintWriter out = null;try {out = response.getWriter();} catch (IOException e) {e.printStackTrace();}String result = "这是服务端返回的结果信息:";out.println(result);out.flush();out.close();super.serveResource(resourceRequest, resourceResponse);}

可以看到其实和普通的servlet处理基本上是一模一样。

二、多ajax请求

上面的示例虽然简单,但仔细一想就会发现有一个问题,如果在一个页面中有多个ajax请求怎么办?我们要依靠一个resourceID来区分不同的请求,同样也是分客户端和服务端。

客户端

方法1:在resourceURL里面添加一个属性id,如<portlet:resourceURL var="ajaxUrl2" id="test2"/>

方法2:在rsourceURL里面添加一个参数,如:

<portlet:resourceURL var="ajaxUrl1"><portlet:param name="p_p_resource_id" value="test1"/>
</portlet:resourceURL>

方法3:在ajax请求时添加相应的参数,如:

function ajaxTest(){$.post('<%=ajaxUrl1%>',{p_p_resource_id:'test1'},function(data){$('#message').html(data);})}

方法4:直接在URL里面拼接

服务端

服务端的处理只有一个,取得此resourceID,方法为String resourceID = resourceRequest.getResourceID();然后根据不同的resourceID判断处理不同的ajax请求。返回和一般的处理一样。

其他

在处理返回结果时,如果不想使用普通的servlet的处理方式,可以使用liferay的ServletResponseUtil.sendFile()方法,此方法主要是用来处理文件的,但用于处理ajax的请求也非常方便。

demo代码下载:点击此处下载

Liferay 6.1开发学习(十一):调度器-定时任务

在liferay的开发中经常需要执行一些定时调度任务,一般情况下在普通的web开发中我们使用quartz来做调度,但是在Liferay中已经对于调度做了一个封装,方便我们在开发中执行调度任务,liferay中封装了两种方法方便我们做调度扩展。

一、基于Portlet的调度

如某个的调度是在某个portlet中使用的,则可以使用一面的方法:

1、  编写一个类,实现接口:com.liferay.portal.kernel.messageing.MessageListener。如下图所示。其中的doReceive()方法是定时执行的内容。

liferay-messageListener

2、  在liferay-portlet.xml里面注册这个调度类,添加如下代码:

<scheduler-entry>
<scheduler-event-listener-class>
xxx.cmsnewsgather.NewsGatherMessageListener(上面此类的全路径)
</scheduler-event-listener-class><trigger><simple><simple-trigger-value>15</simple-trigger-value><time-unit>minute</time-unit></simple></trigger>
</scheduler-entry>

scheduler-event-listener-class:里面的类为第一步里面编写的类

simple-trigger-value:里面为调度周期的数值,time-unit为调度周期的单位。上面的意思为每15分钟执行一次。此时间也可以从配置文件中读取。则将此标签完成<property-key>newsgather.gather.time</property-key>,其中的newsgather.gather.time为属性文件(portal.properties)里面配置的值

Time-unit:表示周期的周期可以为:day、hour、minute、second、week这几个单位。

3、  上面的代码即完成了一个调度器的开始,内容为每15分钟执行一次第一步类里面的doReceive()方法。

二、基于servlet的调度

注:此方法适用于lifery6.0.6,不适用于liferay6.1.x

1、同上,编写一个实现com.liferay.portal.kernel.messageing.MessageListener的类。

2、在相应工程下面的web.xml里面添加servlet的注册信息,如下:

<servlet><servlet-name>Lucene Servlet</servlet-name>
<servlet-class>
xx.xx.servlet.xxxServlet
</servlet-class><load-on-startup>2</load-on-startup>
</servlet>

3、在上面的servlet的init(ServletConfig servletConfig)方法里面添加如下内容:

super.init(servletConfig);
SchedulerEntry schedulerEntry = new SchedulerEntryImpl(); schedulerEntry.setEventListenerClass(xxxMessageListener.class.getName());schedulerEntry.setTimeUnit(TimeUnit.MINUTE);schedulerEntry.setTriggerType(TriggerType.SIMPLE);schedulerEntry.setTriggerValue(15); try {
SchedulerEngineUtil.schedule(
schedulerEntry, PortalClassLoaderUtil.getClassLoader());
}

其中的xxxMessageListener为第一步编写的类。分别在setTimeUnit和setTriggerValue里面设置调度的周期单位和时间。

三、两者的区别及适用情况

对于方法一是推荐使用的,但是有时候我们可能需要在程序中来设置调度周期,但是方法一需要在liferay-portlet.xml里面将调度的周期和时间就固定了。我们需要做一下变通,如在新闻采集的自动采集的代码里面就使用的方法一,在liferay工程中的日程管理也是使用了此功能。此方法适用于下面情况

  • 需要在程序中设置调度周期
  • 但是对于时间的精确度需求不是非常高,可以有一定的误差

如新闻采集的自动采集,如果我们需要对一个网站进行新闻的自动采集,我们一般将这个周期设置成几个小时重复执行一次,我们可以在liferay-portlet.xml里面设置成这个调度是10分钟或者30分钟执行一次检查,这个时候我们在doReceive()里面调用的代码不是具体的执行采集的代码,而是检查现在的这个时间是否在下调度周期中,如果是则执行采集,如果不是则跳过。

对于方法二而言,适用于以下情况:

  • 需要执行调度的代码不明确属于某一个portlet。
  • 需要精确控制时间。

只要满足上面的任何一个条件,都适合使用方法二,方法二里面的调度时间可以从数据库中读取也可以从配置文件中读取。

Liferay 6.1开发学习(十二):文件上传处理

Liferay中提供了完善的文件处理,从liferay 6.1开始在文件的处理方面,不再区分文档和图片,统一为文件媒体库。在普通的portlet插件工程中,如果想将文件上传到Liferay的文档库中,大的阶段可以分为两个流程:一、在portlet中将上传的文件取到,二调用Liferay的API将文件上传到文档库中。

(注:这里的文件上传只讲服务端的处理,至于客户端也就是浏览器使用普通的表单文件上传,还是使用swffileupload或者ajax提交等与服务端没有关系,处理方法都是一样的)

一、在portlet中取到上传的文件

这两的获取有两种方法,一种是普通的fileupload的处理方法,一种是使用liferay的API

1、使用fileupload的处理方法:

此种方法的获取和在普通的servlet里面使用fileupload的方法一样,核心代码如下 :

DiskFileItemFactory factory = new DiskFileItemFactory();//PortletFileUpload upload = new PortletFileUpload(factory); ServletFileUpload upload = new ServletFileUpload(factory);upload.setSizeMax(1024 * 1024 * 200);HttpServletRequest servletRequest = PortalUtil.getHttpServletRequest(request);List<FileItem> items = upload.parseRequest(servletRequest);

注:也可以使用此行代码,如果使用了此行则下面的HttpServletRequest servletRequest = PortalUtil.getHttpServletRequest(request)就可以不要,在upload.parseRequest里面传actionRequest。但是此方法在weblogic环境下面会出错,所以如果要在weblogic下面运行,请使用下面的servletFileUpload。

其他代码就和在普通的Servlet里面使用fileupload一样,这里不再多写。

2、使用Liferay的API获取上传文件

推荐使用下面的代码进行文件上传。

UploadPortletRequest uploadPortletRequest = PortalUtil.getUploadPortletRequest(actionRequest);String sourceFileName = uploadPortletRequest.getFileName("file");String contentType = uploadPortletRequest.getContentType("file");long size = uploadPortletRequest.getSize("file");
// File file = uploadPortletRequest.getFile("file");InputStream is = uploadPortletRequest.getFileAsStream("file");

:其中的getFileName中的file为<input type="file" name="file">中的name值,具体的可以要看自己的调整。

二、将获取到的文件上传到Liferay的文档库

1、上传文件

在portlet中获取到我们上传的文件,只算是完成了第一步。取到了上传的文件,怎么传到Liferay的文件库中呢?使用如下的接口

DLAppLocalServiceUtil.addFileEntry(long userId, long repositoryId, long folderId,java.lang.String sourceFileName, java.lang.String mimeType,java.lang.String title, java.lang.String description,java.lang.String changeLog, byte[] bytes,com.liferay.portal.service.ServiceContext serviceContext)

此接口的参数逐个说明:

long userId:上传此文件的用户id

long repositoryId:仓库存储ID,此ID一般为groupid

long folderId:文件夹ID,可以自己创建,或使用liferay默认的如:DLFileEntryTypeConstants.FILE_ENTRY_TYPE_ID_BASIC_DOCUMENT等,最好是自己根据上传资源的情况分类,如新闻的存到新闻文件,博客的存成博客文件等

String sourceFileName:上传文件的源文件名

String mimeType:算是文件类型,可以使用mimeType = MimeTypesUtil.getContentType(fileName);根据文件名获取。

String title:文件标题,这个与SourceFileName的区别在于,此title是最终显示在系统上的,可以由用户输入,源文件名称是上传获取的文件名称,不能手动改变。可以留空。

String description:关于此文件的描述,可以留空。

String changeLog:文件修改日志,可以留空。

byte[] bytes:文件的正文件,字节数组。

ServiceContext serviceContext:此类可以通过以下代码

ServiceContext serviceContext = ServiceContextFactory.getInstance(DLFileEntry.class.getName(), request);

获取。此对象里面包含了一些环境信息,如groupid,companyid,权限,门户路径,当前语言,userId等信息。

在实际应用中可以自己将上面的这个接口再做一层封装,作为一个公共的文件上传接口,以供其他需要文件上传的地方调用,具体的请自行封装,这里就不再帖我封装的代码。

2、获取文件路径

上面的文件上传之后返回的是一个FileEntry的对象,如果获取到上传的文件的路径呢,方法如下,可以定义一个方法,用来返回文件路径,此方法对图片、各式文件都是有效。

public static String getFilePath(FileEntry fileEntry) {if (null!=fileEntry) {return "/documents/" + fileEntry.getRepositoryId() + "/" + fileEntry.getFolderId() + "/"+ HttpUtil.encodeURL(HtmlUtil.unescape(fileEntry.getTitle()), true) + "/" + fileEntry.getUuid();}else {//如有需要,此处可以定义一个默认图片return StringPool.BLANK;}

或者下面的这个方法,下面的这个更简便一些

public static String getFilePath(FileEntry fileEntry) {if (null!=fileEntry) {return "/documents/" + fileEntry.getRepositoryId() + "/" + fileEntry.getUuid();}else {//如有需要,此处可以定义一个默认图片return StringPool.BLANK;}

有时候如果文件是一个图片,上传的图片可以很大,如何获取一个缩略图呢?如下:

public static String getSmallImagePath(FileEntry fileEntry){
String path = getFilePath(fileEntry);
return path+"?imageThumbnail=1";
}

Liferay 6.1开发学习(十三):弹出层/弹出对话框的使用

在日常的开发中,为了提高用户体验,经常会使用弹出层或者弹出框。在Liferay中可以使用AUI的标签及封装的JavaScript代码方便的达到这一目的。针对弹出内容的不同,弹出对话框大体上可以分为三种形式,弹出div层、弹出普通的HTML页或JSP页面、弹出portlet。

一、弹出div层

有几种写法,本质上是一样的,一种是普通的JavaScript代码,一种是使用aui标签。

1、普通的javascript代码

<script>function test(){AUI().use('aui-dialog', function(A) {var popup = new A.Dialog({bodyContent: A.one('#webDav').html(),centered: true,destroyOnClose: true,modal: false,title: '弹出层测试',width: 500,buttons: [{label: '确定',handler: function() {alert('点击了确定');}},{label:'取消',handler:function(){this.close();}}]}).render();});
}
</script>

代码看起来有点小复杂,其实很简单,逐步的说明一下:

var popup: 这是定义当前的弹出层对象。在其他地方可以使用popup.close()关闭当前的对话框。

bodyContent: A.one('#webDav').html():表示弹出层的正文,这里是弹出一个id为webDav的对象。A.one('#webDav').html()这是aui代码的写法(aui其实是YUI的衍生版,所以和YUI的用法基本上一样),表示选择id为webDav的对象,取其中的html代码。

centered:弹出后是否居中,默认为false.

destroyOnClose:是否在半闭时销毁

modal: 是否模态否,也就是是不是锁定背景是否让背景可以编辑,值为true/false,默认为false,为true时背景不可以编辑。

title:弹出层的标题,使用单引号或双引号。

width:弹出层的宽度,必须为数字,不能用百分比。如果不写为自适应。

height:弹出层的高度,如果不写为自适应。

buttons:在弹出层上面定义的按钮。非必须。我们一般添加确定和取消两个。多个按钮之间使用逗号分割,text的内容为按钮的显示值,handler为绑定的方法

2、使用aui标签

<aui:script use="aui-dialog">var create = A.one('#createNew');create.on('click',function(event) {var popup = new A.Dialog({bodyContent: A.one('#webDav').html(),centered: true,modal: true,title: '弹出层测试',width: 500}).render();});
</aui:script>

和上面的的基本上差不多,这里是在createNew的对象上绑定了一个click的方法,如果此对象被点击了,执行弹出层代码。其他的和上面的一样。

上面使用的是aui标签,所以需要在页中引入

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>

3、使用Liferay.provide

此种方法和上面的非常类似,如下:

<aui:script>
Liferay.provide(window,'showDialog',function() {var A = AUI();var dialog = new A.Dialog({bodyContent: A.one('#webDav').html(),destroyOnClose: true,modal: false,title: '这是标题',width: 900}).render();},['aui-dialog']
);
</aui:script>

写法和上面的没有太多的区别,只是写法的不同。showDialog 这个参数其实就是函数名,在其他地方可以直接调用此方法来弹出下面的弹出层,如果是要传参数可以直接写在showDialog(arg)这个里面。上面的的function里面添加此参数。如function(arg),在下面直接使用arg参数即可。

二、弹出一个HTML页面或者是JSP页面。

弹出一个页面的方法和上面的基本上相同,具体看下面的示例。

<aui:script>
Liferay.provide(window,'showDialog',function() {var A = AUI();var url= "要弹出的页面的地址";var dialog = new A.Dialog({destroyOnClose: true,modal: false,title: '这是标题',width: 900}).plug(A.Plugin.IO,{uri: url}).render();},['aui-dialog']
);
</aui:script>

不同之处是加了一个plug来显示待弹出的页面地址。在dialog里面也可以添加bodyContent属性,里面可以添加上在弹出的过程的提示语,比“页面正在加载中请稍候……”,在url加载后将替换上面的内容。

三、弹出一个portlet页面。

使用二里面的方法也可以弹出一个portlet页面,但是有时候会不能正常工作,步骤2的弹出的原理还是弹出一个div,在这个div里面加载目标页面的内容。弹出portlet页面我们需要弹出一个iframe,这样就可以使弹出的portlet页面正常工作。方法如下:

<aui:script>Liferay.provide(window,'showPortletUrl',function(url) {var instance = this;Liferay.Util.openWindow({cache: false,dialog: {align: Liferay.Util.Window.ALIGN_CENTER,after: {render: function(event) {this.set('y', this.get('y') + 50);}},width: 1000},dialogIframe: {id: 'siteManagementIframe',uri: url},title: '管理页面',uri: url});},['liferay-util-window']);
</aui:script>

在上面的方法中我是将待弹出的porltet的URL以参数的形式传进来的,调用的时候方法为:showPortletUrl(url)。dialogIframe中的id为弹出的iframe的id名,uri为待加载的portlet页面地址。

注意:如果要弹出porltet,请讲windowState设为pop_up,也就是LiferayWindowState.POP_UP这个变量。

Liferay 6.1开发学习(十四):在自己的Portlet中使用Liferay的全文检索

在Liferay中的全文检索是使用的Lucene,方便我们的对内容进行全文检索。liferay中对文章、文档、博客、wiki、留言等实现了全文检索,如何在我们自己的Portlet中使用Liferay的全文检索呢?

实例场景如下:

我们自己实现了一个CMS,没有使用Liferay中的Journal类,比如存放新闻的类叫做CmsArticle。我们现在需要对这个新闻进行全文检索,可以检索新闻正文、标题、摘要、作者等,同时可以进行范围搜索,如只搜索标题、只需要正文或全部等。

一、建立索引类

继承BaseIndexer类,编写一个索引类,如叫做CmsArticleIndexer。重写里面的相关搜索方法。一般需要重写

getClassNames

getSummary

doDelete

doGetDocument

doReindex

getPortletId

等几个方法,其他的方法可以根据需要进行重写。

简单贴几个主要的方法的内容:

protected Document doGetDocument(Object obj) throws Exception {CmsArticle article = (CmsArticle)obj;
long companyId = article.getCompanyId();long groupId = getParentGroupId(article.getGroupId());long scopeGroupId = article.getGroupId();
……………………省略相关字段的获取和上面的一样
Document document = new DocumentImpl();
document.addUID(PORTLET_ID, groupId, articleId);document.addKeyword(Field.COMPANY_ID, companyId);document.addKeyword(Field.PORTLET_ID, PORTLET_ID);document.addKeyword(Field.GROUP_ID, groupId);document.addKeyword(Field.SCOPE_GROUP_ID, scopeGroupId);document.addKeyword(Field.USER_ID, userId);
//将不同的字段添加到索引中document.addDate(Field.MODIFIED_DATE, modifedDate);document.addText(Field.TITLE, title);document.addText(Field.CONTENT, ArticleUtil.getArticleText(content));document.addText(Field.DESCRIPTION, description);document.addText(Field.USER_NAME, userName);document.addKeyword(Field.ENTRY_CLASS_NAME, CmsArticle.class.getName());document.addKeyword(Field.ENTRY_CLASS_PK, articleId);
//这个为必须的,否则会搜索不到内容document.addKeyword(Field.ROOT_ENTRY_CLASS_PK, resourcePrimKey);document.addKeyword(Field.TYPE, type);
return document;}

下面是重建索引的方法,方法很简单,根据传入的companyId或者也可以使用groupid,将某个范围下面的内容进行依次取出添加到索引库里面,这里使用了分页,如果数据量较小可以不用分页,但数据量大时务必分页,否则会将内存耗尽。

//索引调用的方法
protected void doReindex(String[] ids) throws Exception {long companyId = GetterUtil.getLong(ids[0]);
reindexArticles(companyId);
}
//从传入的companyId里面依分页将数据取出添加到索引库里面
protected void reindexArticles(long companyId) throws Exception {int count = CmsArticleLocalServiceUtil.getCmsArticleCountByCompanyId(companyId,APPROVED_STATUS);
int pages = count / Indexer.DEFAULT_INTERVAL;
for (int i = 0; i <= pages; i++) {int start = (i * Indexer.DEFAULT_INTERVAL);int end = start + Indexer.DEFAULT_INTERVAL;reindexArticles(companyId, start, end);}}
//索引内容,在这里可以对新闻的状态等进行判断,如只索引审批或发布过的,未审批的不进行索引
protected void reindexArticles(long companyId, int start, int end)throws Exception {
List<CmsArticle> articles = CmsArticleLocalServiceUtil.getCmsArticlesByCompanyId(companyId, APPROVED_STATUS,start, end);
if (articles.isEmpty()) {return;}
Collection<Document> documents = new ArrayList<Document>();
for (CmsArticle article : articles) {Document document = getDocument(article);documents.add(document);}
//更新索引
SearchEngineUtil.updateDocuments(getSearchEngineId(),companyId, documents);}

上面贴出来的是一些主要方法,可以根据不同的需求进行调整,不满足的可以参考Indexer相关类的实现,liferay里面有博客、wiki、文章等索引实现,的都是很好的学习参数资料,方法为在源码的Indexer类名上按ctrl+T,查看他的相关实现或继承类。

二、注册索引类

将上面写的索引类注册到portlet中。打开liferay-portlet.xml文件,在相关的portlet中添加

<indexer-class>xxx.xxx.xxx.cmsarticle.search.CmsArticleIndexer</indexer-class>

这样就将此索引类注册到portlet中了。

注册有什么好处呢?

1、可以在控制面板中的更新插件处,对某一个指定的portlet重建索引。

2、在控制面板中当点击重建所有搜索索引时,可以调用到此索引类进行索引重建立。

3、如果设置了portal在启动时更新索引,则liferay可以调到此类进行索引更新。

简而言之主是注册之后,可以让Liferay来管理索引,方便的进行统一的索引管理、重建等。

三、搜索

建立完了索引,工作只算完成了一小部分,还有搜索。搜索的过程比建立索引的过程要简单的多,核心代码只要两行。

//实例化索引类
Indexer indexer = IndexerRegistryUtil.getIndexer(CmsArticle.class);
//进行搜索
Hits results = indexer.search(searchContext);
//从结果里面取Document
Document[] docs = results.getDocs();

第一步不需要特别说明,就是对相关索引类进行初始化。第二步里面的搜索参数是一个searchContext,这是一个什么东西呢?这是Liferay包装的一个参数类,可以在里面设置一些常用的搜索参数,如关键词、搜索分页条件等。我们一般可以这样使用:

//实例化一个SearchContext
SearchContext searchContext = SearchContextFactory.getInstance(request);
//start和end是分页用,如我们只要取前20条,为start=0,end=20 searchContext.setStart(start);searchContext.setEnd(end);searchContext.setKeywords(keyWords);

既然有分页,则需要知道结果的总条数才可以,results.length()返回的就的命中的结果数量,也就是搜索结果的总数。根据start、end、总条数即可进行分页处理。

完整的搜索处理类核心代码

Indexer indexer = IndexerRegistryUtil.getIndexer(CmsArticle.class); SearchContext searchContext = SearchContextFactory.getInstance(request);
int start = (pageNumber-1) * pageSize;int end = pageNumber * pageSize;//取消搜索权限,liferay的搜索权限是否过滤是根据searchContext里面是否能获取到的userId来区分的searchContext.setUserId(0);searchContext.setStart(start);searchContext.setEnd(end);searchContext.setKeywords(keyWords);
//这里是自定义的搜索范围,liferay默认是全部,这里是为了实现只搜索标题、只搜索正文等而添加的的searchContext.setAttribute(ArticleSearchConstants.SEARCH_SCOPE, searchScope);int countResult = 0;try {Hits results = indexer.search(searchContext);
//取得搜索关键词的分词词元String[] terms = results.getQueryTerms();//获取结果总数countResult = results.getLength();Document[] docs = results.getDocs();for (int i=0;i<docs.length;i++) {Document document = docs[i];String title = document.get(Field.TITLE);//对标题进行关键词高亮String hightLightTitle = StringUtil.highlight(title, terms);//从索引中提取正文片段,如果找不到就取正文的前200个字String snippet = results.getSnippets()[i];String content = snippet;if (Validator.isNull(snippet)) {content = StringUtil.shorten(document.get(Field.CONTENT), 200);}//高亮摘要String higthLightContent = StringUtil.highlight(content, terms);//取新闻的IDString articleId = document.get(Field.ENTRY_CLASS_PK);//取新闻的groupIdlong groupId = GetterUtil.getLong(document.get(Field.GROUP_ID));
//这里可以将数据的数据封装到相关的搜索结果类里面,进行界面上的显示,下面的搜索结果类的封装省略}} catch (SearchException e) {e.printStackTrace();}

四、其他说明

1、如何在添加文章或内容时对内容建立索引?

可以另建立一个索引线程,在内容添加的时候将此类添加到索引中,删除时从索引中删除。核心的是使用下面的方法。

indexer.reindex(article);

indexer.delete(article);

2、如何重建所有索引?

可以在控制面板中,服务器-->插件安装-->找到此portlet,旁边有一个重建索引的按钮,点击即可完成对索引的重建。

3、索引文件存储于哪个地方?

默认存储于/data/lucene目录下面

Liferay 6.1开发学习(十五):可配置portlet开发

一、什么是可配置portlet

其实portlet本来就是可以配置的,但我们的开发大部分情况下只使用view模式,edit和config模式一般没有使用,对于使用editor和config等模式的portlet,我们可以将他们称为可配置portlet。通过使用可配置portlet,可以做许多个性化定制。

应用场景:

1、如果在首页上有展现专题的地方,可以建立一个专题展现的portlet,这个地方要展现的内容为一个图片或多个图片,点击图片可以跳转相应的链接。但是专题可能需要变化,则这里可以添加一个config或edit模式来让管理员通过配置参数来定制。

2、如首页的新闻栏目,设计时是展现的A栏目,但是实际中用户可能有变化,需要换成其他栏目,同样可以通过可配置portlet来满足。

3、如提供RSS订阅,我们可以在配置项里面设置RSS的输出方法为标题或者是摘要或者是全文,标准可以为atom或者rss2.0等配置。

4、如有一个指标展现,用户需求为可定制的,用户A可以选择柱状图、用户B可以选择折线图、用户C可以选择饼图等。

包括但不限于以上场景,需要通过配置来适用不同的情况,为用户提供可配置选项的地方都可以使用可配置portlet。

可配置portlet的开发方式

可配置portlet的开发方式,我按数据的存储方式的不同,大概的分为两种。一种为使用PortletPreferences存储的,一种为自定义数据表结构存储的。

使用PortletPreferences存储的方式为将配置数据以键值对的形式存储于PortletPreferences的相关属性字段里面。以portlet的实例ID做为识别进行存储信息,适用于配置信息不算太复杂的场景。

如果配置信息比较复杂,推荐建立相关的数据库,将配置信息存储于数据库中。本文主要介绍存储于PortletPreferences中的方法,存储于数据库和普通的portlet的数据存储方法类似。

如果按模式的不同,又可以分为edit、config、help等不同的模式。这些对应于建立portlet时选用的modes的不同而不同,这里主要介绍config模式。其他模式类似。

使用Config模式

1、需要有一个建立好的portlet。portlet的创建,参考前面的portlet简述:http://www.huqiwen.com/2012/09/03/liferay-6-1-development-study-3-portlet-explicate/

2、在Liferay-portlet.xml里面找到此portlet的相关配置,在里面添加configuration-action-class元素,如下:

<portlet><portlet-name>customjspportlet</portlet-name><icon>/icon.png</icon><configuration-action-class>xx.xxx.xxx.customjspportlet.CustomJspConfigurationAction</configuration-action-class><instanceable>true</instanceable><header-portlet-css>/css/main.css</header-portlet-css><footer-portlet-javascript>/js/main.js</footer-portlet-javascript><css-class-wrapper>customjspportlet-portlet</css-class-wrapper></portlet>

3、建立CustomJspConfigurationAction类,此类继承自DefaultConfigurationAction即可。

4、重写其中的render方法。如下。

public String render(PortletConfig portletConfig, RenderRequest renderRequest, RenderResponse renderResponse){
String portletId = renderRequest.getParameter("portletResource");PortletPreferences preferences = PortletPreferencesFactoryUtil.getPortletSetup(renderRequest, portletId);
renderRequest.setAttribute("customjspConfig_page_title",preferences.getValue("customjspConfig_page_title", StringPool.BLANK));renderRequest.setAttribute("customjspConfig_page_link",preferences.getValue("customjspConfig_page_link", StringPool.BLANK));
return "/html/CustomJspPortlet/config.jsp";
}

这个方法是点击portlet中的配置时进入的方法。在这个方法里面做以下几件事情。

1):我们获取到了当前portlet的PortletPreferences。

2):从PortletPreferences里面获取key为customjspConfig_page_title和customjspConfig_page_link的数据,并将他们放到request里面。

3):告诉Liferay我的配置页的JSP的路径是哪个。

5、编写config.jsp页面。config.jsp页面里面是我们要配置的一此参数信息,大部分情况下是一个展现的表单。主要内容可以参考如下(从项目中截取的部分代码,为说明问题已经简化):

<form action="<liferay-portlet:actionURL portletConfiguration="true" />" name="<portlet:namespace />fm" id="<portlet:namespace />fm" method="post">
<ul><li><span>标题:</span><input tabindex="1"  type="text" name="<portlet:namespace />customjspConfig_page_title" id="<portlet:namespace />customjspConfig_page_title" value="<%=title%>" /></li><li><span>链接地址:</span><input id='<portlet:namespace />custom_page_link' name='<portlet:namespace />customjspConfig_page_link' type="text" value="<%=link %>" /> </li><li><input type="button" value="" οnclick="<portlet:namespace />saveConfig()"></li>
</ul>
</form>

这里的关键点为form的action,所有这种模式的可配置portlet的action都可以固定为:<liferay-portlet:actionURL portletConfiguration="true" />。

6、服务端保存配置信息的处理。步骤5中的action会进入步骤3建立的配置类的processAction方法。在上面的配置类里重写processAction方法。里面的内容如下:

public void processAction(PortletConfig portletConfig, ActionRequest actionRequest, ActionResponse actionResponse)throws Exception {String portletResource = ParamUtil.getString(actionRequest, "portletResource");
PortletPreferences preferences = PortletPreferencesFactoryUtil.getPortletSetup(actionRequest, portletResource);
if (Validator.isNotNull(preferences)) {
//从request里面取数据String title = ParamUtil.getString(actionRequest, "customjspConfig_page_title");String link = ParamUtil.getString(actionRequest, "customjspConfig_page_link");
//将数据以键值对的形式填充到preferences里面preferences.setValue("customjspConfig_page_title", title);preferences.setValue("customjspConfig_page_link", link);
//存储数据到数据库中,持久化数据preferences.store();SessionMessages.add(actionRequest, "success");}super.processAction(portletConfig, actionRequest, actionResponse);}

到这里已经完成了可配置portlet的配置部分的开发。

在view.jsp中使用配置数据

前面步骤开发的配置数据的目标是为了在view.jsp中使用,在view.jsp中使用可以在view.jsp的action中使用,也可以直接在view.jsp中直接提取,方法为:

PortletPreferences preferences = renderRequest.getPreferences();String title = preferences.getValue("customjspConfig_page_title", StringPool.BLANK);String link = preferences.getValue("customjspConfig_page_link", StringPool.BLANK);

通过这样的方法即可取到前面的配置信息,取取的数据具体怎么展现,怎么使用,根据不同的业务场景有所不同。

Liferay开发学习(1)相关推荐

  1. Liferay开发学习Part6:Service Builder

    2019独角兽企业重金招聘Python工程师标准>>> 一.什么是Service Builder? Service Builder是liferay IDE提供的一种代码生成方案,开发 ...

  2. Liferay Portal学习笔记-coldTear

    [原创]Liferay Portal学习笔记(一):安装 一. 简单安装 1)下载并安装 JDK1.5 1.  下载并安装 Windows Platform J2SE(TM) Development ...

  3. Unity 创建2D平台游戏开发学习教程

    了解如何使用C#在Unity中创建您的第一款2D平台游戏 你会学到什么 使用Unity创建2D奥运会 使用可脚本化的对象和单一模式 使用良好的编程实践 创造武器和射弹 使用可脚本化的对象和委托模式创建 ...

  4. C#和Unity编码和游戏开发学习教程

    MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz,2 Ch 语言:英语+中英文字幕(根据原英文字幕机译更准确) |时长:110节课(26小时25分钟)|大小解压后:18.6 ...

  5. 初级java开发学习路线_成为初级全栈Web开发人员的10分钟路线图

    初级java开发学习路线 So you have started your journey into the world of web development. But what do you lea ...

  6. ios开发学习-手势交互(Gesture)效果源码分享

    qianqianlianmeng ios开发学习-手势交互(Gesture)效果源码分享 All Around Pull View 介绍:实现视图四个方向(上下左右)都能够拖动更新(pull to r ...

  7. 这可能是东半球最保姆级的后台服务器开发学习路线

    作者 | 编程指北 来源 | 编程指北(id :cs_dev) 前言 这一篇的主题是「Linux C/C++ 服务器/后台开发学习路线」. 这样的文章相信大家都见得不少了,写之前也非常忐忑,能不能和其 ...

  8. AutoCAD.net/Map 3D/AIMS/MapGuide/Civil 3D二次开发学习指南

    作者:杜长宇 Autodesk Infrastructure Map Server(AIMS)/MapGuide API二次开发学习指南 Autodesk Infrastructure Map Ser ...

  9. MongoDB开发学习开天辟地,经典入门

    如果你从来没有接触MongoDB或对MongoDB有一点了解,如果你是C#开发人员,那么你不妨花几分钟看看本文.本文将一步一步带您轻松入门. 阅读目录 一:简介 二:特点 三:下载安装和开启服务器 四 ...

最新文章

  1. 干货丨八大基础概念带你入门机器学习!
  2. VC++大数据量绘图时无闪烁刷屏技术实现
  3. python3下载教程-Python3完全零基础入门精讲 全套视频教程
  4. 【Python web 开发】viewset 实现商品详情页的接口
  5. 腾讯云安全组规则导出
  6. 又栽了?苹果侵犯高通三项专利需赔偿3100万美元
  7. 用javascript来完成显示和隐藏的案例
  8. vscode git使用_vscode中使用git
  9. python多态_python 多继承及多态
  10. JAVA 基础(0)教学视频的选择和笔记本的选择
  11. 又一个程序员突然倒地,身体这件事一定要警钟长鸣!
  12. ubuntu共享文件夹不显示及设备空间不足解决[随手笔记]
  13. (一)《数字电子技术基础》——引言
  14. 元气森林,真能在中国再造一个“可口可乐”吗?
  15. html文件可以打开,asp文件打不开的原因
  16. zencart商品批量设置为免运费
  17. 82.纯CSS液体加载特效
  18. 翻译: 给有野心的19岁少年的建议——Sam Altman
  19. 超过 3K + 的一个开源弹框库,使用方便,功能强大,简直碉堡了
  20. html6.0腾讯,腾讯CDC:QQ电脑管家6.0项目小结

热门文章

  1. 香港监管机构收紧加密货币相关法规
  2. 关于U盘数据提示RAW,需要格式化,数据恢复笔记
  3. 核心单词Word List 42
  4. 7、ARM嵌入式系统:LCD/数码管/OLED初始化
  5. spring学习笔记之配置文件applicationContext.xml
  6. 麦兜搞it python_一位大牛整理的python资源(转)
  7. 3D Slicer简单三维重建
  8. 基于requests-html的python爬虫
  9. 迅雷网页嗅探下载链接的实现
  10. 通达OA发送内部邮件