# 后端开发文档

# 逻辑模块

# 模块的CURD

M18系统提供了基本模块的操作接口,设计通用型的接口最大的目的在于可以批量化处理。

# 使用Web Service

  • 创建Entity

获取当前模块的空Entity

WsParameter param = WsLib.createWsParam("entity/create/" + getModule(), WsType.post);
param.addQueryParam("menuCode", getMenuCode());

HttpResponse response = WsLib.callWs(param);

if (WsLib.isResponseOK(response)) {
  String entryJson = WsLib.resolveResponse(response);
  if (!FacesUtil.isEmpty(entryJson)) {
   SqlEntity  result = JSON.parseObject(entryJson, SqlEntity.class);
  }
} else {
  WebUtil.showMessageInfo(response, null, "createFail");

}

HTTP 请求

POST http://[server]/jsf/rfws/entity/create/[module]?menuCode=[menuCode]

参数

名字 类型 说明
module String(Path) **必填.**当前模块的名称
menuCode String(Query) **必填.**当前菜单的名称
param json String(Query) 如果当前模块有一些特殊的逻辑需要处理,可以传入一个json的参数到checker进行处理;

返回结果

: 如果返回的结果是成功的, 可以拿到一个当前模块的空SqlEntity.

: 如果返回的结果是失败的, 可以在返回的response header中通过key‘error_info’拿到一个CheckMsg的json数组;

类型 位置 说明
成功 Body 模块的空SqlEntity
失败 Header(error_info) CheckMsg的json数组
  • 读取Entity

读取当前模块的已存在的SqlEntity

	WsParameter param = WsLib.createWsParam("entity/read/" + getModule(), WsType.get);
		param.addQueryParam("menuCode", getMenuCode());
		param.addQueryParam("id", action.getData(ActionParam.id).toString());
		param.addQueryParam("iRev", action.getDataOrDefault(ActionParam.iRev, 0).toString());

		HttpResponse response = WsLib.callWs(param);

		if (WsLib.isResponseOK(response)) {
			String entryJson = WsLib.resolveResponse(response);
			if (!FacesUtil.isEmpty(entryJson)) {
				entity = JSON.parseObject(entryJson, SqlEntity.class);
				
			}

		} else {
			WebUtil.showMessageInfo(false, null, null, "readFail");

		}

HTTP 请求

GET http://[server]/jsf/rfws/entity/create/[module]?menuCode=[menuCode]&id=[id]

参数

名字 类型 说明
module String(Path) **必填.**当前模块的名称
menuCode String(Query) **必填.**当前菜单的名称
param json String(Query) 如果当前模块有一些特殊的逻辑需要处理,可以传入一个json的参数到checker进行处理;
id long(Query) **必填.**需要读取entity的id
iRev int(Query) 如果是要读取旧版本,请传入正确的版本号;如果读取最新版本,iRev可以不填或填0

返回结果

: 如果返回的结果是成功的, 可以拿到一个当前模块的正确SqlEntity. : 如果返回的结果是失败的, 可以在返回的response header中通过key‘error_info’拿到一个CheckMsg的json数组;

类型 位置 说明
成功 Body 模块的正确SqlEntity
失败 Header(error_info) CheckMsg的json数组
  • 保存Entity

保存当前模块的SqlEntity

	WsParameter param = WsLib.createWsParam("entity/save/" + getModule(), WsType.put);

		param.addQueryParam("menuCode", getMenuCode());
		param.addQueryParam("entity", JSON.toJSONString(getEntity()));

		HttpResponse response = WsLib.callWs(param);

		if (WsLib.isResponseOK(response)) {
			long recordId = Long.parseLong(WsLib.resolveResponse(response));
			action.setData(ActionParam.id, recordId);
		}

		WebUtil.showMessageInfo(response, "saveSuccess", "saveFail");

HTTP 请求

PUT http://[server]/jsf/rfws/entity/save/[module]?menuCode=[menuCode]&entity=[SqlEntity json String]

参数

名字 类型 说明
module String(Path) **必填.**当前模块的名称
menuCode String(Query) **必填.**当前菜单的名称
param json String(Query) 如果当前模块有一些特殊的逻辑需要处理,可以传入一个json的参数到checker进行处理;
entity jsonString(Query) **必填.**SqlEntity 的JSON String

返回结果

: 如果返回的结果是成功的, 可以拿到保存的SqlEntity的id. : 如果返回的结果是失败的, 可以在返回的response header中通过key‘error_info’拿到一个CheckMsg的json数组;

类型 位置 说明
成功 Body 模块SqlEntity的id
失败 Header(error_info) CheckMsg的json数组
  • 删除Entity

读取当前模块的已存在的SqlEntity

	WsParameter param = WsLib.createWsParam("entity/delete/" + getModule(), WsType.delete);

		param.addQueryParam("menuCode", getMenuCode());
		param.addQueryParam("id", action.getData(ActionParam.id).toString());
		HttpResponse response = WsLib.callWs(param);

		WebUtil.showMessageInfo(response, "deleteSuccess", "deleteFail");

HTTP 请求

DELETE http://[server]/jsf/rfws/entity/delete/[module]?menuCode=[menuCode]&id=[id]

参数

名字 类型 说明
module String(Path) **必填.**当前模块的名称
menuCode String(Query) **必填.**当前菜单的名称
param json String(Query) 如果当前模块有一些特殊的逻辑需要处理,可以传入一个json的参数到checker进行处理;
id long(Query) **必填.**需要删除entity的id

返回结果

如果返回的结果是失败的, 可以在返回的response header中通过key‘error_info’拿到一个CheckMsg的json数组;

类型 位置 说明
成功 Status Status是200代表成功
失败 Header(error_info) CheckMsg的json数组

# 直接使用EJB

  • 创建Entity

和Web Service 的方式相比,EJB的方式不会使用cache;

@EJB
CawEntityCurdAction curdEJB;

public void  create(String module,string menuCode){
  SeCreateParam param = new SeCreateParam(module);
  param.setMenuCode(menuCode);

	EntityResult result = curdEJB.createEntity(param);
}

参数SeCreateParam

只需传入模块的名称和菜单的名称;

如果当前模块有一些特殊的逻辑需要处理,可以将参数写入SeCreateParam的jsonParam里面,在checker进行处理;

返回结果

: 如果返回EntityResult的SqlEntity不为null,则获取到模块的空SqlEntity

: 如果返回EntityResult的SqlEntity为null,则查看里面的List<checkMsg> 得到错误信息;

  • 读取Entity

和Web Service 的方式相比,EJB的方式不会使用cache;

@EJB
CawEntityCurdAction curdEJB;

public void  read(String module, String menuCode, long id, int iRev){
 SeReadParam param = new SeReadParam(module);
		param.setMenuCode(menuCode);

		param.setEntityId(id);
		param.setIrev(iRev);

	EntityResult result = curdEJB.loadEntity(param);
}

参数SeReadParam

只需传入模块的名称,菜单的名称,对应SqlEntity的id,如果需要查询旧版本,就传入iRev;

如果当前模块有一些特殊的逻辑需要处理,可以将参数写入SeReadParam的jsonParam里面,在checker进行处理;

返回结果

: 如果返回EntityResult的SqlEntity不为null,则获取到模块的正确SqlEntity

: 如果返回EntityResult的SqlEntity为null,则查看里面的List<checkMsg> 得到错误信息;

  • 保存Entity
@EJB
CawEntityCurdAction curdEJB;

public void  save(String module, String menuCode, SqlEntity entity){
		SeSaveParam param = new SeSaveParam(module);
		param.setMenuCode(menuCode);
		param.setSqlEntity(entity);
		param.setInsertMir(true);

	CheckResult result = curdEJB.updateEntity(param);
}

参数SeSaveParam

只需传入模块的名称,菜单的名称,对应SqlEntity,还有如果要将旧数据写入旧数据表,请设置insertMir 为true;

如果当前模块有一些特殊的逻辑需要处理,可以将参数写入的jsonParam里面,在checker进行处理;

返回结果

: 如果返回CheckResult的pass为true,则保存成功,可以通过result的getEntityId()获取SqlEntity的id

: 如果返回CheckResult的pass为false,则查看里面的List<checkMsg> 得到错误信息;

  • 删除Entity
@EJB
CawEntityCurdAction curdEJB;

public void  delete(String module, String menuCode, long id){
		SeDeleteParam param = new SeDeleteParam(module);
		param.setMenuCode(menuCode);
		param.setEntityId(id);
		param.setInsertMir(true);


	CheckResult result = curdEJB.deleteEntity(param);
}

参数SeDeleteParam

只需传入模块的名称,菜单的名称,对应SqlEntity的id,还有如果要将旧数据写入旧数据表,请设置insertMir 为true;

如果当前模块有一些特殊的逻辑需要处理,可以将参数写入的jsonParam里面,在checker进行处理;

返回结果

: 如果返回CheckResult的pass为true,则删除成功,

: 如果返回CheckResult的pass为false,则查看里面的List<checkMsg> 得到错误信息;

# Checker与事务

模块Curd过程中,可以通过checker进行一些检查或者其他操作;checker在module.xml中配置

**checker方法的设置:**在module.xml配置的check类中定义方法,方法有@EntityCheck的注释,方法的参数只有一个:SeCurdParam

public class EmployeeChecker {

	@EntityCheck(range = CheckRange.BEFORE, type = CheckType.SAVE)
	public CheckMsg checkMothed(SeCurdParam param) {
      return null}

@EntityCheck参数介绍

名称 必填 类型 说明
type ture CheckType Check有四种类型,分别是SAVE(1), DELETE(2), CREATE(3), READ(4);分别对应curd的四种操作
range true CheckRange CheckRange有六种类型,BEFORE(1),AFTER(2),BOTH(3),SAVING(4),ROLLBACK(5)),DELETEING(6);其中SAVING只能在保存的时候使用;DELETEING只能在删除的时候使用;
checkOrder false int 默认为0,数值越小,越先执行;
actionBreaker false boolean 默认为false,如果设置为true;当check方法运行返回错误时,会立刻终止操作程,返回结果
overrideMethod false String 可以使用Class.method 的方式去覆盖其他Checker的method;

保存的过程

  1. curdEJb调用Save方法;

  2. 运行标志了CheckType =SAVE,CheckRange =BEFORE或者CheckRange =BOTH的方法,如果有其中一个方法返回的CheckMsg 不通过,就返回错误信息,不继续保存的操作;这个时候的check方法以查询为主,主要是检查数据的准确性,不会做一些修改数据库的操作;因为这个时候还没有在save的事务里面;

  3. 如果Save-Before没有错误返回,将进入save的事务,先从SeSaveParam.getSqlBeforeDtos()取出sql List,逐一运行List的sql,getSqlBeforeDtos()里面的信息,一般是从before Check写入;发生错误后,将中断事务,运行

CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;

  1. 如果sql Before没有发生错误,将继续下一步,将entity保存到数据库,保存到数据库的过程如果发生错误,将中断事务,运行CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;

  2. 将entity保存到数据库后,下一步运行CheckType =SAVE,CheckRange =SAVING的checker方法;如果发生错误,将中断事务,运行CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;

  3. .如果SAVING的Checker没有错误,将从SeSaveParam.getSqlAfterDtos()取出sql List,逐一运行List的sql,getSqlAfterDtos()里面的信息;发生错误后,将中断事务,运行

CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;

  1. 如果Sql After也没有发生错误,这个时候将会提交整个save的事务;
  2. 接下来将运行标志了CheckType =SAVE,CheckRange =AFTER或者CheckRange =BOTH的方法,这些方法如果发生错误,返回错误信息也不会回滚之前的事务了,所以这时候尽量不要返回错误信息,只做一些确保正确的操作即可;

请参考下图进一步了解

mssst

删除的过程

删除的过程与保存的过程基本一样,只是deleteing的checker方法,在删除数据库的Entity操作之前运行;

创建的过程

创建的过程相比保存的过程,没有sql Before,没有 Saving,没有 sql After ;

读取的过程

读取的过程相比保存的过程,没有sql Before,没有 Saving,没有 sql After ;

模块的SEARCH

配置对应的stSearch.xml, 模块的名称与stSearch的名称需要相同

# 操作非模块数据

M18对于模块外的数据提供了SqlTableCurdEAO进行读写操作。:如果对应的table是module中的数据,请审慎使用这种方法。

  • 读取数据

使用SqlTableCurdEAO的read(StReadParam param)方法读取数据库table的数据;

@EJB
protected SqlTableCurdEAO curdEAO;

public void read(long id){
  StReadParam param = new StReadParam();
  param.setTableName("draft");	
  param.setId(id);
  
  SqlTable data = curdEAO.read(param);
}
  • 创建空表结构&保存数据

使用SqlTableCurdEAO的genEmptyTable(String tableName)可以获取一个有表结构的空的sqlTable;

使用SqlTableCurdEAO的save(StUpdateParam param)可以保存table的数据;

StUpdateParam 参数一定需要传入tableName,和SqlTable的数据

@EJB
protected SqlTableCurdEAO curdEAO;

public long save(DraftDto draft){
  SqlTable sqlTable = emptyTable.genEmptyTable("draft");
  sqlTable.addRow();

  sqlTable.setBoolean(1, "autoSave", draft.isAutoSave());
  sqlTable.setString(1, "draftCode", draft.getDraftCode());
  sqlTable.setObject(1, "lastModifyDate", new Date());
  ***//do some coding

  StUpdateParam param = new StUpdateParam();
  param.setTableName("draft");
  param.setData(sqlTable);

  try {
    long insertId  = curdEAO.save(param);

    return insertId;
  } catch (SQLException e) {
    CawLog.logException(e);
  }
  reutrn 0;
}
  • 删除数据

使用SqlTableCurdEAO的delete(StDeleteParamparam)方法删除数据库table的数据;

@EJB
protected SqlTableCurdEAO curdEAO;

public void delete(){
  StDeleteParam param = new StDeleteParam();
  param.setTableName("draft");	
  param.setId(1);
  try {
    curdEAO.delete(param);
  } catch (SQLException e) {
    CawLog.logException(e);
  }
}

# 系统表描述

按照前面逻辑模块中的描述,M18使用系统表来描述系统的一些通用功能以及控制。不单单逻辑模块,系统中的很多功能和约定都是通过系统的方式来进行控制和处理的。

xml默认存放在share项目下面。 路径大致为: /main/resources/META-INF/*.xml

# 系统表关键字规范

由于大部分的系统表使用xml格式进行编写,请注意xml格式的一些要求和限制。 虽然xml并不是在服务器中间价运行的具体代码,不过由于配置将影响系统的大部分关键过程,请谨慎的修改。 M18推荐尽量使用小写字母进行编号的标识。并要求不要使用任何特殊符号。 对于table module等编号,推荐使用App的名字作为前缀。 在命名常用名字的时候,需要考虑是否会和其他App的名字产生冲突,并考虑合适的命名规则。

# datadict.xml

dataidct用于描述mysql数据库中的table view 以及对应的column信息。 同时,他会定义table的约束信息。(Index, Foreign Key, Unique Key等) 由于datadict的信息会直接生成数据库表信息,请记得避免使用数据库的关键字以及任何容易出现问题的命名方式。

:系统在启动的时候,仅仅按照xml和数据库结构的差异进行数据库的更新。

:关于View,Stored Procedure,Function的同步将在本部分的后面介绍。

table的属性描述

名字 必填 类型 默认值 说明
name 字符 table的名字,在M18中仅仅运行使用小写字母。并不允许使用任何符号. table由于会生成到数据库中,创建的时候需要考虑全系统唯一
sys 布尔 false 标记是否为系统表。 系统表不会检查数据权限,不允许导入导出
mess 字符 table的MessCode
pk 字符 id 用于标记当前table的主键栏位。在M18目前仅允许使用id栏位作为PK。:未来将删除
virtual 布尔 false 如果标记为true,表示当前记录的信息不需要影响数据库结构。一般View 或者一些虚拟的table需要使用。
supportUdf 布尔 true 用于标记是否允许使用者添加自定义栏位
templet 布尔 false 如果标记为true,不会影响数据库结构。注,该信息在内存无法找到,仅仅影响初始化过程。其他table可以使用Inherit的方式来继承本templet里面的信息。
extend 布尔 false 如果为true,表示来用扩展已经存在的table
onlyInMain 布尔 false 一般而来,我们一个table的xml信息描述会生成两个数据库的table(xxx + xxx_v),如果这个参数为true,则仅仅生成xxx。

关于Inherit的属性描述

名字 类型 说明
name 字符 用于描述需要继承的table templet的名字,只有table描述中templet为true的才可以使用。

: 如果在继承后需要修改某些栏位的属性,在Column中直接添加对应的描述即可。不过一般情况下 并不推荐这样处理。因为容易导致误解。

Column的属性描述

名字 必填 类型 默认值 说明
name 字符 栏位名字,表内唯一,推荐使用驼峰方式命名,不允许重名(忽略大小写下面的重名)。 不允许使用任何特殊符,例如 "*", "_", "-"。
type 字符 用了标记栏位属性
mess 字符 column的MessCode
length 整数 用于标记数据长度,在类型为char/number的情况下应该填写
decimal 整数 用于标记number类型栏位的小数位,仅仅number的时候进行填写
defValue 字符 用于标记栏位对应的默认值
defPattern 字符 用于标记栏位对应的Pattern
required 布尔 false 标记当前栏位是否必填,在save的时候 M18会根据这个进行检查
identity 布尔 false 标记是否自增,一般仅仅用于id栏位
i18nField 布尔 false 用于标记当前栏位是否支持多语言设定。仅仅char栏位可以使用本属性
i18nSrc 字符 在i18nField为true的时候才能使用。用于标记当前栏位跟进其他栏位自动开启i18nField。格式(Table.Column)
allowNull 布尔 false 用于标记对应的栏位在数据库中是否允许使用null. :在数据库中有些类型的栏位有null的要求。
buildIn 布尔 false 用于标记是否系统栏位,在一些功能(例如 Search的可选字段)里面会将系统栏位使用文件夹和其他栏位分割出来。
dataImport 布尔 true 用于标记当前栏位是否支持导入
dataExport 布尔 true 用于标记当前栏位是否支持导出
skipLookup 布尔 false 如果希望在search的时候该栏位不可见 将其标记为true
skipAccess 布尔 false 用于标记当前栏位在生成单据权限的Sql时候,是否需要参与考虑
checkLookupVal 布尔 false 用于标记对应的栏位在导入的时候,是否需要Search权限重新检查对应的栏位是否允许。
genRuleDate 布尔 true 用于标记当前栏位是否可以作为CodeFormat生成过程中的日期栏位
batchUpdate 布尔 true 用于标记当前栏位是否允许批量更新。:有逻辑计算或无批量更新需要的栏位尽量不要进行标记。
udfUpdate 布尔 用于标记对应的栏位是否支持自定义栏位更新
skipFLR 布尔 false 如果为true,改栏位将不受字段级权限控制
dataDupCheck 布尔 false 用于标记对应的栏位是否支持数据重复检查。如果没有设置任何值,对应的栏位类型是字符串或者日期,或者pattern是lookup类型的,默认支持数据重复检查。
dataEasy 布尔 dataImport 用于标志对应的栏位是否支持智能数据处理,如果没有设置任何值,默认使用dataImport的值
lookupCurrentModule 布尔 false 用于标志对应的栏位是否lookup当前模块

栏位属性说明

名字 说明
int_unsigned 对应Java类型long :所有的id字段目前都是用int_unsigned
bigint 对应Java类型long,非必要的情况下不要使用
bit 对应java类型boolean
datetime 对应日期时间栏位 java类型date
date 对应日期栏位 java类型date
nvarchar/varchar 对应java类型String, 这种类型需要指定需要使用的length
text/longtext 对应java类型String, :text的可用长度有限,如果你确定需要足够的长度的栏位的时候请使用longtext
numeric 对应java类型double, 这种类型可以指定length+decimal
:文件类型栏位的说明请参考 文件处理

Index的属性描述

名字 类型 说明
name 字符 Index的名称 一个table里面的index不允许重名
columns 字符 用于标记当前index需要使用的栏位,多个栏位使用;分开
unique 布尔 用于标记是否创建唯一键约束

fk的属性描述

名字 类型 说明
name 字符 Foreign Key的名字
columns 字符 FK在当前table的栏位
refTable 字符 FK指向的table的名字
refColumns 字符 FK指向的栏位,允许空白,在空白的时候默认使用id栏位

:系统启动过程中将使用datadict的信息更新数据库的信息。特别的,对应栏位只增不删,如果有需要,请自己编写代码处理。

如果你需要将一个栏位的属性修改,请另外新增一个栏位。因为数据修复过程可能存在无法预料的问题。然后在必要的时候,自己将不需要的栏位删除。

:目前直接修改栏位属性虽然是允许操作的,不过系统将不会因此造成的问题做任何的提示或者修复。

另外一个需要特别说明的就是将column的长度改小的时候,请注意数据库系统将可能会截断数据,而且将会无法修复。

示例:

<?xml version="1.0"?>
<dd xmlns="http://www.multiable.com/datadict">

<!-- test info -->
<table name="test_base" mess="test_base" templet="true">
	<column name="id" type="int_unsigned" mess="core.id" identity="true"/>
	<column name="iRev" type="int" mess="core.iRev" defValue="0" defPattern="iRev" dataImport="false"  />
	<column name="createDate" type="datetime" mess="core.createDate" defValue="NOW()" defPattern="datetime" dataImport="false" dataExport="false" buildin="true" />
</table>
  
 <table name="test" mess="test" pk="id">
	<inherit name="test_base" />
	<column name="code" type="varchar" mess="code" length="100" defPattern="code" required="true" />
	<column name="desc" type="varchar" mess="desc" length="200" defPattern="desc" i18nField="true"/>
   
    <index name="code" columns="code" unique="true" />
  </table>
  
</dd>

# 数据库信息的同步

系统启动的时候,除了会根据datadict.xml更新table信息,M18还支持开发者更新View,Stored Procedure,Function等。对于datadict信息,我们将这部门描述为other Information。

具体做法如下:

M18要求将文件存放在ejb项目的如下路径ejb/src/main/resources/sql/

文件夹名字分别是view proc func

每个独立的Object应该独立存储为一个文件。在对应的文件脚本里面一般均是删除已经存在的对象,然后在重新创建。

示例:一个Stored Procedure的脚本

drop procedure if exists test_dm;

DELIMITER $$

CREATE PROCEDURE test_dm(IN dbname varchar(100))  
BEGIN
    select * from test A where A.db = dbname;
END$$

DELIMITER ;

更新的过程如下图

mssst

# pattern.xml

在M18中,系统使用pattern来对应一个栏位大致的表现方式。以及做一些常见的界面显示属性的规定。

record属性说明

名字 必填 类型 默认 说明
code true string pattern的名称
type true string pattern的类型;有七种可以选择填写:text;number;date;options;boolean;lookup;color
info false string pattern的一些info说明;

text标签介绍,当record的type选择了text,那么下一级的标签要使用text标签;用于标志当前pattern定义的是一个文本类型;

名字 必填 类型 默认 说明
length true int 定义text的长度
trim fasle boolean false 当设置为true,会将栏位输入值前后的空格删掉
upperCase false boolean false 当设置为true,栏位输入的字母会变成大写
mask false string 定义栏位输入一个固定格式
regex false string 定义栏位的正则表达式
html false boolean fasle 如果设置为true,界面将会使用html组件显示栏位

number标签介绍,当record的type选择了number,那么下一级的标签要使用number标签;用于标志当前pattern定义的是一个数字类型;

名字 必填 类型 默认 说明
length true int 定义number的总长度
decimal fasle int 0 定义小数位的长度,默认为0;
max false double false 定义number的最大值
min false double 定义number的最小值
noSep false boolean fasle 如果为true,数值将会有千分位 “,”
negativeSymbol false boolean fasle 如果为true,当数值为负数时,将会使用括号将数值包住

lookup标签介绍,当record的type选择了lookup,那么下一级的标签要使用lookup标签;用于标志当前pattern定义的是一个查询数据类型;

名字 必填 类型 默认 说明
searchType true string 用于标记栏位对应的stSearch

options标签介绍,当record的type选择了options,那么下一级的标签要使用options标签;用于标志当前pattern定义的是一个下拉框选择类型;

名字 必填 类型 默认 说明
valueClass true string string 用于定义option的数据类型;可选:String,Integer,Double,Combobox
emptyValue false string 用于定义当没有选中任何option时应该是什么值

options里面的子标签option介绍;

名字 必填 类型 默认 说明
value true string 选中后的值,真实保存的值
label true string 显示在页面上看到的值,可以填messCode

option里面的子标签icon介绍;

名字 必填 类型 默认 说明
name false string 图标的名字
library false string 图标库的系统路径
url false string 图标的网络路径
style false string 图标的css类型
styleClass false string 图标的css类型class

特殊pattern列表

请注意下面的均系统特殊的pattern,不推荐新增其他内容 而是直接使用

	<!-- this is for file.info the id of filedata -->
	//用于对应栏位存的是文件的fileId
	<record code="fileId" type="number"><number length="12" /></record>

	<!-- this is for image, use code for safe! -->
	//用于对应栏位存的是图片的imgCode
	<record code="imgCode" type="text"><text length="60" /></record>
	
	<!-- date with time -->
	//用于显示日期加时间
	<record code="datetime" type="date"></record>
	
	//用于显示时间,格式为 HH:MM
	<record code="time" type="text"><text length="10" /></record>

	//用于显示日期
	<record code="date" type="date"></record>

	//用于使用color组件
	<record code="color" type="color"></record>

	//用于表示栏位存的数据可以无限长度,注:仅可以存储2^32长度的信息,不够的情况下请使用longtext
	<record code="charMax" type="text"><text length="-1" /></record>

	//用于表示栏位存的是json格式的string
	<record code="json" type="text"><text length="-1" /></record>

	//用于表示栏位存的数据可以无限长度,datadict栏位对应的type一般为longtext
	<record code="longtext" type="text"><text length="-1" /></record>

	//用于表示使用html组件显示
	<record code="html" type="text"><text length="-1" html="true" /></record>

用户在使用M18系统的时候,需要通过菜单访问对应的模块。所以在M18授权的时候,也是通过菜单的方式进行控制的。系统里面使用navmenu.xml来定义系统的菜单。

menuType的描述,menuType用于定义菜单类型以及对应的图示。

名字 子元素名字 说明
name 用于表示一个菜单类型
desc menuType的MessCode
icon name icon的名字 (icon用于菜单显示)
icon library icon所在位置,系统CSS通过library和name来找到对应的图片
image name image的名字 (image用于图像化菜单)
image library image所在的位置

系统目前有

  • FM 用于表示基础资料模块

  • TRAN 用于表示交易型模块

  • SETTING 用于表示 设定类型模块

  • EBI 用于表示 EBI模块

  • OTHER/留空 用于表示 其他类型模块

    mssst

示例:

<menuType name="FM" desc="fm/master">
  <icon name="cicon-fm-master-data" library="font/cawIcon"></icon>
  <image name="fmIcon.png" library="img/icon"></image>
</menuType>

function的属性描述, function在系统在表示一个菜单对应有的功能。 是授权的最小单元

名字 必填 说明
name function的名字,推荐有字母以及下划线组成。
group 分组用字段
messCode function的MessCode
tooltip function在授权模块显示的tooltip 用于用户快速了解对应的功能含义

现有功能的列表请在系统的授权模块进行查看。

menuHelper的属性描述,主要用于生成menuParam

名字 说明
code 表示当前helper对于指定的菜单会生效。code需要添加menu的code
mType 表示当前helper对于特定类型的菜单都会生效
class 一个类的全名,需要实现MenuHelper。可以自定义isFor方法用于影响当前class影响的范围。

folder的属性描述

名字 说明
code folder的名字
messCode folder的MessCode
apDebug 布尔值,如果是true的时候,仅仅在debug模式才会显示出来
flag 用于控制特定模块在功能不被完全购买的时候 不显示
order 用于定义folder在树中的顺序

menu的属性描述

名字 子元素名字 类型 说明
code 字符 menu的名字
messCode 字符 menu的MessCode
src 字符 menu对应的页面位置
module 字符 当前模块默认使用的module
mType 字符 菜单对应的menuType
templet 布尔 表当前模块为模块,其他模块运行继承(主要继承function)
apDebug 布尔 布尔值,如果是true的时候,仅仅在debug模式才会显示出来
flag 字符 用于控制特定模块在功能不被完全购买的时候 不显示
inherit name 字符 填写templet为true的模块
inherit include 字符 function的名字 并使用分号连接起来,用于继承指定功能
inherit except 字符 function的名字 并使用分号连接起来,用于排除继承功能
function name 字符 function的名字 并使用分号连接起来(用于表示当前模块有的功能,最高优先级)
controller NA 字符 class名字 用于指定当前菜单需要使用的controller
listener NA 字符 class名字 用于指定当前菜单需要使用的listener
param key 字符 param的key param用于程序对于菜单进行添加额外参数使用
param value 字符 param对应的value
menuParam name 字符 参数名字,menuParam用于菜单连接使用,用于自定义连接的参数
menuParam description 字符 参数在界面显示的MessCode
menuParam pattern 字符 对应的参数类型
menuParam folder 字符 参数分组
order 整数 用于定义menu在folder中的顺序

示例:

<?xml version="1.0"?>
<nm xmlns="http://www.multiable.com/navmenu" app="caw">

	<function name="New" group="Editor" messCode="core.saveNew" tooltip="right.tt.new" />
	<function name="Save" group="Editor" messCode="core.save" tooltip="right.tt.save" />
	
	<folder code="sys" messCode="core.sys">
		<menu code="test" messCode="test" src="view/module/test" module="test" mType="">
			<function name="New;Save" />
		</menu>
	</folder>
</nm>

在代码中如何判定user是否有某个权限?

UserRightInfo info = UserLib.getRight(uid);
FrameRight right = info.getFrameRight(menu,beId);

: 3PD自己添加的功能需要通过right里面的otherRight来判断。

# module.xml

Module表示一个模块(or功能)相关的表的集合。在M18的设计中Module必须有一个主表,其他表必须和主表的PK发生一定意义上的关联关系。

module的属性描述

名字 类型 说明
name 字符 模块的名字
mess 字符 模块的MessCode
extend 布尔 用于表示当前描述用于扩展原有的module的属性
mainTable 字符 用于指定当面模块的主表 对应的table需要在子元素的table中出现
useBe 布尔 表示当前模块需要使用Be,一般来说为交易型模块
fmshare 字符 当这个栏位不为空的时候,表示为基础资料模块。推荐填写为N,表示默认将基础资料模块设置为BE数据共享
useAccess 布尔 表示当前模块是否支持Data Guard权限控制。
useAttach 布尔 用于控制模块是否启用附件功能。
useApv 布尔 是否允许启用批核功能。
useChangenote 布尔 是否启用变更单功能。
useCache 布尔 是否使用cache。默认为true,:如果你的模块已经自行cache 或者使用频率很低 或者无法cache等的时候,请记得将cache设置为false。 为了确保cache正确被更新,请更新数据的时候 必须将iRev + 1
useAutoGenCode 布尔 是否允许使用编码生成规则。需要对应的模块有New功能。
genCode_Field 字符 编码生成规则的目标字段。默认为code栏位
genCode_Date 字符 编码生成规则的默认日期栏位。默认为createDate栏位
fieldDataGuard 整数 标记对应的模块是否启用字段级权限。-1表示不启用 1表示启用 0 表示对应的模块有新增数据功能的时候启用
udfLogicSkip 布尔 禁用用户自定义赋值更新功能
supportUdf 布尔 是否允许使用自定义栏位功能
checkCodeExist 布尔 在界面输入编号的时候,是否主动进行编号存在的检查 并提示用户。
dataSynch 字符 用于标记当前模块是否允许在Data Easy中使用。默认需要模块有New的功能。如果为enable会默认允许。:原本计划修改为boolean,后来因为兼容性问题没有修改。
dataImportConveter 字符 Class类名,用于导入功能的处理类 见导入与导出
excelGener 字符 Class类名,用于导出功能的处理类 见导入与导出
template 布尔 用于标记当前配置描述是否模板,模板用于自定义模块功能
tableOrders 字符 用于说明下面table的解析次序,使用分号进行分割。:主表一定第一次序
codeDupLevel 字符 用于标记code重复的基本。默认为全系统唯一。值为BE的时候 BE+Code在数据库中唯一。:系统系统的时候会自动创建唯一键约束, 请确保修改前数值的唯一性,避免无法正确启动系统。
skipExpired 布尔 用于标记当前模块不启用失效功能
skipSysBC 布尔 用于定义当前模块不创建code的唯一性约束;
importAllowUpdate 布尔 用于定义当前模块在数据导入是否支持更新,默认为true;
importThreadMode 布尔 用于定义当前模块在数据导入是否支持多线程,默认为true;

table的属性描述

名字 类型 说明
name 字符 table的名字,对应datadict的信息
tpl 字符 用于自定义模块。用于标记对应table使用那个真实的datadict(可以指向真实的表)
c 布尔 用于标记是否支持create. 如果不支持的时候,将在操作的时候忽略这个table
r 布尔 用于标记是否支持read. 如果不支持的时候,将在操作的时候忽略这个table
u 布尔 用于标记是否支持save. 如果不支持的时候,将在操作的时候忽略这个table
d 布尔 用于标记是否支持delete. 如果不支持的时候,将在操作的时候忽略这个table
initRow 整数 用于标记在新增的时候,模块对应table需要初始化的行数。一般需要的情况下 应该仅仅设置为1
forceInit 布尔 用于标记模块在被read出来的时候,如果对应的table的行数不足initRow中填写的数量的时候 是否强制补齐对应的记录数。:通常仅仅用于后续补充表的时候 可能需要使用
hpk 字符 用于标记当前table与主表关联的栏位。:主表不需要填写 。 同一个模块的关联关系不要创建外键
fkey 字符 用于标记footer表中唯一性栏位
hfname 字符 用于标记第二层footer的信息。用于定位其上一层footer
hfkey 字符 上一层footer的唯一性栏位
order 字符 指定一个栏位用于控制table的读取排序次序。仅仅影响read的时候处理,不影响其他任何操作
cpnType 字符 用于标记对应的table和主表的关系是1:N,请慎重标记。:标记为table的时候 支持自定义栏位功能。
discriminatorColumn 字符 标记一个分区栏位,用于处理table同时给多个模块使用的情况,在save的时候会自动设置为当前的module的名字
resetDiscriminatorColumn 布尔 标记是否需要强制设置分区栏位。
dataImport 布尔 标记是否支持导入功能 :如果主表不支持的话,当前模块也不支持导入功能
dataExport 布尔 标记是否支持导出功能 :如果主表不支持的话,当前模块也不支持导出功能
columnOrders 字符 栏位序列,使用分号进行连接,用于标记栏位处理次序
fieldRightSetting 布尔 用于控制对应的table是否允许设置字段级权限
compareKey 字符 栏位序列,使用分号进行连接,数据差异比较的时候 使用这个key进行两行数据的判定。默认使用id栏位
sfKey 字符 当前footer表中与上一层footer关联的栏位

checker的属性描述

名字 类型 说明
class 字符 class类名,用于标记对应的checker的名字
exclude 字符 使用分号连接的方法名字。对应的名字将不会被当前checker运行
include 字符 都是分号连接的方法名字。仅仅已经书写的名字才会被checker运行
skipSuper 布尔 标记checker忽略class的上层class中的方法
apployTo 字符 表示当前checker应用的范围。默认当前模块,你可以apply到指定模块,或者使用*表示所有的模块

param的属性描述

名字 说明
key param的key param用于程序对于模块进行添加额外参数使用
value param对应的value

dataImportExtend的属性描述

名字 说明
extendSrc 用于标记在数据导入模块使用的拓展页面
dtoClass 用于拓展页面的数据类

dataExportExtend的属性描述

名字 说明
extendSrc 用于标记在数据导出模块使用的拓展页面
dtoClass 用于拓展页面的数据类

示例:

<?xml version="1.0"?>
<md xmlns="http://www.multiable.com/module" app="caw">

	<module name="user" mess="user" mainTable="user" useAccess="true" useAttach="true">
		<table name="user" key="code" initRow="1" />
		<table name="useroption" key="id" initRow="1" forceInit="true"/>
		<table name="userrole" key="beId" hpk="hId" cpnType="table"/>
        <table name="usercontrol" key="id" initRow="1" hpk="hId" forceInit="true" />
        <table name="userdefbe" key="id" initRow="0" hpk="hId" fKey="beId" order="priority" cpnType="table"/>
        
        <!-- this checker is for all module!  -->
		<checker class="com.multiable.core.ejb.checker.ModuleChecker" applyTo="*"/>
		<checker class="com.multiable.core.ejb.checker.ExtDataChecker" applyTo="*"/>
		
		<checker class="com.multiable.core.ejb.checker.UserChecker"  />
  	</module>
</md>

# i18n的实现

M18系统支持在多种语言显示,系统要求至少实现英语, 简体中文, 繁体中文等。用户可以在系统内添加自定义语言。

对应的配置文件放在/main/resources/META-INF/lang/Message_*.properties

开发者使用Eclipse可以直接编辑对应的语言文件。

mssst

:在每添加一个新的MessCode的情况下, 英文是一定需要添加的。

为了确保你添加的MessCode不和其他开发者冲突,M18推荐MessCode使用app名字作为前缀。

在代码中你可以通过MessCode获取到你需要的文本信息。例如

String text = CawGlobal.getMess("EBI");

特别的,如果你的MessCode需要在JavaScript中使用,请在webKey.properties中间中添加记录。然后在JS中就可以进行调用了。

var mText = myView.getMess('EBI');

:在JavaScript使用的Mess应该避免web的一些关键字。

webKey.properties 示例

# web mess for core
CloseAllTabs
EBI
all

用户添加多语言以及MessCode,请查看系统中的模块。 新增语言 描述编号

M18系统还支持数据层面的多语言。在datadict中将栏位设置i18nField为true。又因为系统运行用户在新增语言 进行语言添加,所以系统这部分信息使用一个Json栏位来存储对应的数据。

:该字段系统根据当前table是否存在i18nField自动生成.

mssstmssst

特别的,在SqlEntity中,对应的栏位会被还原为SqlTable的简单栏位。

你可以如下操作对应的栏位

String desc= employee.getString(1,"desc_en");
employee.setString(1,"desc_zh_CN",desc);
employee.setString(1,"desc_en","");

:进行使用模块读写可以这样操作,如果你直接更新数据库,对应的栏位并不存在,需更新Json栏位i18nField

# Search的实现

search是M18系统常见的数据查询,通过配置stInfo.xml和stSearch.xml,可以查询出对应table/view等的数据;

页面Lookup组件的数据查询也是使用了search去实现的。

: 系统一些通用功能判定通用也是用过Search功能实现。

# stInfo.xml

stInfo是在定义search的table数据,为了避免table直接暴露给用户的一个折中方法。如果在stsearch中使用了一个stInfo,而对应的信息在stInfo.xml没有定义,系统会默认根据datadict生成对应的stInfo信息。

stInfo标签的属性描述

名字 必填 类型 说明
name true string stInfo 的名字,一般与所对应的table同名
mess true string stInfo的mess
table true string 对应datadict table的名字;
inCols false string 这个参数的作用是:定义table中哪些栏位在这个stInfo是可用的。如果这个参数为空,代表对应table的所有栏位在这个stInfo都时可用的。不为空,则两个栏位之间需要用使用分号 “;” 隔开
exCols false string 这个参数的作用是:定义table中哪些栏位在当前stInfo是不可用的。如果这个参数为空,代表对应table的所有栏位在这个stInfo都时可用的。不为空,则两个栏位之间需要用使用分号 “;” 隔开
autoRelation false boolean 用于标记lookup栏位自动创建relation
skipBuildin false boolean 用于标记build-in栏位可以在lookup条件中使用

relation标签的属性描述:relation标签是定义当前stInfo的table如何与另一个stInfo的table进行关联;关联后用于search的查询;

名字 必填 类型 说明
col false string 必须是stInfo对应table里面的栏位,本stInfo的table将会和tarSt对应的table通过col和tarCol关联
tarSt true string 需要关联到的stInfo名称
tarCol false string 必须需要关联到的stInfo对应table里面的栏位
tarNullAble false boolean 默认为false;如果为true;则关联时使用left outer join;否则使用inner join
joinCond false string 如果值不为空,则使用这个设置的条件进行关联;否则使用col 和tarCol进行关联;

col标签的属性描述:col标签用于定义自定义栏位用在search的结果返回;这个标签不是必须的

名字 必填 类型 默认 说明
name true string 栏位的名称
mess true string 栏位的messCode
pattern false string 用于标记栏位对应的Pattern
sql false string 如果这个栏位的值是要从数据库查询出来的,就需要设置一段sql给到这个
cond false boolean true 如果值为true,这个栏位可以作为查询条件使用
sort false boolean true 如果值为true,这个栏位可以用于排序
show false boolean true 如果值为true,这个栏位可以显示到页面上

handler 标签的属性秒: handler标签用于配置stInfo的handler类用于初始化stInfo;这个标签不是必须的

名字 必填 类型 说明
stInfoName true string stInfo的名称
className true string handler的class路径

# stSearch.xml

stSearch标签的参数属性

名字 必填 类型 默认 说明
name true string stSearch 的名字
mess true string stSearch的messCode
srcSt false string 使用stInfo.xml里面配置的stInfo
maxLevel false int 3 用于表示stInfo向下最多可以relation多少层
defCond false string 默认查询条件
sort false string 默认排序方式
supportMultiSort false boolean false 如果设置为false,只能使用一个栏位排序
asyncGetCount false boolean false(如果没有设置ejb或者没有设置slave,默认值为true) 如果设置为true,lookup查询总数的将会和查询数据分开查询
mustJoinSt false string 用于定义这个查询中必须要join的stInfo,stInfo必须在srcSt的relation层级关系里;两个或多个stInfo需要用分号“‘;”隔开;
ejb false string 拼Search sql 的ejb,必须要继承GenSearchSqlAdapter;如果栏位没有设置,使用系统默认的ejb;
udfUse false boolean true 如果设置为false,不会出现在自定义查询模块
accessModule false string 用于指定当前stSearch查询数据对应的模块
accessField false string “id” 用于拼access Sql 时,指定srcSt通过哪个栏位和footer table 的id栏位关联
nameCardSupport false boolean true 用于设置是否支持nameCard
hideFormat false boolean false 如果设置为true;lookup将会隐藏format,用户不能设置
hideFilter false boolean false 如果设置为false,用户无法在Lookup页面添加filter条件
supportSearch false boolean true 如果设置为false,用户无法在lookup页面使用模糊查询功能
supportDeletedData false boolean false 如果为true,search可以查询已被删除的记录
slaveEjb false string slave的class路径
slaveApplyTo false string 当前的slave可以使用在其他的stSearch上;这里填写stSearch name;如果需要填写两个以上的stSearch,请使用分号“;”隔开;
skipAccess false boolean false 如果设置为true,lookup将跳过Access检查

slaveCol标签的属性描述:slaveCol标签用于定义自定义栏位用在search的结果返回;这个标签不是必须的

名字 必填 类型 默认 说明
col true string 栏位的名称
mess true string 栏位的messCode
pattern false string 用于标记栏位对应的Pattern
sql false string 如果这个栏位的值是要从数据库查询出来的,就需要设置一段sql给到这个
cond false boolean true 如果值为true,这个栏位可以作为查询条件使用
sort false boolean true 如果值为true,这个栏位可以用于排序
show false boolean true 如果值为true,这个栏位可以显示到页面上
followField false string 填一个当前stInfo下或者关联table下的field;用于将当前加的slaveField跟在这个field后面在format设置界面的左边栏显示;

bindCond 标签的参数介绍;bingCond是stSearch默认加的一个条件;

名字 必填 类型 默认 说明
andOr false string 如果没有设置,会使用“and”去拼sql
leftBrackets false string 如果需要填值,一般会填"("
leftFieldMode false string "column" 表示leftField的类型
leftField false string 如果leftFieldMode 是“column”;那么这里就要填栏位名称;如果leftFieldMode是“value”,那么这里就填数据
operator fasle string "=" 这里填操作符号;默认为等于号
rightFieldMode false string "value" 表示rightField的类型
rightField fasle string 如果leftFieldMode 是“column”;那么这里就要填栏位名称;如果leftFieldMode是“value”,那么这里就填数据
rightBrackets false string 如果需要填值,一般会填")"
condString false string 这里填一段sql的查询条件;如果这里设置了sql条件,上面的那些参数配置将会失效;

format标签的参数介绍; format用于配置查询的系统format

名字 必填 类型 默认 说明
id true int -1 不能大于0;如果大于0将改为-
mess true string format的messCode

formt里面的cond 标签的参数介绍;用于设置format的默认条件;

名字 必填 类型 默认 说明
andOr false string 如果没有设置,会使用“and”去拼sql
leftBrackets false string 如果需要填值,一般会填"("
leftFieldMode false string "column" 表示leftField的类型
leftField false string 如果leftFieldMode 是“column”;那么这里就要填栏位名称;如果leftFieldMode是“value”,那么这里就填数据
operator fasle string "=" 这里填操作符号;默认为等于号
rightFieldMode false string "value" 表示rightField的类型
rightField fasle string 如果leftFieldMode 是“column”;那么这里就要填栏位名称;如果leftFieldMode是“value”,那么这里就填数据
rightBrackets false string 如果需要填值,一般会填")"
condString false string 这里填一段sql的查询条件;如果这里设置了sql条件,上面的那些参数配置将会失效;

format里面的col标签的参数介绍; 用于配置默认显示栏位;

名字 必填 类型 默认 说明
col true string 填栏位的名称
mess false string 如果想改变栏位显示mess;可以这里填一个messCode覆盖
width false string 栏位在lookup Table 默认显示多宽
sortable false boolean true 用于标记对应的栏位可以使用排序

format中sort标签的介绍;sort标签用于配置默认的排序;

名字 必填 类型 默认 说明
col true string 填栏位的名称
ascending false boolean false 如果设置为true;将会使用由大到小排序

namecard标签介绍:请查看资料名片(namecard)开发

handler标签介绍;handler标签用于配置lookup参数StParameter的init方法;init方法在导入数据对lookup栏位进行search检查前运行,和页面lookup栏位在查询前运行

名字 必填 类型 默认 说明
name true string 填一个class的路径;class里面的方法必须要有@InitStSearchParam注释的

stSearchInitHandler标签介绍;stSearchInitHandler标签用于配置初始化stSearch的class;

名字 必填 类型 默认 说明
stSearchName true string 填一个stSearch的名称
className true string 填一个继承了StSearchInitHandlerAdapter的类的路径

**注:**通过配置stInfo和stSearch,就可以使用search查询;

配置xml :stInfo

<stInfo name="simpleUser" mess="share.user" table="user">
	</stInfo>
<stInfo name="employee" mess="share.employee" table="employee">
	<relation col="createUid" tarSt="simpleUser" tarCol="id" />
	<relation col="lastModifyUid" tarSt="simpleUser" tarCol="id" />
</stInfo>

stSearch


<stSearch name="employee" mess="employee" srcSt="employee" maxLevel="3" sort="#employee#.code" accessModule="employee" asyncGetCount="true">
		<bindCond leftField="id" operator="!=" rightField="0" />

		<format mess="core.defFormat">
			<col col="code" />
			<col col="desc" />
		</format>

		<namecard imgCode="photoCode" width="290"></namecard>

</stSearch>

# 影响Search过程

**注:**在search过程中,开发者可以通过配置slave,handler去干预search;

1 配置slave类;需要继承SearchSlaveAdapter;然后通过beforeDatalookup和afterDatalookup两个方法干预search过程;beforeDatalookup通过添加条件,可以影响查询结果;afterDatalookup可以直接对查询结果进行修改,并返回希望得到的结果;

2 handler类:在stSearch.xml配置的handler标签;处理方法在导入数据对lookup栏位进行search检查前运行,和在页面lookup栏位查询前运行;

通过例子代码可以看到;handler里面的处理方法需要添加注释@InitStSearchParam;然后方法必须是static的,并且参数也是固定的(StParameter param, SqlEntity entity, String tableName, String colName, int row) ;

	<handler name="com.multiable.core.share.modulestsearch.InitStParam"></handler>
public class InitStParam {

	@InitStSearchParam(moduleName = "dept", stSearch = "dept")
	public static void initDeptSupDeptParam(StParameter param, SqlEntity entity, String tableName, String colName, int row) {

		if (!("dept".equals(tableName) && "supDept".equals(colName))) {
			return;
		}
	}
}

3 initHandler类:

在stSearch.xml添加标签stSearchInitHandler;可以在让3PD对系统已有的stSearch进行额外的初始化处理;

handler类需要继承StSearchInitHandlerAdapter;

<stSearchInitHandler stSearchName ="employee" className ="com.multiable.core.share.modulestsearch.EmpStSearchHandler"/>
public class EmpStSearchHandler extends StSearchInitHandlerAdapter {

	@Override
	public void initStSearch(StSearchInfo stSearchInfo) {

		CawLog.info(stSearchInfo.getName());
	}
}

在stInfo.xml添加标签handler;可以在让3PD对系统已有的stInfo进行额外的初始化处理;

handler类需要继承StInfoHandlerAdapter;

	<handler stInfoName ="employee" className ="com.multiable.core.share.modulestsearch.EmpStInfoHandler"/>
public class EmpStInfoHandler extends StInfoHandlerAdapter {
	@Override
	public void initStInfo(StInfo stInfo) {
		CawLog.info(stInfo.getName());
	}

}

# 自定义的Search

方案1:重定义searchEJB

**注:**如果系统默认拼接sql功能不能满足查询的需要,可以重定义SearchEJB;

限制:ejb需要继承GenSearchSqlAdapter

示例:在stSearch标签的ejb写上对应EJB的名字;ejb ="GenEmptySqlEJB";然后继承GenSearchSqlAdapter类即可;返回的SearchSqlInfo填好查询数据的sql:searchSql和查询总数的sql:countSql;

示例:

@Stateless
@Local(GenSearchSql.class)
public class GenEmptySqlEJB extends GenSearchSqlAdapter {

	@Override
	public SearchSqlInfo getLookupSql(StSearchInfo myInfo, StParameter param) {
		SearchSqlInfo emptyData = new SearchSqlInfo();
		emptyData.setSearchSql("");
		emptyData.setCountSql("");
		return emptyData;
	}
}

方案2:重定义searchEJB + slave

**注:**如果一个stSearch的数据不需要从数据库查询出来,可以使用重定义SearchEJB + 使用slave生成数据;

限制:ejb需要继承GenSearchSqlAdapter,slave需要继承SearchSlaveAdapter

示例:在stSearch标签的ejb写上对应EJB的名字;ejb ="GenEmptySqlEJB" slaveEjb="PatternInfoSlave";

java代码

GenEmptySqlEJB 请参考上面的GenEmptySqlEJB例子

PatternInfoSlave:在slave生成数据,只需要override afterDatalookup 方法即可;

示例:

@Stateless
@Local(SearchSlave.class)
public class PatternInfoSlave extends SearchSlaveAdapter {

	@SuppressWarnings("unchecked")
	@Override
	public void afterDatalookup(SearchResult result, StParameter param) {
		SqlTable sqlTable = StLookupLib.createEmptyLookupTable();
		sqlTable.addField(new SqlTableField("type", String.class));

		List<String> keys = new ArrayList<>();
		*** //do some search coding

		int id = 1;
		int startRow = param.getStartRow() < 1 ? 1 : param.getStartRow();
		int endRow = param.getEndRow() > keys.size() ? keys.size() : param.getEndRow();
		for (int i = startRow; i <= endRow; i++) {
			CawPattern pattern = CawGlobal.getPattern(keys.get(i - 1));
			int rec = sqlTable.addRow();
			sqlTable.setLong(rec, "st_id", id);
			sqlTable.setString(rec, "st_code", pattern.getCode());
			sqlTable.setString(rec, "st_desc", pattern.getType());
			sqlTable.setLong(rec, "id", id);
			sqlTable.setString(rec, "code", pattern.getCode());
			sqlTable.setString(rec, "type", pattern.getType());
			id++;
		}

		result.setResultTable(sqlTable);
		result.setTotalRows(keys.size());
	}
}

方案3:自己编写组件

**注:**系统现有的lookup组件无法满足用户的需求,那么可以自行编写组件,不再使用stSearch/stInfo的配置了;

限制:页面和查询的所有东西都需要自行处理;

# 权限控制

# 影响登录

  • loginHandler

用户登录的过程,除了密码可以做限制外;开发者可以通过app.xml配置loginHandler标签进行登录干预;使用loginHandler的checkLoginInfoBeforeLogin方法可以在用户登录前干预用户登录;使用afterLogin方法可以在用户登录后做一些特殊处理;

loginHandler标签参数介绍

名称 说明
className class的完成路径,class需要继承LoginHandlerAdapter
  • wsSkipAccess

M18系统对于一些web service的请求,一般都会检查web service是否有权限请求数据的;会判断请求里面的参数userKey或者Authorization是否有权限;使用app.xml配置wsSkipAccess标签使一些web service的请求跳过这些权限的检查

wsSkipAccess标签参数介绍

名称 说明
pathNames 如果请求的web service包含这里定义的路径,可以跳过检查;如果是多个路径,可以使用分号“;”隔开;例子:"/cawLogin/checkLoginUser;/image/getImage“
  • webSkipAccess

M18系统对于一些不是web service的请求也一样会检查session的登录状态;使用app.xml配置webSkipAccess标签会使一些请求跳过检查

webSkipAccess标签参数介绍

名称 说明
pathNames 如果请求路径包含这里定义的路径,可以跳过检查;如果是多个路径,可以使用分号“;”隔开;例子:"/CaptchaServlet;/wfAction”

# 模块权限

M18系统通过模块和菜单配置当前模块有什么权限,如何配置请查看navmenu.xml;

# 单据权限

模块如果启用了权限控制,在参看单据的时候会进行单据的权限查询;如何启用请看module.xml;

单据的权限查询sql放在模块对应的StSearchInfo.java 里面;

权限查询sql介绍

名称 说明
accessStr_noBe 如果模块没有使用Be,将会使用这个sql判断单据的权限
accessStr_oneBe 如果模块使用了Be;查单个Be里面的数据,使用这个sql查询
accessStr_manyBe 如果模块使用了Be;查多个Be里面的数据,使用这个sql查询
accessStr_mir 判断已删除单据的权限,使用这个sql查询

权限sql生成的过程

每一次stSearch初始化的时候,都会运行StSearchInfo的initAccess方法初始化生成这些查询sql;这里会根据访问控制模块的设置,先拼当前模块的access表(白名单和黑名单表)的sql;然后模块里面所有表(系统表除外)的lookup field(如果要忽略,请在datadict标志为skipAccess=“true”)会判断是否要检查access权限,如果要就将sql拼起来;

# 如何影响权限数据

访问控制模块有自动计算访问控制的功能;在模块单据保存后,会根据自动计算访问控制设置的条件计算出单据的权限数据,然后保存起来;

**注:**然而,开发者可以通过entityHandler的autoCalcAccess方法干预这些自动计算访问控制的结果;

entityHandler的参数介绍

名字 类型 说明
name string 填模块的名称,如果为空,则这个entityHandler作用于所有模块
className string class的完整路径,class需要继承EntityHandlerAdapter

# 如何替换Search的权限

开发者可以通过影响Search过程 的第3点initHandler类实现;

在stSearch的InitHandler中修改权限查询sql;就可以替换Search的权限了;

# 字段权限

系统表不能使用字段权限,还有所有表的“code”,“desc”,“beId”;buildin栏位也不能设置字段权限;

开发者可以通过module.xml的table标签配置参数fieldRightSetting=‘false’,使模块的这个table下的所有字段不能使用字段权限控制;

**注:**可以通过RightLib的以下方法获取字段权限;

public static List<FieldRightDto> getFieldRight(long beId, Set<String> tables)//获取当前用户的字段权限,Set<String> tables传入需要获取字段权限的table name 
public static List<FieldRightDto> getFieldRight(long beId, Set<String> tables, long uid)//传入user id 获取对应用户的字段权限,Set<String> tables传入需要获取字段权限的table name 

# 常用功能

# 关于配置文件

  • cawserver.properties

cawserver.properties放在jboss的config文件夹里面;用于配置当前服务器的初始化参数;M18系统启动后会读取这些配置,使用配置的参数工作;

cawserver.properties 参数介绍

#jboss server的识别码 在集群环境下,必须要设置为不一样
caw.jboss.id=cl

#jboss server属于哪一个群,为空时不会执行一些集群的事件
caw.jboss.cluster=abc

#有部分工作在服务器启动的时候需要运行,仅仅main.server为true的会执行。
#在集群环境 请确保只有一部server为true
caw.main.server=true


#注意:所有的路径不要使用\\结尾,由AP在代码自己处理
#两个path必须要指向用一个文件夹。dbDoc表示db服务器可以访问的路径。appDoc表示Jboss可以访问的路径。(对应路径用于文件传输,请确保访问速度OK)
#当两个服务器在一起的时候 请不要使用网络路径。注意 请一定要使用\\来作为路径分隔符
#share path in window(192.168.10.118)
caw.fileData.dbDoc.path=D:\\CAWShareAP
caw.fileData.appDoc.path=\\\\192.168.10.118\\CAWShareAP

#这个指向一个jboss的临时路径,仅仅需要jboss端可以访问的本地路径即可(如果本地址为空 将使用 caw.fileData.dbDoc.path 如果不是一部机器的情况下,这个的操作将影响速度)
caw.file.app.path=D:\\temp\\caw4

#请调整为对外的发布路径;一般是发邮件到客户,在邮件上有链接可以操作到M18系统,所以这里应该填一个外部访问的路径;
caw.web.url=http://www.abc.com/jsf/

#服务器内部资源访问路径
#本配置需要修改
caw.internal.url=@web_url

#系统将通过这个路径访问ws,推荐使用本机,如果在集群分工的情况下,可以合理分配服务器的使用,不过仍旧推荐使用本机。
#本配置不应该修改
caw.ws.url=@wsdl_url/jsf/rfws/

#共享文件夹(caw.file.app.path,caw.fileData.appDoc.path)文件的有效时间,根据最后修改时间算起,有效期单位是天;当这个数据没有填,或者填的不是数字,默认为30天,
caw.file.life.effect = 30

#M18系统使用的数据库ip地址
caw.database.ip=192.168.10.117

#M18系统使用的数据库端口
caw.database.port=3306

#M18系统使用的数据库名字
caw.database.dbname=nh7

#M18系统使用的数据库用户名
caw.database.user=nh7

#M18系统使用的数据库密码
caw.database.password=nh7@7jiaj


#AccessToken为空的时候,是不允许使用为登入模式访问系统。使用AccessToken访问,默认为admin用户。在开发时,推荐设置为简单的 例如ap,在正式使用的情况下,推荐设置为复杂 or 空
caw.ws.glAccessToken=iz

#本机可以跑定时任务,如果不为1 则不跑;
caw.sch.quartz = 1

#当caw.sch.quartz 不为1时,在本机发布任务,将会通过这个路径访问ws ,在目标机发布任务;目标机必须可以跑定时任务;
caw.sch.targetUrl = http://127.0.0.1:8080/jsf/rfws/
#target 机的caw.ws.glAccessToken
caw.sch.targetAccessToken=iz
  • reset.caw

reset.caw放在jboss的config文件夹里面;用于开发环境,开发者可以在这里配置一些差数用于更方便开发;这里可以配制cawserver.properties 里面的参数,这里的配置会覆盖cawserver.properties 的配置;

#AP debug 模式,M18系统有一些功能是在apDebug模式下才能用的;这里设置为1启用apDebug模式
caw.ap.debug = 1
#在启动的时候 强制skip同步数据库结构(注意:可能会因为缺少表or字段导致部分功能不能运行)
caw.ap.skip.sycnDB=1

#下面几个参数,是用于将模块Check运行的信息打印在log中;module是注册哪些模块需要打印check信息,模块名称用“;”隔开,create,save,read,delete设置为1时,表示运行相应操作的checker时打印check信息;
caw.ap.debug.checker.create=0
caw.ap.debug.checker.save=0
caw.ap.debug.checker.read=0
caw.ap.debug.checker.delete=0
caw.ap.debug.checker.module=


#用于锁定job,可以锁定多个job,使用;隔开,被锁定的job只能被锁定job的服务器运行
caw.ap.sch.debugJobName=
#用于锁定jobGroup,可以锁定多个jobGroup,使用;隔开,被锁定的job只能被锁定job的服务器运行
caw.ap.sch.debugJobGroup=
#锁定job多久,单位是小时
caw.ap.sch.debugTime=

#如果设置为1跳过启动上传report format
caw.report.skipUploadFormat = 0

# 表结构信息

对于M18的开发者而言,了解原本系统模块/功能的后台结构对于功能的开发是必须的资料。我们这里介绍如何在系统中找到表结构信息 以及如何使用api查找对应的表结构信息。

在系统模块数据字典中可以找到对应菜单模块对应的表结构信息。

mssst

在代码里面,已知table名字,获取信息

DdTable table = CawGlobal.getDdTable("employee");

遍历datadict信息

Ehcache cache = CawCache.getCache(CawCache.ddCache);
		
@SuppressWarnings("rawtypes")
List keys = cache.getKeys();

for (Object key : keys) {
  DdTable table = (DdTable) cache.get(key).getObjectValue();
  // add you code here
}

# 导入与导出

M18系统用户批量数据导入和导出的功能,用于数据初始化 临时备份 以及迁移准备等使用。

  • 导出 M18系统支持将模块数据导出到excel文件,请查看数据导出模块; 如果开发者需要控制导出到excel文件的数据,可以在module.xml配置对应模块的IExcelGener, excelGener=“class的完整路径” , class需要实现IExcelGener接口;使用exportData方法用于导出数据包括excel文件的格式;appendMoreData方法用于拼接导出数据;

**注:**M18系统默认是用ExcelGener实现IExcelGener接口;

public interface IExcelGener {
	\\生成不含数据用于导入数据准备的模板Excel
	public Workbook createTemplate(String moduleName, long beId, String dataExportConfigJson) throws Exception;
	\\将SqlEntity的数据生成Excel
	public Workbook exportData(DataExportConfig config, SqlEntity entity) throws Exception;

}
  • 导入 M18系统支持模块数据从excel文件中导入,请查看数据导入模块; 如果开发者希望自己控制excel导入的数据处理,可以在module.xml配置对应模块的IExcelGener和dataImportConveter;excelGener=“class的完整路径” ,class需要实现IExcelGener接口;dataImportConveter=“class的完整路径” ,class需要继承ImportConverterAdapter类;

使用IExcelGenercreateTemplate方法创建导入数据的excel模板;使用dataImportConveter的convert方法将excel的数据转换成M18系统的SqlEntity结构;使用getImportResultWriter方法得到ImportResultWriter,用于将错误信息写到excel文件中;

**注:**M18系统默认使用EntityImportConverter类转换导入的excel数据;使用ExcelImportResultWriter类将错误信息写到excel文件中;

**注:**如果用户不重新定义IExcelGenerdataImportConveter的情况下,想干预导入功能,还有一个办法;

在app.xml中定义标签entityHandler ,

entityHandler的参数介绍

名字 类型 说明
name string 填模块的名称,如果为空,则这个entityHandler作用于所有模块
className string class的完整路径,class需要继承EntityHandlerAdapter

下面entityHandler的这些方法都与导入有关

public Map<String, Set<String>> getImportSkipColumn(String moduleName, long beId);

public Map<String, List<DdColumn>> getImportExtraColumn(String moduleName, long beId);

public List<String> handleExtraBeforeAllColumn(String table);

public void updateExtraColumnValue(SqlEntity entity, int sqlTableRow, String moduleName, String tableName, DdColumn ddColumn, Object cellValue, long beId);

public Set<String> getImportSkipTable(String moduleName, long beId);

public default String modifyColumnsRemark(String remark, String tableName, DdColumn col, DataExportConfig config) {
		return remark;
	}

entityHandler关于导入的方法介绍

名称 说明
getImportSkipColumn 返回Map<String, Set<String>>;map的key是table name,set是field name的set;用于表示当前模块哪个table下的哪些field将不能使用导入功能
getImportExtraColumn 返回Map<String, Set<String>>;map的key是table name,list是DdColumn的List;用于表示当前模块哪个table需要加多一些field用与导入,这些field将会表现在导入模块界面栏位映射的组件上和显示在导入的模板excel上
handleExtraBeforeAllColumn 返回一个List,表示哪些额外加入的栏位,需要在所有栏位处理之前处理;
updateExtraColumnValue 用于处理额外加入的栏位值
getImportSkipTable 返回一个set表示当前模块哪些table将不能使用导入功能
modifyColumnsRemark 用于修改模板excel里面对模块栏位的说明

# 文档打印

M18系统支持使用ireport进行文件打印的处理。系统通过jsper将对应的数据和格式生成pdf,并通过浏览器的查看PDF进行文件的查看并提供打印功能。

在开始打印的任何处理之前,请确保你对于ireport有一定的认识。

请在电脑中安装ireport的最新版本,并将M18中的com.jaspersoft.studio.data_6.2.0.final.jar替换到安装目录中。:Ireport编辑软件是一个Eclipse.

请将caw share的jar加入到JaspersoftStudio的项目中,以便系统在处理M18函数的时候可以给出正确提示。

  • 编写 docReport.xml

report属性说明

名字 类型 说明
code 字符 用于唯一标示一个report。对应的code推荐以@开头
module 字符 module.xml中的code,标示当前打印属于那个模块。允许设置多个模块,使用分号进行分割
providerCode 字符 provider的class名字, 用户字段获取 数据获取 以及数据分析处理等。
reportDto 字符 dto的class名字,用于处理打印参数。
reportDtoSettingSrc 字符 用于指定设置页面的位置,一般对应的reoportdto的信息可以在这个页面进行设置。
providerMess 字符 provider对应的MessCode
providerOrder 整数 在一个模块有多个打印设置的时候有效,用于控制打印的次序
apDebug 布尔 当设置为true的时候,仅仅debug模式可见

format的属性描述

名字 说明
code format的编号,对应的编号将在系统启动的时候生成一天format的记录,默认推荐编号和report的编号一致
description format的说明字段
remark format的详细说明字段
packageName 用于告知系统Jrxml存在的路径(代码路径)

jrxml的属性描述

名字 说明
name jrxml的名字 推荐仅仅有字母组成 一般来说应该以Jrxml结尾
main 标记那个jrxml为主文件,在一个格式中有且仅有一个jrxml为主文件

resetData的属性描述

名字 说明
prCode provider的class名字, 作用于同一个provider的所有xml设定
resetClass class类名,实现CawJrPrintRestData。用于修改已经创建的打印格式的数据获取过程,也可以给原本的格式添加额外的数据

reportHandler的属性描述

名字 说明
reportCode report的code栏位
handlerClass class类名,实现ReportHandler。允许开发者将系统启动过程中初始化的jrxml格式进行进一步编辑。一般用于特定功能的隐藏。

reportShowHandler的属性描述

名字 说明
prCode provider的class名字, 作用于同一个provider的所有xml设定
handlerClass class类名,实现ReportShowHandler。使得特定的打印功能可以控制pdf生成后是否直接下载or进行其他操作。

示例:

<docReport>
	<report module="employee" code="@bpm_emp"  
			reportDto="com.multiable.core.share.data.ireport.emp.EmpJrDto" reportDtoSettingSrc="/view/dialog/empPrintSetting.xhtml"
	        providerCode="com.multiable.ireport.provider.emp.EmployeeProvider" providerOrder="1" providerMess="jr.emp_provider" apDebug ="true">
	        
		 <format code="@bpm_emp" description="Print" remark="Print Employee" packageName="com.multiable.core.share.data.ireport.resource.emp">
	        <jrxml name="emp.jrxml" main="true"></jrxml>
	        <jrxml name="empPic.jrxml"></jrxml>    
	        <jrxml name="empUser.jrxml"></jrxml>    
	     </format>
	</report>
</docReport>

  • 添加必要的Class以及Sql

添加Dto class

public class EmpJrDto extends ModuleReportDto {
	private boolean prtPic = true;

	@Override
	public String getDefaultTitle() {
		return "Testing Ireport!";
	}
	public boolean isPrtPic() {
		return prtPic;
	}
	public void setPrtPic(boolean prtPic) {
		this.prtPic = prtPic;
	}
}

我们可以看到这仅仅一个简单的pojo 类,我们要求模块的dto均需要使用ModuleReportDto。

添加Provider

public class EmployeeProvider extends ModuleProvider {

	@Override
	public void initReportStru(CawReportDataSet reData) {
		// set table alias name
		reData.setAlias(0, "EMPLOYEE", "employee");

		reData.setAlias(1, "DEPT", "dept");
		reData.setAlias(2, "EMPLOYEEPIC", "employeepic");
		reData.setAlias(3, "PICUSER", "user");

		reData.setQuery("EMPLOYEE");
	}

	@Override
	public void adjustData(CawReportDataSet reData) {

		reData.setRelationTo("EMPLOYEE", "dept", "id", "DEPT");
		reData.setRelationTo("EMPLOYEEPIC", "userId", "id", "PICUSER");
		reData.assignSubReport("EMPLOYEE", "EMPLOYEEPIC", null, "id", "hId");
	}

	@Override
	public CawReportDataSet genIdsData() {

		String sql = "{Call prtEmployee('" + getReportDto().getMainIdString() + "')}";

		MacQuery query = new MacQuery();
		query.setQuery(sql);

		CawReportDataSet ds = fillAndAdjustData(query);

		return ds;
	}

}

sql例子

drop procedure if exists prtEmployee;

DELIMITER $$

CREATE PROCEDURE prtEmployee( in idStr varchar(2000))
  
BEGIN
	
	create temporary table if not exists prtemptempTable 
    select a.*  
	from employee a
	where find_in_set(a.id, idStr);
  
   #create index
	create index I_Employee_dept  ON prtemptempTable(dept); 

    #main query 0
    SELECT * from prtemptempTable;
    
    #dept 1
    select * from dept a 
    where exists(select NULL from prtemptempTable b where b.dept=a.id);
    
    drop table prtemptempTable;
END$$

DELIMITER ;

:Sql部分推荐使用select *以区别读取出尽量多的栏位。

  • 启动Jboss(使用在JaspersoftStudio可以获取数据源),编写JrXml

Jrxml推荐复制一个已有的进行修改,不推荐从零开始。除非你熟练掌握Ireport的相关技巧。

创建一个数据源如下:

mssst

mssst

:上图的URL为:http://127.0.0.1:8080/jsf/rfws/cawProvider/fields/com.multiable.ireport.provider.emp.EmployeeProvider?accessToken=iz&lang=zh_CN

accessToken 使用cawserver.properties 中的caw.ws.glAccessToken

provider是上面xml中定义的名字

lang 返回的field 说明mess 的语言(不传输的时候, 取GlobalOption的lang)

skipLang 如果为true,返回的field 不会加上mess说明(可不传输,默认为false)

:不限制使用(cawProvider/fields)这个ws,如果有需要,可以仿照CawJrProviderEJB自行处理

:前面系统jboss的原因是为了可以使用这个URL

创建这个新增一个Jrxml,使用read Fields即可以获取到当前

  1. 将jrxml放入代码,进一步编写xml 参照1.0只不过的说明和示例

  2. 重启Jboss进行测试。

    mssst

:目前系统在启动的时候将jrxml编译放到数据库里面,由于这个会受到数据量大小的影响启动时间,之后这部分将进行调整。

# 自定义模块

自定义模块的模块模板,开发者可以自定义模块模板,通过app.xml的template标签中module标签配置;

module标签参数

名字 必填 类型 说明
code true string 模板的编号
description true string 模板的messCode
module true string 对应模板的模块名,对应的模块名在module.xml里面需要配置参数template="true"
helper true string 填helper类的完成路径,需要继承CawUeHelper
子标签<page> true string 使用当前模板生成的自定义模块,模块的初始页面
子标签<setting> true string 当自定义模块选择当前设置的模板,这个标签配置的页面将会在自定义模块里面的模板选项中显示

helper类需要继承CawUeHelper

public class FmTemplateHelper extends CawUeHelper {

	private static final long serialVersionUID = 5554948686647491057L;

	@Override
	public void initModuleInfo(SqlTable ue, int row, Module module) {

		String jsonStr = ue.getString(row, "backEndInfo");


		if (!StringLib.isEmpty(jsonStr)) {
			FmtOption option = JSON.parseObject(jsonStr, FmtOption.class);
			module.setUseAttach(option.isSupportAttachment());
		} else {
			module.setUseAttach(false);
		}

		module.setFmShare("A");

	}

}

# 初始化数据

M18系统的数据库信息是通过代码自动生成的。所以开发者在数据库无法留存任何的预设数据。

在app.xml定义一个entityDataInit。

名字 说明
name entityDataInit的名字
initDataCreator class类名,实现InitDataCreator 在对应的init方法中编写代码生成数据。

:这个方法每次Wildfly启动的时候都会运行,请自行绑定需要初始化的数据是否已经存在数据库中。

示例

public class TestDriverCreator extends BaseDataCreator {

	@Override
	public void init() {
		if (hasEntityData()) {
			return;
		}

		// Init test
		SqlEntity entity = create();
      
		JdbcDriverEntity accseeDriver = EntityLib.toObject(entity, JdbcDriverEntity.class);
		accseeDriver.setCode("test");
		accseeDriver.setDesc("test");
		accseeDriver.setJdbcType(JdbcType.Driver);
		accseeDriver.setClassName("net.test.jdbc.TestDriver");
		accseeDriver.setGrammarType(GrammarType.UDF);
		accseeDriver.setIndepPw(true);
		accseeDriver.setWithDbName(false);
		accseeDriver.setConnStrTplt("jdbc:test://${[@D_FilePath]};memory=false");
		EntityLib.mergeToEntity(accseeDriver, entity);
      
		update(entity);
	}
}

# 数据修复

在版本升级的过程中,系统有时候因为各种原因需要修正原本数据库中的数据。

在app.xml定义一个dataFix。

名字 说明
className class类名,需要实现DataFix. 在run方法中处理需要的数据修复。:这个同时也是唯一键,不要移动class位置
ap 注明对应的datafix负责的ap
desc 描述对应datafix修复的数据问题
order 用于定义datafix的运行顺序

示例:

<dataFix className="com.multiable.core.ejb.bean.cache.datafix.TestToFix" ap="Test" desc="fix test"></dataFix>
public class TestToFix extends DataFixAdapter {

	@Override
	public int run() {
		
		String fixSql = "update testinfo set myIdType = 'text'; ";

		try {
			CawDs.runUpdate(fixSql);
		} catch (SQLException e) {
			CawLog.logException(e);
		}
		return 0;
	}
}

: 请尽量考虑可能存在的错误情况,避免数据修复没有正确运行。

:请尽量编写数据修正的检查代码,而且数据修复的代码仅仅会运行一次。

系统判定表datafix中的记录来判定对应的fix是否已经运行。

# 如何新增jar

为了确保在Wildfly在运行的时候可以获取到对应的第三方jar包,需要确保jar已经添加到Wildfly的运行环境中。

M18推荐使用module.xml(wildfly)的方式来进行处理。 M18推荐同时修改MANIFEST.MF以及jboss-deployment-structure.xml

:在编译工具中(例:Eclipse)代码无错误,仅仅说明你的编译环境中包含需要的jar。并不是能够确保运行环境中 对应的代码可以执行。 :Wildfly有自己的classloader管理方式。

:由于在项目发布的时候,Wildfly会将ear下面的lib(或者war下面的lib)自动进行复制一份来进行运行。

如果多次发布(系统重启),会生成很多空间占用。

具体请参考Wildfly文档 (opens new window)

# 定位启动错误

由于配置/jar/代码/系统环境等等原因,M18启动有时候会发生失败的情况。而启动后往往会发现很多不同的错误。请记得只理会第一个回答导致启动出错的Exception,并且修复这一个后重新启动看看。

  • 千万不要从后往前找错误
  • 不要尝试一次过解决log里面提示的所有错误
  • 不要认定一定是代码上面的问题
  • 如果发现无足够的信息,推荐print log出来,就算是需要修改第三方的代码
  • 若是在Eclipse里面,启动出错的时候推荐删除所有断点

# 定时任务

# 创建定时任务

  • 需要创建一个job 类

    job类需要继承CawQuartzJob,只需实现doExecute 方法即可;

public class SyncJob extends CawQuartzJob {
  
	public static String JOBGROUP = "caw.sync";
  
	@Override
	protected CheckMsg doExecute(JobExecutionContext context, CawJobDto dto) throws Exception {
         //do something	

		return msg;
	}

}
  • 发布job

    先构建一个CawJobDto 的对象,使用方法CawJobLib.scheduleJob(dto)发布即可, CawJobDto 有一个三个参数的构造方法,第一个是jobGroup ,第二个是当前entity的id,第三个是自己创建的job的class;jobGroup+ id 必须构建出一个唯一的jobName, 还有一个必须要填的是TimeScheduleDto,这个是time组件的dto,使用页面去配置生成或者自己new一个出来都可以;jobMess可以的也可以填;CawJobDto 里面还有一个jsonData, 可以填一些job需要的参数,doExeccute 时在 dto可以获取到;

    		CawJobDto dto = new CawJobDto(SyncJob.JOBGROUP, id, SyncJob.class);
    
    		dto.getJsonData().put("id", id);
    		dto.setTimeSchedule(timeDto);
    		dto.setJobMess("syncJob");   
    
    		CawJobLib.scheduleJob(dto);
    

  • 删除job

    使用 CawJobLib 中的 deleteJob 删除即可

    CawJobDto dto = new CawJobDto(UnlockUserJob.JOBGROUP, param.getEntityId(), UnlockUserJob.class);
    
    CawJobLib.deleteJob(dto);
    

# 关于集群下面的说明

job被发布后,会把信息保存到cawjob表中;如果job过期了;会有定时任务将这些过期的job移除;

只有当服务器的cawserver.properties配置了caw.sch.quartz = 1;这台服务器才能运行M18的job;

如果开发者手动发布一个job在一台服务器,那么这个job只在这台服务器里跑,集群的其他服务器不会处理这个job;然后所有服务器每天每隔8小时会将本服务器运行job都移除,重新发布数库cawjob表中12个小时内会跑的job;这个时候集群服务器就会出现多台服务器抢一个job的情况,但是每一次都只会有一台服务器抢成功并运行这个job,其他服务器不运行;

**注:**当服务器的caw.sch.quartz = 0时,这台服务器不会运行job;但是可以通过caw.sch.targetUrl 和caw.sch.targetAccessToken配置将从这台服务器发布的job指派给固定的服务器运行;在目标服务器发布job;目标服务器必须可以运行job;

cawserver.properties配置

#本机可以跑定时任务,如果不为1 则不跑;
caw.sch.quartz = 1

#当caw.sch.quartz 不为1时,在本机发布任务,将会通过这个路径访问ws ,在目标机发布任务;目标机必须可以跑定时任务;
caw.sch.targetUrl = http://127.0.0.1:8080/jsf/rfws/
#target 机的caw.ws.glAccessToken
caw.sch.targetAccessToken=iz

# 如何测试定时任务

如果在开发的过程中,想去debug一个job,但是可能其他人的服务器跟你用的是同一个的时候,就会出现抢运行job的情况;所有这个时候,可以使用开发者配置文件reset.caw里面的参数去锁定job只在本机运行;

reset.caw配置

#用于锁定job,可以锁定多个job,使用;隔开,被锁定的job只能被锁定job的服务器运行
caw.ap.sch.debugJobName=caw.sync.1
#用于锁定jobGroup,可以锁定多个jobGroup,使用;隔开,被锁定的job只能被锁定job的服务器运行
caw.ap.sch.debugJobGroup=caw.sync
#锁定job多久,单位是小时
caw.ap.sch.debugTime=5

定时任务使用数据库表介绍 创建定时任务 | 表名 | 说明 | | ---------- | ----------------- | | cawjob | 存放定时任务发布的信息 | | cawjoblog | 存放定时任务运行过程的信息 | | cawjoblock | 存放定时任务被那台服务器锁住的信息 | 关于集群下面的说明 如何测试定时任务

# 文件流的处理方式

在之前描述datadict属性的时候,你应该发现在对应的属性里面无法处理文件流类型的东西。

M18系统统一的文件流存储到filedata中。

  • 同一个文件仅仅会对应filedata的一行记录,系统使用MD5+size来识别文件的唯一性
  • 保存在filedata的文件不提供删除功能
  • 为了避免用户遍历的方式读取出图片(图片存在不需要验证登陆的link),系统使用imgcode

文件的操作

FileCurdEao fileEao = JNDILocator.getInstance().lookupEJB(FileCurdEao.class);

byte[] byteArray = **\\需要保存的byte[]
\\直接将内存的中的数据保存到db (仅仅合适处理小文件)
long fileId = fileEao.saveFile("pdf", byteArray, "work.pdf");

String filePath =**\\需要保存的文件路径(服务器路径)
\\使用文件将数据保存到DB
fileId = fileEao.saveFile("pdf", filePath, "work.pdf");

\\将已经保存的文件数据读取出来
String filePath_read = fileEao.getFilePath(fileId);

\\如果你需要记录文件,那么将这个fileId保存起来即可

图片的操作

ImageLocal imageEJB = JNDILocator.getInstance().lookupEJB("ImageEJB", ImageLocal.class);
InputStream inputStream = ***\\需要处理的图片文件

\\将图片保存 获取imgcode
String imgcode = imageEJB.uploadImage("imgName", inputStream);

\\通过key重新获取到图片文件
File img = imageEJB.getImage(imgcode, flase);
\\第二个参数为ture的时候默认返回缩略图,推荐尽量使用获取缩略图

\\如果你需要记录图片,那么将这个imgcode保存起来即可

# 集群Cache同步

CawCache,主要是存放一些从配置读取的内容,和一些从数据库读取出来的不会频繁修改的数据(避免多次查询数据库);现在系统中Datadict,Mess, 模块的SqlEntity等数据都是使用了CawCache存放数据.

# 使用CawCache

开发者想要添加一种Cache,可以使用CawCache的方法添加;请查看CawCache的addSelfPopulatingCache方法

然后使用CawCache的putObject 或者remove方法,去操作cache中的数据;

通过CawCache 的getObject方法,可以拿到cache的数据

//第一个参数是cache的名字,第二个参数是指cache最多有多少个,第三个参数是指cache没被使用多久后就会失效(单位是秒);第4个参数如果为true,则读取这个cache的时候要复制一个object出来返回
CawCache.addSelfPopulatingCache(navMenu, CawLib.getBaseCacheSize() * 2, 86400, false, new NavmenuEntryFactory());

//第一个参数是已注册的cache名字;第二个参数是要放入到这个cache的缓存的key,需要是可序列化的key;第三个参数是要放入到这个cache的缓存内容
CawCache.putObject(cawcheName, key, object);

//第一个参数是已注册的cache名字;第二个参数是这个cache里面缓存的key,
MenuItemConfig menuCfg = (MenuItemConfig) CawCache.getObject(CawCache.menuCache, menukey);

//第一个参数是已注册的cache名字;第二个参数是这个cache里面缓存的key,
CawCache.remove(CawCache.userSettingCache, uid);

# 集群里面的注意事项

在集群的情况下,由于数据的修改,缓存就有可能出现不同步的现象;

**注1:**使用cache的时候必须考虑好如何同步数据,不然将会出现无法预测的问题(不单单针对CawCache)

**注2:**并不是所有的缓存都需要同步

现在推荐使用以下的两种方式去同步缓存;

  • 默认同步缓存方法

    推荐用法:cache的内容简单,只对CawCache做简单的put或delete;

    请使用 CawClusterLib 的方法 callCawCacheClusterEvent

增加集群缓存

	CawClusterCacheDto dto = new CawClusterCacheDto();
	dto.setName(UseInfoCache.CACHE_KEY);
	dto.getCacheMap().put(menuCode, uic);
	dto.setCacheClass(UseInfoCache.class);
	CawClusterLib.callCawCacheClusterEvent(dto);

删除集群缓存

      CawClusterCacheDto dto = new CawClusterCacheDto();
      dto.setName(UdfLogic.CACHE_KEY);
      dto.getDeleteKeys().add(code);
      CawClusterLib.callCawCacheClusterEvent(dto);

CawClusterCacheDto 介绍:

参数名 类型 说明
name String 需要同步的cache的名字
cacheMap Map<Serializable, Object> 这个map中缓存的值将会添加到集群的其他机器中,map存的是缓存的key-value 结构
cacheClass Class<?> cacheMap中value的class类型
deleteKeys Set<Serializable> 集群的其他机器将会移除的缓存,set存的是缓存的key
  • 自定义同步缓存方法

    需要同步CawCache或者一些静态的Map,Collection等缓存,有些缓存可能不是简单的添加或删除,或者需要同时操作几种不同的缓存,那么就需要自定义class去处理了;

在app.xml中配置标签clusterListerner

clusterListerner标签参数介绍

名字 类型 说明
eventType string 同步缓存的类型
className string 这里填class的完整路径;同步缓存的处理class,class 需要继承CawClusterListenerAdapter,并实现run方法

使用自定义方法同步缓存

在需要同步集群缓存的地方调用CawClusterLib 的callClusterEvent方法;然后其他集群的机器就会运行对应Listener的run方法了:

		JSONObject jo = new JSONObject();
		jo.put("cacheName", JsfCache.theme);
		jo.put("cacheKey", (long) action.getData(ActionParam.id));

		//一个参数是xml配置的eventType;第二个参数是String,一般是传一个json,集群服务器拿到这个参数后会传给clusterListerner的run方法;
		CawClusterLib.callClusterEvent(CawClusterEvent.JSFCACHE, jo.toJSONString());

# Object共享

M18系统支持不同系统之间进行数据共享;一般是一些设置模块使用;使用数据共享要在app.xml中配置标签dataObject;

dataShare标签介绍

名字 必填 类型 默认值 说明
className true string 填class的完成路径,class必须继承ModuleObjectHandler

dataObject 配置例子

<dataObject  className="com.multiable.core.ejb.bean.dataobject.handler.ComboObjectHandler" ></dataObject>

DataObjectHandler 例子

public class ComboObjectHandler extends ModuleObjectHandler {

	@Override
	public String getName() {
		return "comboData";
	}

	@Override
	public String getMenuCode() {
		return "comboData";
	}

	@Override
	public String getModuleName() {
		return "comboData";
	}

	@Override
	public String getMess() {
		return "comboData";
	}

	@Override
	public List<String> getInfoMessList() {
		List<String> messList = new ArrayList<>();

		messList.add("dataObject.comboDataInfo1");

		return messList;
	}

	@Override
	public Set<String> getDepends() {
		Set<String> depends = super.getDepends();
		depends.add("udfMess");
		return depends;
	}
}

ModuleObjectHandler 是实现了DataObjectHandler接口的,下面是DataObjectHandler部分方法介绍

public interface DataObjectHandler {

	//导入object上传文件后会运行这个方法,可以通过覆写这个方法干预entity的生成
	public DataObjectBaseDto restoreDsDto(DsEntryObject entryObj);
	
  	//导出object的时候,可以通过覆写这个方法,将一些图片文件类的信息写到导出的Object里面;
	public DsEntryObject genDsEntry(DataObjectBaseDto dto);

  	//导出object的时候,可以通过覆写这个方法去干预entity的导出
	public void fillupDto(DataObjectBaseDto dto, DataShareObjectDto dsDto);

	//导入object进行安装的时候,可以通过覆写这个方法,干预object的安装
	public Map<Long, CheckMsg> install(long beId, DataObjectBaseDto dsDto, List<DsInstallDto> insList, Map<String, Map<Long, Long>> depMapping, Object detail);

}

# 常用工具类

# 基本操作

说明
ArrayLib 处理数据的工具类;值得注意的方法 1.检查数组中是否包含有一个字符串,2.合并数组
ClassLib 处理Class的工具类;主要提供了方法 1.根据class路径或者Class<?> 获取类名, 2. 获取class 的Field 3. 获取class 的方法 4.获取class 的类型
DateLib 处理Date value的工具类;主要提供的方法 1 获取最大时间和最小时间,2 比较两个时间相差多少,3 对date value 进行加减,4根据String 创建Date, 5获取当前时间和一些转换date类型的方法
DateFormatLib 处理Date类型转换的工具类;主要提供的方法 1 将Date 根据dateFormat 转换成对应的String ,2 将String 根据dateFormat 转换成Date类型
ListLib 处理List 的工具类;主要提供的方法有 1 获取new List,2 判断List是否为空, 3 判断两个List是否一样,4 List的排序,5获取一个Object 的Field 的Value List
MathLib 提供数学计算的工具类;主要提供的方法有 1 对小数的四舍五入,接近的最大整数,接近的最小整数, 2 选取一串数字中最大数,最小数 ,3 获取一串数字的平均值,总和,4 获取绝对值 5 转换String 成 int,Double
StringLib 处理String 的工具类;主要提供的方法有 :1获取唯一的String, 2检查String是否相等,是否为空 , 3按照不同的方案剪切String 4 按照不同的方案加上String 或者替换String ,5 提供转换size 成 MB,KB的方法 , 6 将String 中的字符替换成 HTML 的格式

# 特定操作

说明
SqlTableLib 实现SqlTable的表内操作,以及SqlTable到POJO或JSON字符串的相互转换;主要提供的方法有: 1对比两个SqlTable 是否相等, 2 将一个SqlTable 中的一行复制到另一个SqlTable, 3 SqlTable 与PoO 之间的相互装换,4 SqlTable 与 Json 字符串之间的相互装换
CheckMsgLib 处理CheckMsg 的工具类;主要提供的方法有 1 提供创建CheckMsg 的方法, 2将CheckMsg 和Json String 相互转换 ,3 根据CheckResult 创建ResponseBuilder ,4 将 jsonObject 或SqlTable 转换成jsonString 后放入Response 中返回 ,5创建错误信息的Response
ConvertLib 转换对象的类型的工具类;主要提供的方法 1. 转换Obeject 成 Boolean,String,Double,Long,Integer,Number,Date , 2.将String 转换成对象的Object 类型,例如Date,Number,Boolean
CawLib 获取caw配置信息的工具类;主要提供了方法 1. 获取服务器ip地址,2.获取cawserver.properties配置文件信息,3. 获取唯一的key

发送邮件示例

@EJB(beanName = "MacEmailSenderEJB")
private MacEmailSenderLocal emailEJB;
***
MacEmail email = new MacEmail();
for (MacEmailAttachment atta : getAttachments()) {
  email.addAttachment(atta);
}

email.setTo("test@mac.com");
email.setCc("test1@mac.com");
email.setSubject("sunject - test");
email.setContent("conent");

CheckResult result = emailEJB.sendEmailWithId(email, emailSettingId);
***

:emailSettingId是邮件服务器设定的Id,如果不传输将使用默认格式。

审核追踪示例

List<AuditTrailData> datas = new ArrayList<>();
AuditTrailData data = new AuditTrailData();
data.setCode(code);
data.setDesc(desc);
data.setEntityId(entityId);
data.setExeTime(System.currentTimeMillis() - start);
data.setiRev(iRev);
data.setMenuCode("user");
data.setModuleName("user");
data.setOperation("test");
data.setOperationDesc("failed");

datas.add(data);

AuditTrailLib.addAuditTrail(datas);

:start是代码开始运行时的时间戳。