NCC架构参照开发源码实现

NCC架构参照开发源码研究

1.参照

​ 在界面开发过程中,经常会出现需要引用系统中已定义的数据场景。在编辑界面通过弹出一个参照展示界面供选择参照来源数据。在开发过程中,会经常重复使用相同的 数据选择界面,例如单据上一般都会引用组织。因此需要提供一套统一的数据选择控件及服务,在业务单据开发时,只需要直接使用,减少业务开发的工作量。其次是基于表单模版进行开发时,框架需要一套标准的数据选择控件,已便于动态的进行控制。当需要引用数据时,使用统一的前端控件即可完成数据的选择,而不需要开发。为此,在yonbip中提供了统一公共的数据选择控件及服务框架,简称为参照。 参照管理,即管理各个领域服务中注册的参照信息,提供各个领域的参照查询、编辑、新增功能,简单来说,一个实体依赖另外一个实体的数据,另外一个实体就叫参照,参照是主要用于其他页面选择使用的。

参照,就是对其他实体信息的参照和对照

YonBIP高级版参照:

  • 前端JS:通过配置属性控制参照的界面显示。
  • 后端Java:拼接查询参数数据的具体SQL语句。

2.参照的业务场景

使用参照的场景通常满足以下几个要求:

  • 第一,用户需要填写的数据不可以随意填写,且已经存在于数据库。
  • 第二,这些数据是一些常用和固定的数据,如组织、部门、人员、岗位等。
  • 第三,用户在选择数据时需要进行权限过滤,以防止用户越权选择自己无权管理的数据。

3. 参照分类(样式截图)

  • 表型参照
    • 后端 Java 类需继承 DefaultGridRefAction 类,前端 JS 配置中需加入 ColumnConfig 的配置一次请求。
    • 列表参照(单/多选)

ncc1.png

单选

ncc2.png

下拉样式参照:

ncc3.png

树形参照

​ 后端 Java 类需继承 DefaultTreeRefAction 类,前端 JS 配置中需加入 rootNode 和 TreeConfig 的配置一次请求。

单选

ncc4.png

多选

ncc5.png

树表型参照

实际上是树形参照和表形参照的集合。两次请求,一次请求走树形参照 action,一次请求走表形参照 action。

ncc6.png

4. 参照的实现原理

参照所在数据库表为:bd_refinfo 。

ncc8.png

5.代码研究

5.1 ICommonAction(接口)

1
2
3
4
5
6
7
8
9
package nccloud.framework.web.action.itf;

import nccloud.framework.web.container.IRequest;

public interface ICommonAction {
//该接口定义了一个doAction方法。
//强制要求实现类必须实现doAction方法,处理请求并返回响应结果
Object doAction(IRequest var1);
}

5.2 NCCAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// NCCAction 是一个抽象类,提供了处理 Web 请求的标准化流程。
//该类实现了一个ICommonAction接口,包含请求参数处理、操作执行和异常处理等核心逻辑
public abstract class NCCAction implements ICommonAction {
//用来存储操作码
private String strMdOperateCode = null;
private String strOperateCode = null;
private String strResourceCode = null;

//默认构造函数
public NCCAction() {
}

//检查数据权限,确保用户可以执行该操作
protected void checkDataPermission(IRequest request, Object objData) throws Exception {
DataPermissionAction dataPermissionAction = new DataPermissionAction();
ValidationException validException = dataPermissionAction.checkDataPermission(this.getResourceCode(), this.getMdOperateCode(), this.getOperateCode(), objData);
dataPermissionAction.dealValidationException(validException);
}

//核心方法,处理请求,执行制定操作
public final Object doAction(IRequest request) {
Logger.debug("begin action-->" + this.getClass().getName() + ".doAction()");
Object para = null;//存储解析后的请求参数
Object result = null;//存储操作执行后的结果

try {
//获取请求参数
para = this.getPara(request, this.getParaClass());
if (!this.doBeforeAction(request, para)) {
//执行前的预处理,如果失败就返回
Object var10 = result;
return var10;
}

//执行核心逻辑
result = this.execute(request, para);
this.doAfterSuccess(request, para);
} catch (Exception var8) {
Exception ex = var8;
this.doAfterFailure(request, para);//操作失败后的处理
this.handleException(ex);//异常处理
} finally {
Logger.debug("end action-->" + this.getClass().getName() + ".doAction()");
}

return result;//返回执行结果
}

//在操作失败后,提供子类重写
protected <T> void doAfterFailure(IRequest request, T para) {
}

//在操作成功后,提供子类重写
protected <T> void doAfterSuccess(IRequest request, T para) throws Exception {
}

//操作执行前的预处理,默认返回值true,允许操作执行,子类可重写
protected <T> boolean doBeforeAction(IRequest request, T para) throws Exception {
return true;
}

//抽象方法,子类必须实现这个方法用于定义具体操作的执行逻辑
public abstract <T> Object execute(IRequest var1, T var2) throws Exception;

//getter方法
//获取 MdOperateCode
public final String getMdOperateCode() {
return this.strMdOperateCode;
}

// 获取 OperateCode
public final String getOperateCode() {
return this.strOperateCode;
}

// 从请求中获取参数并转换为指定类型的对象
public <T> T getPara(IRequest request, Class paraClass) {
String strRead = request.read();
T para = JsonFactory.create().fromJson(strRead, paraClass);
return para;
}

// 获取参数类型,默认返回 HashMap.class
protected Class getParaClass() {
return HashMap.class;
}

// 获取 ResourceCode
public final String getResourceCode() {
return this.strResourceCode;
}

// 处理业务异常,根据不同类型的异常返回相应的错误信息
protected void handleBusinessException(BusinessException ex) {
String strMsg = null;
if (ex instanceof BizLockFailedException) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000004");
} else if (ex instanceof LockFailedException) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000000");
} else if (ex instanceof ValidationException) {
strMsg = ((ValidationException)ex).getMessage();
} else if (ex instanceof VersionConflictException) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000002");
} else {
strMsg = ex.getMessage();
}

ExceptionUtils.wrapBusinessException(strMsg);
}

//处理不同类型的异常
protected void handleException(Exception ex) {
Throwable ex2 = ExceptionUtils.unmarsh(ex);
Logger.error("**************************************************** NCCAction Error ****************************************************");
Logger.error(ex2.getMessage(), ex);
if (ex2 instanceof RuntimeException) {
this.handleRuntimeException((RuntimeException)ex2);
} else if (ex2 instanceof BusinessException) {
this.handleBusinessException((BusinessException)ex2);
} else {
this.handleUnknownException(ex2);
}

}

//处理运行时异常
protected void handleRuntimeException(RuntimeException ex) {
if (ex instanceof FrameworkSecurityException) {
ExceptionUtils.wrapBusinessException(NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000006"));
} else if (ex instanceof BusinessExceptionAdapter) {
this.handleBusinessException(((BusinessExceptionAdapter)ex).originalException);
} else if (!(ex instanceof nccloud.base.exception.BusinessException) && !(ex instanceof nccloud.framework.core.exception.BusinessException)) {
this.handleUnknownException(ex);
} else {
this.handleUnknownException(ex);
}

}

//处理未知异常
protected void handleUnknownException(Throwable ex) {
String strMsg = ex.getMessage();
if (StringUtils.isBlank(strMsg)) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000001");
}

BusinessException business = new BusinessException(strMsg);
business.setStackTrace(ex.getStackTrace());
ExceptionUtils.wrapException(business);
}

//setter方法
public void setMdOperateCode(String mdOperateCode) {
this.strMdOperateCode = mdOperateCode;
}

public void setOperateCode(String operateCode) {
this.strOperateCode = operateCode;
}

public void setResourceCode(String resourceCode) {
this.strResourceCode = resourceCode;
}
}

5.3 IRefSqlBuilder

1
2
3
4
5
6
7
8
9
10
11
//SQL生成器
public interface IRefSqlBuilder {
//用于拼接Sql语句中where 后的条件语句
String getExtraSql(RefQueryInfo var1, RefMeta var2);

//设置where后条件语句中占位符对应的真实数据(为了防止Sql注入)
SqlParameterCollection getExtraSqlParameter(RefQueryInfo var1, RefMeta var2);

//设置SQL语句中的order by内容--实现排序
String getOrderSql(RefQueryInfo var1, RefMeta var2);
}

5.4 AbstractRefAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 抽象的引用动作类,继承了NCCAction并实现了IRefSqlBuilder接口,负责数据引用查询的操作
public abstract class AbstractRefAction extends NCCAction implements IRefSqlBuilder {
private Boolean blShowDisabledData = null;
private String strDataPowerColumn;
private String strMdClassId;
private String strUnitPkKey = null;
private String strUsualDataPkFieldName = null;
private String strUsualDataTableName = null;

//无参构造
public AbstractRefAction() {}

/**
* 执行引用查询操作
* @param request 请求对象
* @param para 查询参数
* @return 查询结果
* @throws Exception 异常
*/
public <T> Object execute(IRequest request, T para) throws Exception {
TreeRefQueryInfo refQueryInfo = (TreeRefQueryInfo) para;
Map<String, String> queryCondition = refQueryInfo.getQueryCondition();
boolean isShowUsual = UFBoolean.valueOf(queryCondition.get("isShowUsual")).booleanValue();

// 如果显示常用数据
if (isShowUsual) {
String strUsualGridRefActionExt = queryCondition.get("UsualGridRefActionExt");
strUsualGridRefActionExt = StringUtils.isBlank(strUsualGridRefActionExt)
? UsualRefSqlBuilder.class.getName()
: strUsualGridRefActionExt + "," + UsualRefSqlBuilder.class.getName();
String gridRefActionExts = queryCondition.get("GridRefActionExt");
gridRefActionExts = StringUtils.isBlank(gridRefActionExts)
? strUsualGridRefActionExt
: gridRefActionExts + "," + strUsualGridRefActionExt;
queryCondition.put("GridRefActionExt", gridRefActionExts);

// 如果没有指定主键,设置主键
if (StringUtils.isBlank(this.getQueryValue(refQueryInfo, "unitPks"))) {
this.setQueryValue(refQueryInfo, "unitPks", this.getPk_org(refQueryInfo));
}
} else {
// 初始化分页信息
this.initPageInfo(refQueryInfo.getPageInfo());
}

// 初始化单位主键
this.initUnitPk(refQueryInfo, this.getKeyUnitPks());

// 处理并引用返回结果
RefQueryResult refQueryResult = this.processData(refQueryInfo);
return refQueryResult;
}

// 获取数据权限列字段,如果未指定则返回引用源数据的主键字段
public final String getDataPowerColumn(RefQueryInfo refQueryInfo, RefMeta refMeta) {
return StringUtils.isBlank(this.strDataPowerColumn) ? refMeta.getPkField() : this.strDataPowerColumn;
}

// 获取额外的SQL条件,拼接Sql语句中where 后的条件语句
public String getExtraSql(RefQueryInfo refQueryInfo, RefMeta refMeta) {
return null;
}

// 获取额外的SQL参数,设置where后条件语句中占位符对应的真实数据(为了防止Sql注入)
public SqlParameterCollection getExtraSqlParameter(RefQueryInfo refQueryInfo, RefMeta refMeta) {
return null;
}

// 获取引用的VO
protected RefVO_mlang[] getMultiLangRefVO() {
return null;
}

// 获取语言资源
protected String getMultiLangResource(String strDirName, String strFieldValue, String strResId) {
return NCLangRes4VoTransl.getNCLangRes().getString(strDirName, strFieldValue, strResId);
}

// 抽象方法,子类必须实现该方法,用于获取引用元数据
public abstract RefMeta getRefMeta(RefQueryInfo var1);

// 获取SQL构建器
//这是一个工厂方法,返回 SQL 构造器
//DefaultRefSqlBuilder 负责根据 RefQueryInfo 构建 SQL 查询语句。
protected IRefSqlBuilder getRefSqlBuilder(RefQueryInfo refQueryInfo) {
return new DefaultRefSqlBuilder(this, this, refQueryInfo);
}

// 初始化单位主键
protected void initUnitPk(RefQueryInfo refQueryInfo, String strQueryKey) {
if (!StringUtils.isBlank(strQueryKey)) {
String[] strUnitPks = this.getUnitPks(refQueryInfo);
if (!ArrayUtils.isEmpty(strUnitPks) && !StringUtils.isBlank(strUnitPks[0])) {
this.setQueryValue(refQueryInfo, strQueryKey, strUnitPks[0]);
}
}
}

// 处理数据
protected abstract RefQueryResult processData(TreeRefQueryInfo var1);

// 多语言翻译处理
// 通过 RefVO_mlang 中的多语言配置,设置不同语言的显示值,保证参照名称在不同语言环境下能正确展示。
protected void processMultiLang(RefQueryInfo refQueryInfo, RefRow refRow) {
RefVO_mlang[] multiLangRefVOs = this.getMultiLangRefVO();
if (!ArrayUtils.isEmpty(multiLangRefVOs)) {
Map<String, Cell> mapRowValues = refRow.getValues();
for (RefVO_mlang mlRefVO : multiLangRefVOs) {
String strResId = mlRefVO.getPreStr();
String[] strResIdFieldNames = mlRefVO.getResIDFieldNames();

if (!ArrayUtils.isEmpty(strResIdFieldNames)) {
for (String fieldName : strResIdFieldNames) {
Object objValue = mapRowValues.get(fieldName).getValue();
if (objValue != null) {
strResId += objValue.toString();
}
}

Object objFieldValue = mapRowValues.get(mlRefVO.getFieldName()).getValue();
String strDirName = mlRefVO.getDirName();
if (mlRefVO.getDirFieldName() != null) {
Object objDirFieldName = mapRowValues.get(mlRefVO.getDirFieldName()).getValue();
if (objDirFieldName != null) {
strDirName = objDirFieldName.toString();
}
}

String strFieldValue = objFieldValue == null ? null : objFieldValue.toString();
String strMultiLang = this.getMultiLangResource(strDirName, strFieldValue, strResId);
mapRowValues.get(mlRefVO.getFieldName()).setValue(strMultiLang);

RefMeta refMeta = this.getRefMeta(refQueryInfo);
if (refMeta != null && ObjectUtils.equals(refMeta.getNameField(), mlRefVO.getFieldName())) {
refRow.setRefname(strMultiLang);
}
}
}
}
}

//用于枚举值的多语言处理。对于枚举类型的字段
//系统通过多语言映射表返回相应语言的值,确保参照数据中的枚举字段多语言化。
protected void processMultiLangForEnum(RefQueryInfo refQueryInfo, RefRow refRow) {
Map<String, Map<String, String>> mapRes = this.getMultiLangForEnum();
if (!MapUtils.isEmpty(mapRes)) {
Map<String, Cell> mapRowValues = refRow.getValues();
for (String strEnumFieldName : mapRes.keySet()) {
Cell cell = mapRowValues.get(strEnumFieldName);
Object objFieldValue = cell.getValue();
String strMultiLang = mapRes.get(strEnumFieldName).get(objFieldValue);
cell.setValue(strMultiLang);
}
}
}

protected String[] processRefPks(RefQueryInfo refQueryInfo, IRefSqlBuilder refSqlBuilder, String... strQueryRefPks) {
return strQueryRefPks;
}

protected RefRow[] processRefRows(RefQueryInfo refQueryInfo, IRefSqlBuilder refSqlBuilder, RefRow... refRows) {
if (!ArrayUtils.isEmpty(refRows)) {
for (RefRow refRow : refRows) {
this.processMultiLang(refQueryInfo, refRow);
this.processMultiLangForEnum(refQueryInfo, refRow);
}
}
return refRows;
}

// 设置查询条件的值
public void setQueryValue(RefQueryInfo refQueryInfo, String strQueryKey, String strQueryValue) {
refQueryInfo.getQueryCondition().put(strQueryKey, strQueryValue);
}
}

5.5 DefaultGridRefAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class DefaultGridRefAction extends AbstractRefAction {
public DefaultGridRefAction() {
}

public abstract RefMeta getRefMeta(RefQueryInfo var1);

/** @deprecated */
@Deprecated
protected void initPageInfo(PageInfo pageInfo) {
}

protected RefQueryResult processData(TreeRefQueryInfo refQueryInfo) {
//1.获取SQL构造器
IRefSqlBuilder refSqlBuilder = this.getRefSqlBuilder(refQueryInfo);
//2.初始化处理器
NCGridRefDBProcessor processor = new NCGridRefDBProcessor(this.getRefMeta(refQueryInfo), refSqlBuilder);
//3.根据条件查询参照主键
String[] strQueryRefPks = processor.queryRefPks(refQueryInfo);
//4.处理查询出的参照主键
strQueryRefPks = this.processRefPks(refQueryInfo, refSqlBuilder, strQueryRefPks);
//5.根据主键查询所有所需的字段
RefRow[] refRows = processor.getRowsByPks(strQueryRefPks);
//6.处理最后查询出的结果集
refRows = this.processRefRows(refQueryInfo, refSqlBuilder, refRows);
//7.构造返回结果,返回数据
RefQueryResult refQueryResult = processor.constructResult(refRows, refQueryInfo);
return refQueryResult;
}
}

5.6 NCGridRefDBProcesso

5.7 Tax_hws_serviceDefaultGridRefAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Tax_hws_serviceDefaultGridRefAction extends DefaultGridRefAction {

public Tax_hws_serviceDefaultGridRefAction() {
super();
setResourceCode(IOrgResourceCodeConst.ORG);
}

@Override
public RefMeta getRefMeta(RefQueryInfo refQueryInfo) {
RefMeta refMeta = new RefMeta();
setResourceCode(IOrgResourceCodeConst.ORG);
refMeta.setCodeField("code");
refMeta.setNameField("name");

refMeta.setPkField("pk_vtax_tax_hws_service");
refMeta.setTableName("vtax_tax_hws_service");
refMeta.setMutilLangNameRef(true);
return refMeta;
}

}

6.参照前端

ncc9.png

ncc10.png

ncc12.png

点击(事件)参照表格后返回的 json 对象:

ncc13.png

ncc14.png

7.YonBIP 参照创建(应用注册)

ncc15.png

ncc16.png

ncc17.png

ncc18.png

8.讲解思路

  1. 模型层 (Model)
    1. NCGridRefDBProcessor
    2. IRefSqlBuilder
    3. INCDataQuery
    4. IMultiNCGridRefRowService
    5. ServiceLocator
  2. 视图层 (View)
    1. Refer 组件(前端 React 组件)
  3. 控制器层 (Controller
    1. DefaultGridRefAction
    2. AbstractRefAction
    3. NCCAction

9.参照前端(React)

  • 参照前端的相关属性配置

  • 前端主要设置了参照的相关属性,包括多余配置(领域模块名、默认语言、多语文件);

  • 选择生成的参照类型(grid-表型参照;tree-树形参照;gridtree-树表型参照);

  • refName为参照标题名字,

  • placeholder 是指参照输入框的占位提示文字,即在输入框中还未输入任何内容时,会显示的提示文本。

  • columnConfig 定义了参照弹窗中表格的列配置,决定了弹窗中展示的数据列和它们的顺序。这个配置通常与我们的业务需要相关,业务需要列显示什么字段就进行配置;

  • queryGridUrl后端接口路径,用于从后端获取参照数据。当参照组件加载时,系统会使用这个 URL 发送请求,以获得可以选择的具体数据列表。

1
2
3
columnConfig: [{name: ['编码', '名称'], code: ['refcode', 'refname']}]
//name: 用于定义表格列的显示名称。
//code: 对应后端数据返回的字段名,决定了表格中展示的数据源。

10.参照后端(Java)-MVC 模式讲解

​ 1.参照类Tax_hws_serviceDefaultGridRefAction

  • Tax_hws_serviceDefaultGridRefAction 类是整个参照功能的入口类,继承自 DefaultGridRefAction,该类主要用于处理用户从前端触发的参照请求,并生成查询参照数据的关键元数据。在这个类中,我们看到最核心的部分是 getRefMeta() 方法,它构建并返回 RefMeta 对象,表示参照的元数据定义。

  • getRefMeta(RefQueryInfo refQueryInfo) 这个方法是 Tax_hws_serviceDefaultGridRefAction 的核心方法,负责构建并返回参照元数据 RefMeta,这个对象包含了参照所需的所有信息(如字段名、表名、主键等),这些信息用于后续的 SQL 查询和数据展示。包括以下几部分:

    • 编码字段codeFieldcode,表示在表格中展示的每条记录的编码字段。
    • 名称字段nameFieldname,对应表格中展示的记录名称字段。
    • 主键字段pkFieldpk_vtax_tax_hws_service,这是数据库表 vtax_tax_hws_service 的主键字段,用于标识每条记录。
    • 表名tableNamevtax_tax_hws_service,表示从数据库中查询的表名。
    • 多语言支持:通过 setMutilLangNameRef(true) 说明参照支持多语言。

    主要作用

    • 定义当前参照的元数据,包括参照表的名称、参照字段(编码、名称、主键)以及是否支持多语言。
    • 通过 getRefMeta() 返回 RefMeta,为 DefaultGridRefAction 的核心查询逻辑提供必要的元数据信息。
    • 配置数据权限控制和是否显示被停用数据等业务规则(如 setResourceCodesetShowDisabledData)。

RefMeta 存储的数据示例:

Code Field: code

Name Field: name

PK Field: pk_vtax_tax_hws_service

Table Name: vtax_tax_hws_service

Multi-language Support: true

ncc24.png

10.2DefaultGridRefAction

  • DefaultGridRefAction 是一个抽象类,继承自 AbstractRefAction,它为表格参照功能提供了默认的实现。DefaultGridRefAction 中定义了参照功能的几个核心方法,帮助处理参照的查询逻辑,尤其是通过 processData() 方法完成对数据的查询与处理。
  • NCGridRefDBProcessor 是负责实际数据库查询的处理器,它结合 RefMetaIRefSqlBuilder 生成 SQL 语句,执行查询,并将结果封装成 RefRow[]RefQueryResult
  • getRefMeta():返回 RefMeta 对象,定义参照功能的数据结构,如字段、表名等。
  • processData()processData() 方法是整个数据查询的核心步骤,它负责通过 TreeRefQueryInfo 进行 SQL 查询构造,执行查询,处理主键与字段数据,最后返回一个查询结果集。
  • 职责:
    • 执行查询逻辑:根据 Tax_hws_serviceDefaultGridRefAction 中返回的 RefMeta,以及前端传递的查询条件,生成 SQL 查询,并执行查询。
    • 数据处理:通过数据库处理器(NCGridRefDBProcessor)进行主键查询、字段查询,并进一步处理查询出的数据。
    • 返回查询结果:最终将处理后的参照数据封装为 RefQueryResult 对象,返回给前端。
  • DefaultGridRefAction.java(核心处理逻辑类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public abstract class DefaultGridRefAction extends AbstractRefAction {
public DefaultGridRefAction() {
}

public abstract RefMeta getRefMeta(RefQueryInfo var1);

/** @deprecated */
@Deprecated
protected void initPageInfo(PageInfo pageInfo) {
}

protected RefQueryResult processData(TreeRefQueryInfo refQueryInfo) {
//1.获取SQL构造器
//IRefSqlBuilder 是一个接口,负责生成 SQL 查询语句。
//在这里,我们通过 getRefSqlBuilder(refQueryInfo) 方法获取 SQL 构造器。
//refQueryInfo 对象包含了前端传递的查询条件(如关键字、分页信息、过滤条件)
//这些条件会用于生成 SQL 查询。
//refSqlBuilder通过 RefQueryInfo 的数据生成最终执行的 SQL 查询语句
//处理条件拼接、排序等复杂的 SQL 操作。
IRefSqlBuilder refSqlBuilder = this.getRefSqlBuilder(refQueryInfo);


//2.初始化处理器
//NCGridRefDBProcessor 是一个用于执行数据库查询的处理器。
//它会结合 RefMeta(元数据)和 IRefSqlBuilder 生成查询。
//this.getRefMeta(refQueryInfo):调用会返回当前参照的元数据信息(如字段名、主键、表名等)用于SQL生成
//refSqlBuilder 用于构造 SQL 查询的实际语句:
//通过将 RefMeta 和 IRefSqlBuilder 传入,处理器能够构建出最终要执行的 SQL 查询。
NCGridRefDBProcessor processor = new NCGridRefDBProcessor(this.getRefMeta(refQueryInfo), refSqlBuilder);


//3.根据条件查询参照主键
//理器通过调用 queryRefPks() 方法,根据 refQueryInfo 中的条件,查询参照数据的主键(PK)。
//queryRefPks() 返回一个主键数组 strQueryRefPks。
//表示数据库中符合查询条件的记录的主键值。
String[] strQueryRefPks = processor.queryRefPks(refQueryInfo);

//4.处理查询出的参照主键
//通过调用 processRefPks() 方法,进一步处理查询出来的主键(参照过滤,处理特定的业务逻辑)
//处理完之后,返回一个更新后的主键数组 strQueryRefPks。
strQueryRefPks = this.processRefPks(refQueryInfo, refSqlBuilder, strQueryRefPks);

//5.根据主键查询所有所需的字段
//通过主键数组 strQueryRefPks,调用 processor.getRowsByPks(),从数据库中查询每条记录的所有字段信息。
//返回值是一个 RefRow[] 数组
//每个 RefRow 代表一个查询结果的行,包含所有所需的字段(如 code,name,pk 等)。
RefRow[] refRows = processor.getRowsByPks(strQueryRefPks);

//6.处理最后查询出的结果集
//进一步处理查询出的数据行,类似于处理主键的步骤。
//对数据行进行额外的处理或过滤,比如多语言处理、字段格式化等。
//处理完成回,返回一个refRows数组
refRows = this.processRefRows(refQueryInfo, refSqlBuilder, refRows);

//7.构造返回结果,返回数据
//调用 processor.constructResult() 方法,将处理过的 RefRow[] 和分页信息打包成一个 RefQueryResult 对象。
//refQueryResult 是最终返回给前端的数据结构,包含了查询的结果集和分页信息。
RefQueryResult refQueryResult = processor.constructResult(refRows, refQueryInfo);
return refQueryResult;
}
}

10.3 AbstractRefAction.java

  • AbstractRefAction 提供了很多参照功能中的基础逻辑,如数据权限、多语言处理、SQL 构建等。它通过接口 IRefSqlBuilder 实现 SQL 查询构建,并为参照功能提供了灵活的扩展接口。
  • DefaultRefSqlBuilder 会使用 RefQueryInfo 中存储的条件,包括组织、集团 ID 和其他过滤条件,这些都是由入口类或 AbstractRefAction 设置的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 抽象的引用动作类,继承了NCCAction并实现了IRefSqlBuilder接口,负责数据引用查询的操作
public abstract class AbstractRefAction extends NCCAction implements IRefSqlBuilder {
private Boolean blShowDisabledData = null;//控制是否展示被停用的数据
private String strDataPowerColumn;//负责数据权限的字段。系统可以根据用户角色或组织权限来过滤参照数据。
private String strMdClassId;
private String strUnitPkKey = null;
private String strUsualDataPkFieldName = null;
private String strUsualDataTableName = null;

//无参构造
public AbstractRefAction() {}

/**
* 执行引用查询操作
* @param request 请求对象
* @param para 查询参数
* @return 查询结果
* @throws Exception 异常
*/
public <T> Object execute(IRequest request, T para) throws Exception {
// 将通用参数 para 转换为 TreeRefQueryInfo,从而可以访问参照数据特定的查询条件 queryCondition。
TreeRefQueryInfo refQueryInfo = (TreeRefQueryInfo) para;
Map<String, String> queryCondition = refQueryInfo.getQueryCondition();
// 判断是否显示常用数据:通过 isShowUsual 判断用户是否选择了查看常用数据(即常用参照)。
boolean isShowUsual = UFBoolean.valueOf(queryCondition.get("isShowUsual")).booleanValue();
// 如果显示常用数据.开始构建常用参照的SQL
if (isShowUsual) {
// 系统会使用 UsualRefSqlBuilder,以便构建常用参照的 SQL 查询。
String strUsualGridRefActionExt = queryCondition.get("UsualGridRefActionExt");
strUsualGridRefActionExt = StringUtils.isBlank(strUsualGridRefActionExt)
? UsualRefSqlBuilder.class.getName()
: strUsualGridRefActionExt + "," + UsualRefSqlBuilder.class.getName();
// 确保常用数据查询能使用适当的 SQL 构建规则。
String gridRefActionExts = queryCondition.get("GridRefActionExt");
gridRefActionExts = StringUtils.isBlank(gridRefActionExts)
? strUsualGridRefActionExt
: gridRefActionExts + "," + strUsualGridRefActionExt;
queryCondition.put("GridRefActionExt", gridRefActionExts);
// 设置单位主键(组织过滤)
if (StringUtils.isBlank(this.getQueryValue(refQueryInfo, "unitPks"))) {
this.setQueryValue(refQueryInfo, "unitPks", this.getPk_org(refQueryInfo));
}
} else {
this.initPageInfo(refQueryInfo.getPageInfo());
}
this.initUnitPk(refQueryInfo, this.getKeyUnitPks());
// 系统调用 processData() 方法执行具体的参照数据查询
// 并返回查询结果 RefQueryResult,该对象包含查询后的数据和分页信息。
RefQueryResult refQueryResult = this.processData(refQueryInfo);
return refQueryResult;
}

// 获取额外的SQL条件,拼接Sql语句中where 后的条件语句
public String getExtraSql(RefQueryInfo refQueryInfo, RefMeta refMeta) {
return null;
}

// 获取额外的SQL参数,设置where后条件语句中占位符对应的真实数据(为了防止Sql注入)
public SqlParameterCollection getExtraSqlParameter(RefQueryInfo refQueryInfo, RefMeta refMeta) {
return null;
}

// getPk_org() 和 getPk_group() 提供了数据权限控制的组织和集团过滤条件。
// 入口类通过 setQueryValue() 方法将这些组织、集团 ID 作为条件传入 RefQueryInfo
// 确保只有当前用户所属的组织和集团数据可以被查询到。
public String getPk_org(RefQueryInfo refQueryInfo) {
String strPk_org = this.getQueryValue(refQueryInfo, "pk_org");
return StringUtils.isBlank(strPk_org) ? this.getPk_group(refQueryInfo) : strPk_org;
}

public String getPk_group(RefQueryInfo refQueryInfo) {
String strPk_group_session = SessionContext.getInstance().getClientInfo().getPk_group();
if (refQueryInfo == null) {
return strPk_group_session;
} else {
String strPk_group2 = this.getQueryValue(refQueryInfo, "pk_group");
return StringUtils.isBlank(strPk_group2) ? strPk_group_session : strPk_group2;
}
}

// 获取SQL构建器
// 这是一个工厂方法,返回 SQL 构造器
// DefaultRefSqlBuilder 负责根据 RefQueryInfo 构建 SQL 查询语句。
protected IRefSqlBuilder getRefSqlBuilder(RefQueryInfo refQueryInfo) {
return new DefaultRefSqlBuilder(this, this, refQueryInfo);
}

// 多语言翻译处理
// 通过 RefVO_mlang 中的多语言配置,设置不同语言的显示值,保证参照名称在不同语言环境下能正确展示。
protected void processMultiLang(RefQueryInfo refQueryInfo, RefRow refRow) {
RefVO_mlang[] multiLangRefVOs = this.getMultiLangRefVO();
if (!ArrayUtils.isEmpty(multiLangRefVOs)) {
Map<String, Cell> mapRowValues = refRow.getValues();
for (RefVO_mlang mlRefVO : multiLangRefVOs) {
String strResId = mlRefVO.getPreStr();
String[] strResIdFieldNames = mlRefVO.getResIDFieldNames();

if (!ArrayUtils.isEmpty(strResIdFieldNames)) {
for (String fieldName : strResIdFieldNames) {
Object objValue = mapRowValues.get(fieldName).getValue();
if (objValue != null) {
strResId += objValue.toString();
}
}

Object objFieldValue = mapRowValues.get(mlRefVO.getFieldName()).getValue();
String strDirName = mlRefVO.getDirName();
if (mlRefVO.getDirFieldName() != null) {
Object objDirFieldName = mapRowValues.get(mlRefVO.getDirFieldName()).getValue();
if (objDirFieldName != null) {
strDirName = objDirFieldName.toString();
}
}

String strFieldValue = objFieldValue == null ? null : objFieldValue.toString();
String strMultiLang = this.getMultiLangResource(strDirName, strFieldValue, strResId);
mapRowValues.get(mlRefVO.getFieldName()).setValue(strMultiLang);

RefMeta refMeta = this.getRefMeta(refQueryInfo);
if (refMeta != null && ObjectUtils.equals(refMeta.getNameField(), mlRefVO.getFieldName())) {
refRow.setRefname(strMultiLang);
}
}
}
}
}

//用于枚举值的多语言处理。对于枚举类型的字段
//系统通过多语言映射表返回相应语言的值,确保参照数据中的枚举字段多语言化。
protected void processMultiLangForEnum(RefQueryInfo refQueryInfo, RefRow refRow) {
Map<String, Map<String, String>> mapRes = this.getMultiLangForEnum();
if (!MapUtils.isEmpty(mapRes)) {
Map<String, Cell> mapRowValues = refRow.getValues();
for (String strEnumFieldName : mapRes.keySet()) {
Cell cell = mapRowValues.get(strEnumFieldName);
Object objFieldValue = cell.getValue();
String strMultiLang = mapRes.get(strEnumFieldName).get(objFieldValue);
cell.setValue(strMultiLang);
}
}
}

//processRefPks() 和 processRefRows() 方法
//会进一步调用 AbstractRefAction 的权限和多语言处理逻辑。
protected String[] processRefPks(RefQueryInfo refQueryInfo, IRefSqlBuilder refSqlBuilder, String... strQueryRefPks) {
return strQueryRefPks;
}

protected RefRow[] processRefRows(RefQueryInfo refQueryInfo, IRefSqlBuilder refSqlBuilder, RefRow... refRows) {
if (!ArrayUtils.isEmpty(refRows)) {
for (RefRow refRow : refRows) {
// 多语言处理
this.processMultiLang(refQueryInfo, refRow);
this.processMultiLangForEnum(refQueryInfo, refRow);
}
}
return refRows;
}

}

10.4NCCAction

  • NCCAction 是一个抽象基础类,定义了通用的操作流程和错误处理机制,为具体的业务逻辑类(例如参照功能类)提供支持。
  • NCCAction 提供了执行业务操作的通用流程,并定义了 doAction() 方法,作为所有具体操作类的执行入口。在参照功能中,这个类的主要作用包括以下几方面:
    • 标准化的操作流程doAction() 定义了操作执行的通用流程,包含参数获取(getPara)、预处理(doBeforeAction)、实际执行(execute)、后处理(doAfterSuccess)和异常处理(handleException)。
    • 权限校验checkDataPermission() 方法用于数据权限校验,确保只有符合权限的数据可以被访问。
    • 异常处理handleException()handleBusinessException() 处理不同类型的异常,确保系统稳定运行,并将合适的错误信息返回给用户。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// NCCAction 是一个抽象类,提供了处理 Web 请求的标准化流程。
//该类实现了一个ICommonAction接口,包含请求参数处理、操作执行和异常处理等核心逻辑
public abstract class NCCAction implements ICommonAction {
//用来存储操作码
private String strMdOperateCode = null;
private String strOperateCode = null;
private String strResourceCode = null;

//默认构造函数
public NCCAction() {
}

//检查数据权限,确保用户可以执行该操作
protected void checkDataPermission(IRequest request, Object objData) throws Exception {
DataPermissionAction dataPermissionAction = new DataPermissionAction();
ValidationException validException = dataPermissionAction.checkDataPermission(this.getResourceCode(), this.getMdOperateCode(), this.getOperateCode(), objData);
dataPermissionAction.dealValidationException(validException);
}

//核心方法,处理请求,执行制定操作
public final Object doAction(IRequest request) {
Logger.debug("begin action-->" + this.getClass().getName() + ".doAction()");
Object para = null;//存储解析后的请求参数
Object result = null;//存储操作执行后的结果

try {
//获取请求参数
para = this.getPara(request, this.getParaClass());
if (!this.doBeforeAction(request, para)) {
//执行前的预处理,如果失败就返回
Object var10 = result;
return var10;
}

//执行核心逻辑
result = this.execute(request, para);
this.doAfterSuccess(request, para);
} catch (Exception var8) {
Exception ex = var8;
this.doAfterFailure(request, para);//操作失败后的处理
this.handleException(ex);//异常处理
} finally {
Logger.debug("end action-->" + this.getClass().getName() + ".doAction()");
}

return result;//返回执行结果
}

//在操作失败后,提供子类重写
protected <T> void doAfterFailure(IRequest request, T para) {
}

//在操作成功后,提供子类重写
protected <T> void doAfterSuccess(IRequest request, T para) throws Exception {
}

//操作执行前的预处理,默认返回值true,允许操作执行,子类可重写
protected <T> boolean doBeforeAction(IRequest request, T para) throws Exception {
return true;
}

//抽象方法,子类必须实现这个方法用于定义具体操作的执行逻辑
public abstract <T> Object execute(IRequest var1, T var2) throws Exception;

//getter方法
//获取 MdOperateCode
public final String getMdOperateCode() {
return this.strMdOperateCode;
}

// 获取 OperateCode
public final String getOperateCode() {
return this.strOperateCode;
}

// 从请求中获取参数并转换为指定类型的对象
public <T> T getPara(IRequest request, Class paraClass) {
String strRead = request.read();
T para = JsonFactory.create().fromJson(strRead, paraClass);
return para;
}

// 获取参数类型,默认返回 HashMap.class
protected Class getParaClass() {
return HashMap.class;
}

// 获取 ResourceCode
public final String getResourceCode() {
return this.strResourceCode;
}

// 处理业务异常,根据不同类型的异常返回相应的错误信息
protected void handleBusinessException(BusinessException ex) {
String strMsg = null;
if (ex instanceof BizLockFailedException) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000004");
} else if (ex instanceof LockFailedException) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000000");
} else if (ex instanceof ValidationException) {
strMsg = ((ValidationException)ex).getMessage();
} else if (ex instanceof VersionConflictException) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000002");
} else {
strMsg = ex.getMessage();
}

ExceptionUtils.wrapBusinessException(strMsg);
}

//处理不同类型的异常
protected void handleException(Exception ex) {
Throwable ex2 = ExceptionUtils.unmarsh(ex);
Logger.error("**************************************************** NCCAction Error ****************************************************");
Logger.error(ex2.getMessage(), ex);
if (ex2 instanceof RuntimeException) {
this.handleRuntimeException((RuntimeException)ex2);
} else if (ex2 instanceof BusinessException) {
this.handleBusinessException((BusinessException)ex2);
} else {
this.handleUnknownException(ex2);
}

}

//处理运行时异常
protected void handleRuntimeException(RuntimeException ex) {
if (ex instanceof FrameworkSecurityException) {
ExceptionUtils.wrapBusinessException(NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000006"));
} else if (ex instanceof BusinessExceptionAdapter) {
this.handleBusinessException(((BusinessExceptionAdapter)ex).originalException);
} else if (!(ex instanceof nccloud.base.exception.BusinessException) && !(ex instanceof nccloud.framework.core.exception.BusinessException)) {
this.handleUnknownException(ex);
} else {
this.handleUnknownException(ex);
}

}

//处理未知异常
protected void handleUnknownException(Throwable ex) {
String strMsg = ex.getMessage();
if (StringUtils.isBlank(strMsg)) {
strMsg = NCLangRes4VoTransl.getNCLangRes().getStrByID("uif2", "DefaultExceptionHanler-000001");
}

BusinessException business = new BusinessException(strMsg);
business.setStackTrace(ex.getStackTrace());
ExceptionUtils.wrapException(business);
}

//setter方法
public void setMdOperateCode(String mdOperateCode) {
this.strMdOperateCode = mdOperateCode;
}

public void setOperateCode(String operateCode) {
this.strOperateCode = operateCode;
}

public void setResourceCode(String resourceCode) {
this.strResourceCode = resourceCode;
}
}

10.5 NCGridRefDBProcessor

  • NCGridRefDBProcessor 是参照功能中具体负责执行数据库操作的核心类,它处理参照数据的查询、结果处理等功能。
    • 这个类的主要任务是通过与 RefMetaRefQueryInfo 协作,生成 SQL 查询语句并执行它,最终返回参照数据。这类的主要方法包括 queryRefPks()getRowsByPks()constructResult()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//根据用户输入的查询条件(如关键字、过滤条件等),生成查询主键的 SQL 语句并执行,返回符合条件的主键数组。
@Override
public String[] queryRefPks(RefQueryInfo para) {
this.refQueryInfo = para;
PageInfo page = para.getPageInfo();
String[] pks = null;
// 判断是否进行全文检索
// 如果需要全文检索,通过 qryByFulltxt()方法生成基于关键字的查询结果。
if (this.getFulTxtFlg(para)) {
pks = this.qryByFulltxt(para);
}
// 非全文检索
else {
// 系统会根据分页信息来决定是查询所有主键(调用 queryAll()),
// 还是根据分页信息分段查询(调用 queryByPage())。
// 当前页码为-1时查全部数据
if (page.getPageIndex() == -1) {
pks = this.queryAll(para);
}
else {
// 根据分页信息分段查询
pks = this.queryByPage(para, page);
}
}
return pks; // 返回查询到的主键
}


public void setMaxRows(int maxRows) {
this.maxRows = maxRows; // 设置最大行数
}

// 生成查询主键的 SQL 语句,用于从数据库中获取符合条件的主键数据。
// 根据 RefMeta 中定义的表名和主键字段,生成基础 SQL 查询。
// 如果查询条件中有搜索主键(SearchPks),系统会根据主键来过滤。
// getWhereSql() 和 getOrderSql() 方法分别用于生成 WHERE 条件和 ORDER BY 排序。

// RefMeta(参照数据结构定义)和 RefQueryInfo(用户查询条件)
public String getQueryIDSql(RefQueryInfo para) {
this.refQueryInfo = para;
// 构建基础 SQL 查询
StringBuilder sb = new StringBuilder();
// this.refMeta.getPkField() 参照主键
// this.refMeta.getTableName() 参照表名
sb.append(" select ").append(this.refMeta.getPkField()).append(" from ").append(this.refMeta.getTableName());
sb.append(" where 1 = 1 ");
// 如果有搜索主键,添加过滤条件
if (para.getSearchPks() != null && para.getSearchPks().length > 0) {
sb.append(" and ").append(this.refMeta.getPkField()).append(" in (");
for (String pk : para.getSearchPks()) {
sb.append("'").append(pk).append("',");
}
sb.deleteCharAt(sb.length() - 1); // 删除最后一个逗号
sb.append(")");
}
// 生成 WHERE 条件和排序条件
sb.append(this.getWhereSql(para));
sb.append(this.getOrderSql(para));
return sb.toString(); // 返回生成的 SQL 查询语句
}



// 根据主键数组查询对应的参照数据行,并将数据封装为 RefRow[] 返回。
// 首先根据 pks 数组构建 SQL 查询,通过 IMultiNCGridRefRowService 查询数据库中的实际数据。
// 查询结果为 NCRefRow[] 格式,然后系统会将其转换为前端展示的 RefRow[] 格式,方便前端展示。
public RefRow[] getRowsByPks(String[] pks) {
// IMultiNCGridRefRowService:通过这个服务对象调用数据库.
// 执行根据主键过滤的 SQL 查询,并返回数据库中存储的参照数据行。
IMultiNCGridRefRowService service = ServiceLocator.find(IMultiNCGridRefRowService.class);
SqlParameterCollection collection = this.getSqlParameterCollection(this.refQueryInfo);

// 根据主键查询具体的数据行,RefRow 是适合前端展示的数据格式,包含了参照的code、name、pk等信息。
NCRefRow[] rows = service.getRowsByPksAndWheresql(this.getMeta(), pks, this.wheresql, collection);

// 将 NCRefRow 转换为 RefRow(供前端使用的数据格式)
RefRow[] ret = this.convert(rows);
return ret;
}

// 将查询结果封装为 RefQueryResult 对象,并处理分页信息,将其返回给调用者。
// 根据 RefRow[] 数据,计算分页信息并构建返回的结果。
// 通过 PageInfo 来设置分页总页数和当前页的信息。
public RefQueryResult constructResult(RefRow[] rows, RefQueryInfo para) {
// 创建查询结果对象
RefQueryResult result = new RefQueryResult();
// 设置查询结果中的行数据
result.setRows(rows);
// 获取查询参数中的分页信息
PageInfo page = para.getPageInfo();
// 计算总页数,确保至少有一页
// 根据总记录数 this.total 和每页记录数 page.getPageSize() 计算总页数
int totalPageNum =
(this.total + page.getPageSize() - 1) / page.getPageSize();
// 设置分页信息中的总页数
page.setTotalPage(totalPageNum);
// 设置查询结果中的分页信息
result.setPage(page);
// RefQueryResult 是最终返回给调用者的数据对象,它包含了查询的行数据(RefRow[])以及分页信息(PageInfo)。
return result;
}
 整个参照流程包括前端发送查询条件、后端接收和解析条件、生成 SQL 查询并执行数据库查询、数据权限和多语言处理、最终将结果返回前端并展示。在这一过程中,涉及多类协同工作,确保查询高效、灵活,同时满足权限和多语言需求。

具体流程

1. 前端请求阶段

用户在前端页面上操作,可能选择某个字段的参照功能(例如选择某个服务项目),页面会展示一个参照弹窗,允许用户根据关键字搜索、分页浏览或选择组织等条件过滤数据。用户输入的查询条件会通过 AJAX 请求或其他方式,发送到后端 API。

2. 后端接收请求并初始化查询

2.1 NCCAction.doAction() 作为操作入口

  • NCCAction 是后端接收请求的入口类,通过 doAction() 方法接收用户的查询请求。
  • doAction() 调用 getPara() 方法读取并解析前端传来的查询条件,将其封装为 RefQueryInfo 对象,包含查询关键字、分页信息、过滤条件(如组织、主键过滤)等。

2.2 执行 AbstractRefAction.execute() 处理查询条件

  • NCCAction 中调用 execute() 方法,此时进入 AbstractRefAction 类中实现的 execute() 方法。
  • execute() 方法负责解析 RefQueryInfo 中的查询条件:
    • 判断是否显示常用数据,如果是,则会附加 UsualRefSqlBuilder 的 SQL 构建逻辑。
    • 检查并设置 unitPks(组织主键),为参照查询增加组织过滤。
    • 初始化 PageInfo,用于分页显示数据。
  • 最终,execute() 方法调用 processData() 方法,正式进入参照数据的查询处理阶段。

3. 数据查询和处理阶段

3.1 DefaultGridRefAction.processData() 生成查询 SQL

  • processData() 方法首先会获取 SQL 构建器 IRefSqlBuilder,该对象生成参照功能的 SQL 语句。
  • processData() 负责生成查询 SQL,并调用数据库查询处理类 NCGridRefDBProcessor 执行查询。

3.2 NCGridRefDBProcessor.queryRefPks() 查询符合条件的主键

  • NCGridRefDBProcessor 中,queryRefPks() 方法会根据查询条件生成 SQL 语句:
    • 判断是否需要进行全文检索,如需要则调用 qryByFulltxt(),否则按页面分页信息决定调用 queryAll()queryByPage()
  • 执行查询后,queryRefPks() 返回符合条件的主键数组。

3.3 NCGridRefDBProcessor.getRowsByPks() 查询参照数据行

  • 根据 queryRefPks() 返回的主键数组,getRowsByPks() 会执行进一步的 SQL 查询,获取每条参照数据的完整行。
  • 结果以 NCRefRow[] 的格式返回,其中包含了每一行的编码、名称、主键等字段信息。
  • 系统会将 NCRefRow[] 转换为 RefRow[] 格式,这是前端更适合使用的数据格式。

3.4 NCGridRefDBProcessor.constructResult() 封装结果

  • constructResult() 方法将查询到的数据行 RefRow[] 封装为 RefQueryResult 对象。
  • 它还会处理分页信息,如计算总页数,设置 PageInfo 等,确保数据分页返回符合用户请求。

4. 数据权限、多语言处理阶段

processData() 中,数据行会进一步经过权限控制和多语言处理,确保符合用户权限的控制要求。processMultiLang() 会处理多语言转换,processRefPks() 则进行主键过滤和权限控制,确保只有符合权限的数据行被返回。

5. 返回结果给前端

  • 最终,execute() 返回 RefQueryResult 对象,doAction() 将该结果封装并返回给前端。
  • 前端接收 RefQueryResult 数据,该数据包含查询到的参照数据行和分页信息。

6. 前端展示结果

  • 前端接收到后端返回的参照数据,将其展示在参照弹窗中。用户可以浏览分页内容、查看参照的名称和编码等字段。
  • 用户可以进一步选择或确认某个参照值,从而完成整个参照功能的交互流程。