序言

CUBA 平台是一个开源框架,目的是为了将业务应用系统的开发过程流程化。框架集成了可靠的架构、开箱即用的企业级组件和高效的工具,因此您能比以往更快的交付现代 Web 应用程序。

在这个快速开始教程,我们将介绍一些 CUBA 的入门知识,开发一个非常简单,但是功能完备的会议计划应用系统。这个例子会展示创建任何 Web 应用程序都需要的三个功能:如何设计数据模型、如何操作数据、如何创建业务逻辑,此外,还将演示如何用 CUBA 创建用户界面。做为示例,我们将创建一个简单的、但是功能完整的应用版本,用来帮助大会做会议计划。实际上通过观看这个入门教程,足以使您能开始开发您自己的 CUBA 应用程序,我们现在就开始。这个入门教程中,我们将会使用 CUBA Studio, 所以请在开始前先安装并接受试用许可协议,以便能使用可视化设计器。

示例代码仓库: https://github.com/cuba-platform/sample-session-planner.

创建一个空项目

我们使用 Intellij IDEA 菜单创建一个空的 CUBA 项目,并命名为 SessionPlanner。 我们将使用 Java 8 作为默认的 JDK。

text

我们将使用一个内存数据库 - HSQLDB。

text

第一次启动CUBA Studio的时候,它会提示你申请一个CUBA Studio的商业试用版。

text

免费试用28天,提供一些非常有用的可选的设计器,我们来开始使用这个商业试用版。

创建数据模型

第一个任务 - 创建实体。业务领域模型只包含两个类:Speaker 和 Session。其关系为一对多。一位发言人可以主持多个会议。

text

做为新手, 我们来创建 Speaker 实体。可以用 IDEA 中项目欢迎页的一个链接来创建:

text

或者右键点击 Studio 界面左侧 CUBA 项目树的 Data Model 节点,然后选择 New -> Entity。

text

输入实体名 - Speaker 并按照需求创建其属性:

名称类型必须属性其他约束
firstNameString (255)
lastNameString (255)
emailString (1024)“Email” 验证器

在 CUBA 中我们使用标准的 JPA 实体,您可以用代码编辑器或者可视化设计器创建实体。只要点击 “+” 图标为实体添加属性,CUBA Studio 会为您生成类成员变量。

text

在 CUBA 中,您可以指定一个格式将实体以字符串的形势展示在 UI 中,这就是 Instance Name。对于 speaker,我们选择姓和名(first 和 last name)。

text

如果我们看一看实体设计器底部的 Text 标签页,可以看到这就是一个使用了 JPA 注解的 Java 类。如果需要,也可以手动修改生成的代码,修改后切换到 Designer 标签页,设计器也会反映出代码的改动。

同样,您也能看到DDL预览文件,需要的话,可以自己创建索引。假如我们预见会有很多对 “姓(last name)” 的搜索,为了使搜索更有效率,我们可以对 last name 字段添加索引。

text

接下来,我们创建 Session 实体并将其关联至我们的 Speaker 类。字段需求表如下。会议结束时间是一个计算值,为开始时间加持续时间。

名称类型是否必须
topicString (255)
startDateLocalDateTime
endDateLocalDateTime
speaker关联至 “Speaker” 实体,多对一关系
durationInteger
descriptionString (2000)

我们要加一个对speaker的必须的引用。关系为多对一。所以我们定义一个 association 关联,叫做 speaker,它指向 speaker 类。最终,字段定义结果大概如下:

text

创建计算属性

现在我们为session的结束时间创建一个可以自动计算的属性。首先添加getter方法,命名为 getEndDate。用 @MetaProperty 注解这个方法。对于计算属性,需要指定需要加载的属性。在我们这个例子中,需要加载的是 startDate 和 duration,接下来就是添加计算逻辑。

@Transient
@MetaProperty(related = {"startDate", "duration"})
public LocalDateTime getEndDate() {
   return (startDate != null && duration != null) ? startDate.plusHours(duration) : null;
}

注意这里Studio会高亮这个方法,因为我们需要给属性指定文本标签,键入 Alt+Enter 然后把文本加到消息包中。

text

这就行了。领域模型创建好了。

生成 CRUD 界面

CUBA Studio 带有 UI 界面生成向导,可以帮我们创建基本,但是很有用的 UI 界面:

  • 浏览界面 - 在数据网格中展示实体列表
  • 编辑界面 - 使用类似表单的界面编辑一个实体实例

首先,我们创建处理 Speaker 的界面。由于实体结构非常简单,在创建界面时我们可以直接使用默认参数。点击实体设计器顶部的 Screens 菜单下的 Create Screen 菜单项即可打开界面创建向导。

text

也可以在 CUBA 项目树中右键点击一个实体,然后选择 New -> Screen 菜单项打开界面创建向导。

text

对于 Speaker 实体,我们为其创建默认的浏览界面和编辑界面。在向导的 Screen Templates 标签页选择 Entity browser and editor screens,然后点击 Next。这个实体很简单,我们可以直接使用默认参数创建界面。

text

可以看到,每个界面包含了两个部分:一个控制器类,由 Java 编写,用来处理界面内部逻辑和事件响应,以及一个 XML 布局,定义了界面的展示形式。在我们的例子中,浏览界面由 speaker-browse.xml 和 SpeakerBrowse.java 文件组成,而编辑界面由 speaker-edit.xml 和 SpeakerEdit.java 组成。源文件都在 CUBA 项目树的 Generic UI -> Screens 中。

text

请注意界面的 XML 描述中的 data 部分 - 定义了数据如何从数据库获取。

<data readOnly="true">
   <collection id="speakersDc"
               class="com.company.planner.entity.Speaker"
               view="_local">
       <loader id="speakersDl">
           <query>
               <![CDATA[select e from planner_Speaker e]]>
           </query>
       </loader>
   </collection>
</data>

通过 CUBA Studio 窗口顶部的按钮可以很容易在界面控制器、界面描述文件以及关联实体之间切换:

text

text

text

为session创建浏览和编辑界面

运行界面生成器向导,选择 Entity browser and editor screens,然后停在 Entity browser view 步骤。

在CUBA平台,实体视图(Entity View)指定了需要从数据库读取哪些字段。你可以在一个单独的文件里定义view,然后在不同的模块里使用;也可以在创建界面的时候创建内联的view。

这次我们创建一个内联的view,只选择必要的数据(字段): end date 和 speaker引用。

text

下一步你会发现相关的字段已经选上了。

text

你会发现对应的字段已经加到界面了。

text

然后,为新session设置默认的持续时间为1小时。方法是在 Component Inspector 窗口的 Handlers 标签页订阅 InitEntity 事件

text

然后在代码中设置它。

@Subscribe
public void onInitEntity(InitEntityEvent<Session> event) {
   event.getEntity().setDuration(1);
}

创建数据库

选择 CUBA -> Generate Database Scripts 菜单来为数据库创建生成sql。你可以在弹出窗口审阅生成的脚本,然后再保存为项目文件。请注意,这些脚本也是项目的一部分,可以在项目树的 Main Data Store 节点找到这些脚本。

text

如果需要添加一些特殊的内容,您可以手动修改脚本,比如额外的索引或者初始化数据的插入语句。

text

点击 Create database 按钮应用这些脚本然后创建数据库。除了我们创建的应用程序表之外,CUBA 会创建一些系统表,用来存储用户、角色、任务等信息。

好了,数据库已经创建。

运行开发模式的应用程序

要启动应用程序,可以通过点击 IDE 顶部的 “Run” 按钮。

text

也可以在主菜单选择 CUBA -> Start Application Server 。

text

等待一会您就可以使用浏览器访问应用了。URL 会在 IDEA 的运行工具框内显示。我们这个例子里是 localhost:8080/app。在浏览器里打开这个地址,然后使用 “admin” 用户登录。默认密码是 “admin”。你可以在 Application 菜单下看到实体操作界面。 然后我们给数据库添加一些数据:添加两个 speaker,然后添加两个 session,计划时间为本周剩余的时间。您可以尝试为 speaker 输入无效的 email 看看验证器是否能正常工作。

text

text

生成的界面适合只有基本操作的情况,但是现实中的 UI 通常更加复杂。

定制用户界面

除了表格视图之外,我们再添加一个日历视图。因此,在浏览界面,我们需要添加标签页控件,再将日历放入标签页,提供在日历中编辑和重新计划会议的功能。就像这样:

text

在设计器打开 session-browse.xml 文件并在组件树 Component Hierarchy 中把sessionsTable放到TabSheet中。

text

在 TabSheet 下再添加一个 tab 。

text

在上面放一个 calendar 日历组件。

text

在 Component Hierarchy 窗口选择 TabSheet 元素,然后在 Component Inspector 中选择 expanded 。Studio会要求一个ID。在CUBA中,代码通过ID标记一个界面元素。

text

为 calendarTab 和 tableTab 指定ID。然后为他们设置标题: Sessions calendar 和 Sessions table 。

text

修改calendar控件 - 指定 data container,然后使它撑满界面。

text

最后,让Sessions表格撑满界面。

text

在 CUBA 中,UI 组件能绑定到实体及其属性上。

我们将日历组件绑定到界面中获取的数据集。用 search 域找到属性然后绑定:

  • startDateProperty 到会议的开始时间
  • endDateProperty 到会议结束时间
  • captionProperty 到会议的主题
  • And descriptionProperty 到会议的描述

text

然后将日历只显示工作时间。

text

除了可视化编辑器,你也可以使用 XML 编辑。我们来添加导航按钮。

text

要查看 UI 的改动,您不需要重启应用程序,只需要在应用中重新打开界面。CUBA 框架支持界面的热部署。现在您可以看到会议都显示在日历中了。

使用界面API

当我们与 UI 进行交互的时候,会产生交互事件,因此,CUBA 提供了 API 可以订阅这些事件并进行处理。我们试试处理日历条目的点击事件,并调用会议编辑器。CUBA 提供了界面构建 API 用来操控界面,我们一会能用到。

在 session-browse.xml 选择 sessionsCalendar,然后在Component Inspector 窗口的 Handlers 标签中,选择 CalendarEventClickEvent 然后点击箭头,将它放到controller中。

text

会为您生成下面的空方法。

@Subscribe("sessionsCalendar")
public void onSessionsCalendarCalendarEventClick(Calendar.CalendarEventClickEvent<LocalDateTime> event) {
  
}

我们需要调用编辑界面来修改 session 的属性。我们来使用 EditorScreenFacet。这是一个可以用来预配置编辑界面的组件。

在 session-browse.xml 的 Component Palette窗口,找到 EditorScreen,然后把它挪到 Component Hierarchy 窗口的 window 元素下。

text

然后做以下设置:

  • ID - sessionEditDialog;
  • data container - sessionsDc;
  • edit mode - EDIT;
  • entity class - Session;
  • open mode - DIALOG;
  • screen class - SessionEdit.

text

回到事件处理代码,点击窗口顶部的 Inject 按钮然后在弹出窗口的 Screen API 区域,选择 sessionEditDialog 服务。

injection (和 subscription) 的另一个方法是 - 在编辑器中按下 Alt+Insert 然后在弹出菜单中选择 Inject :

text
需要搜索的话,键入名字然后使用键盘的上下键浏览可用的 injection。

text

把事件object中的实例作为需要编辑的实例传入,然后我们展示编辑界面。

事件处理的代码大致如下:

@Subscribe("sessionsCalendar")
public void onSessionsCalendarCalendarEventClick(Calendar.CalendarEventClickEvent<LocalDateTime> event) {
   sessionEditDialog.setEntityProvider(() -> (Session) event.getEntity());
   sessionEditDialog.show();
}

这就可以了。重新打开session浏览界面,然后通过点击日历中的session来打开编辑页面。

text

编辑页面看上去不太好看,我们调整一下宽高。在IDE中,打开界面的 XML 描述,选择 dialogMode 属性,然后把 width 和 height 设为 auto。

text

在应用中关闭编辑页面然后从日历中再次打开,现在看上去好些了。

text

添加业务逻辑

现在我们将用 CUBA Studio 来创建一个服务,实现业务逻辑,然后在一个界面中使用这个服务。此服务将用于重新安排会议,检查同一个发言人不会在同一天内有多于两场会议。

在 CUBA 项目树中右键点击 service 节点然后选择 New ->Service。会弹出服务创建对话框。输入 SessionService 做为接口名称,Studio 会自动生成 SessionServiceBean 作为实现类的名称。

text

在接口中按照下面的代码创建一个 rescheduleSession 方法:

public interface SessionService {
   String NAME = "planner_SessionService";

   Session rescheduleSession(Session session, LocalDateTime newStartDate);
}

该方法接收 session 和会议的新 startDate 作为参数,并返回更新后的 Session 实例。

要实现该方法,打开 SessionServiceBean 类的代码编辑器,可以在 CUBA 项目树的 Middleware - Services 节点找到该类:

text

可以看到类是空的,并且有错误:

text

在类中任意处按下 Alt+Insert,在弹出菜单中选择 Implement Methods :

text

选择 rescheduleSession 方法,会自动生成如下代码:

@Service(SessionService.NAME)
public class SessionServiceBean implements SessionService {

   @Override
public Session rescheduleSession(Session session, LocalDateTime newStartDate) {
       return null;
   }
}

首先,计算session计划所在那天的开始和结束时间。

@Override
public Session rescheduleSession(Session session, LocalDateTime newStartDate) {
   LocalDateTime dayStart = newStartDate.truncatedTo(ChronoUnit.DAYS).withHour(8);
   LocalDateTime dayEnd = newStartDate.truncatedTo(ChronoUnit.DAYS).withHour(19);
return null;   
}

在服务中,我们将使用 CUBA API 来访问数据 - DataManager 类。我们用它创建JPQL查询来检查在定义的时间段内是否有为 Speaker 安排的会议,并且传参数给它。然后我们检查查询结果,依据结果,我们用新的开始时间更新session实例或者直接返回原来的session实例。

在类中按下 Alt+Insert ,Inject DataManager,然后在弹出菜单中选择 Inject from :

text

在弹出框中选择 DataManager :

text

方法的实现大概如下:

@Override
public Session rescheduleSession(Session session, LocalDateTime newStartDate) {
   LocalDateTime dayStart = newStartDate.truncatedTo(ChronoUnit.DAYS).withHour(8);
   LocalDateTime dayEnd = newStartDate.truncatedTo(ChronoUnit.DAYS).withHour(19);
   Long sessionsSameTime = dataManager.loadValue("select count(s) from planner_Session s where " +
           "(s.startDate between :dayStart and :dayEnd) " +
           "and s.speaker = :speaker " +
           "and s.id <> :sessionId", Long.class)
           .parameter("dayStart", dayStart)
           .parameter("dayEnd", dayEnd)
           .parameter("speaker", session.getSpeaker())
           .parameter("sessionId", session.getId())
           .one();
   if (sessionsSameTime >= 2) {
       return session;
   }
   session.setStartDate(newStartDate);
   return dataManager.commit(session);
}

service好了,我们把它加到session浏览界面。它会在calendar的拖拽 drag-and-drop 事件中被调用。

在 session-browse.xml 中,选择 sessionsCalendar,然后在 Component Inspector 窗口的 Handlers 标签中,选择 CalendarEventMoveEvent ,然后点击箭头加到controller中。

在订阅了拖拽事件 drag-and-drop 的方法中,我们加一些代码,从事件中拿到session实体,然后传给service重新计划它。然后,我们在浏览界面的data container 中更新它。

@Subscribe("sessionsCalendar")
public void onSessionsCalendarCalendarEventMove(Calendar.CalendarEventMoveEvent<LocalDateTime> event) {
   Session session = sessionService.rescheduleSession((Session) event.getEntity(), event.getNewStart());
   sessionsDc.replaceItem(session);
}

要使得新service部署,我们需要重启应用,还是用IDEA的 Run 按钮。

text

应用重启之后,我们可以打开会议日历 - 搞定!可以拖拽会议进行重新安排了!测试一下,我们再添加一个会议并尝试重新安排。

添加品牌信息

在CUBA中,你可以直接修改资源文件,覆盖标准文本。我们来试试基于应用的业务领域 - conference planning 修改文本。

在CUBA项目树的 Generic UI - Main Message Pack 节点下,打开主消息包文件 - messages.properties 。

text

这是个标准的 java .properties 文件,所以可以自由编辑 - 添加新的消息键值或者修改已有的键值。我们修改消息来体现应用程序的用途。示例如下:

application.caption = Session Planner
application.logoLabel = Session Planner
application.logoImage = branding/app-icon-menu.png
application.welcomeText = Welcome to Session Planner!

loginWindow.caption = Session Planner Login
loginWindow.welcomeLabel = Welcome to Session Planner!
loginWindow.logoImage = branding/app-icon-login.png

menu-config.application-planner = Sessions and Speakers
menu-config.planner_Speaker.browse=Speakers
menu-config.planner_Session.browse=Sessions

由于 CUBA 有热部署功能,我们只需重新登入就能看到变化。

应用市场

框架带有 应用市场,市场内有很多组件,这样您可以非常方便的添加有用的功能,比如为已有的应用程序添加 图表地图 。可以通过 Studio 的 CUBA -> Marketplace 菜单直接安装这些组件。

text

我们来添加 Helium 扩展。这是一个可视化主题,你可以用它替换标准主题,点击 install 然后 apply 即可。

现在我们需要停止程序然后应用组件的数据库初始化脚本。

text

运行程序,打开 settings 界面,你会看到下拉列表中出现了添加的主题,选择它,然后应用。

text

重新登入 - 主题已经生效。

text

我们也可以打开主题设置界面,修改它并预览。

总结

CUBA 框架提供很多有用的 API,能帮您快速的创建业务系统。这次快速开始只演示了最基本的 CUBA 框架和 CUBA Studio 功能。访问我们的网站: cuba-platform.cn,你会看到更多的 示例指南

感谢您对 CUBA 平台有兴趣。享受用 CUBA 开发吧!