# 后端开发文档
# 逻辑模块
# 模块的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; |
保存的过程
curdEJb调用Save方法;
运行标志了CheckType =SAVE,CheckRange =BEFORE或者CheckRange =BOTH的方法,如果有其中一个方法返回的CheckMsg 不通过,就返回错误信息,不继续保存的操作;这个时候的check方法以查询为主,主要是检查数据的准确性,不会做一些修改数据库的操作;因为这个时候还没有在save的事务里面;
如果Save-Before没有错误返回,将进入save的事务,先从SeSaveParam.getSqlBeforeDtos()取出sql List,逐一运行List的sql,getSqlBeforeDtos()里面的信息,一般是从before Check写入;发生错误后,将中断事务,运行
CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;
如果sql Before没有发生错误,将继续下一步,将entity保存到数据库,保存到数据库的过程如果发生错误,将中断事务,运行CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;
将entity保存到数据库后,下一步运行CheckType =SAVE,CheckRange =SAVING的checker方法;如果发生错误,将中断事务,运行CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;
.如果SAVING的Checker没有错误,将从SeSaveParam.getSqlAfterDtos()取出sql List,逐一运行List的sql,getSqlAfterDtos()里面的信息;发生错误后,将中断事务,运行
CheckType =SAVE,CheckRange =ROLLBACK的checker方法;然后返回错误信息;
- 如果Sql After也没有发生错误,这个时候将会提交整个save的事务;
- 接下来将运行标志了CheckType =SAVE,CheckRange =AFTER或者CheckRange =BOTH的方法,这些方法如果发生错误,返回错误信息也不会回滚之前的事务了,所以这时候尽量不要返回错误信息,只做一些确保正确的操作即可;
请参考下图进一步了解
删除的过程
删除的过程与保存的过程基本一样,只是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 ;
更新的过程如下图
# 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>
# navmenu.xml
用户在使用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/
留空
用于表示其他类型模块
示例:
<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可以直接编辑对应的语言文件。
注:在每添加一个新的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
自动生成.
特别的,在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查找对应的表结构信息。
在系统模块数据字典
中可以找到对应菜单模块对应的表结构信息。
在代码里面,已知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类;
使用IExcelGener
的createTemplate
方法创建导入数据的excel模板;使用dataImportConveter
的convert方法将excel的数据转换成M18系统的SqlEntity结构;使用getImportResultWriter
方法得到ImportResultWriter
,用于将错误信息写到excel文件中;
**注:**M18系统默认使用EntityImportConverter
类转换导入的excel数据;使用ExcelImportResultWriter类将错误信息写到excel文件中;
**注:**如果用户不重新定义IExcelGener
和dataImportConveter
的情况下,想干预导入功能,还有一个办法;
在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的相关技巧。
创建一个数据源如下:
注:上图的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即可以获取到当前
将jrxml放入代码,进一步编写xml 参照1.0只不过的说明和示例
重启Jboss进行测试。
注:目前系统在启动的时候将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是代码开始运行时的时间戳。