Commit 0c8e9bb6 by 郭旭

修改后端教案

parent 708c3cbc
## 后端项目代码调试
# 后端项目代码调试
## 后端项目代码调试
#### 项目开发调试
# 后端项目代码调试
## 项目开发调试
***视频教程***
http://114.115.138.98:9497/gjjs-book/assets/videos/rear-end-debug.mp4
##### 以执行一个rule为例,讲解前端请求到后端的跟踪debug过程,供大家参考,如下图:
以执行一个rule为例,讲解前端请求到后端的跟踪debug过程,供大家参考。
###### 前端请求
### 前端请求
前端信用证开立Exteky字段输入2000,然后回车,触发executeRule事件(“didgrp.apl.pts.extkey”)到后端
......@@ -14,37 +14,37 @@ http://114.115.138.98:9497/gjjs-book/assets/videos/rear-end-debug.mp4
![](../assets/images/hddebug1.png)
![](../assets/images/hddebug2.png)
###### Resource打断点
### Resource断点
后端AbstractCommonResource executeRule方法接收到请求
![](../assets/images/hddebug3.png)
###### Service打断点
### Service断点
resource executeRule代码执行到service层
![](../assets/images/hddebug5.png)
![](../assets/images/hddebug6.png)
###### Emitter打断点
### Emitter断点
service executeRule代码执行到emitter层处理,AbstractRuleEmitter缓存有每个rule所对应的执行方法,通过反射去找到具体对应emitter的相应方法,然后去执行该方法
![](../assets/images/hddebug7.png)
![](../assets/images/hddebug8.png)
![](../assets/images/hddebug12.png)
###### Rule层级打断点
### Rule层级断点
emitter作为rule的执行入口,里面按照顺序再去执行具体的业务逻辑代码
![](../assets/images/hddebug13.png)
###### 返回结果打断点
### 返回结果断点
rule执行完回到service层进行数据的赋值封装,返回给前端
![](../assets/images/hddebug14.png)
![](../assets/images/hddebug15.png)
##### 3、一个debug调试小技巧(drop frame)的使用
### debug调试技巧(drop frame)
在调试过程中,对于之前走过的代码想重新走一遍,不用把这次请求走完然后重新触发前端调用到后端再进行调试,这样会浪费时间影响效率,可以使用drop frame按钮,让断点回到上个断点位置重新步入。
......@@ -53,16 +53,247 @@ rule执行完回到service层进行数据的赋值封装,返回给前端
![](../assets/images/hddebugdrop1.png)
![](../assets/images/hddebugdrop2.png)
#### 测试验证
## 常见缺陷解决
##### 单元测试
### check隐藏字段
com.ceb.gjjs.mda.junitTest包下有单元测试类,可以测试service层接口方法。
check校验时,浏览器控制台上filederror中报隐藏/不存在的字段的check错误:
单元测试,测的是Service接口的方法,并没有像浏览器一样发起请求,所以是无法经过拦截器com.brilliance.mda.support.td.AuthInterceptor的,因此得事先将用户的信息添加到环境中;再就是参数输入,这里可以提前将之前在浏览器发起的请求数据保存为json文件,测试的时候就直接读取对应交易下的json文件的数据即可。
对比td和新国结,找到为什么不一样,先找到原因,一般情况为invisible/visible的问题
#### 出现原因
td代码中的invisible/visible面板,有使该面板上的所有字段置为disabled/enable的功能,但在新国结后端代码中未实现invisible/visible置对应字段为disabled/enable的功能,而校验时很多check方法的进入条件有判断字段是否为enable,当字段隐藏时td自动置字段为disabled,因check进入条件判断enable为假,不进入check此字段,而新国结上字段隐藏时未置字段为disabled,check进入条件判断为真,进入了check方法,导致校验时报隐藏字段的check错误。
![check](../assets/images/check.png)
#### 解决方案
在进入此check前加标志位判断,判断是否为xxx交易,且是否满足xx标志位条件,如:满足为gitopn交易,且开立方式为否时,不进入此check,否则按其原来的判断条件正常走check方法。
```java
//check了隐藏字段,加标志位条件。如gitopn开立方式为否时check了此隐藏字段,进入check方法的条件再加判断交易为gitopn时,且开立方式不为否时进入此check,否则按其原来的判断条件走此check
```
![check2](../assets/images/check2.png)
代码中 ctx.getTransName().equals("GITOPN") 可替换为 MdaUtils.compareTo(ctx.getTransName(),"GITOPN") == 0
验证代码逻辑正确后,在bdproject项目中对应的script文件中修改代码,重新编译生成后端代码,检查生成的新代码是否正确:
bdproject代码:
![check3](../assets/images/check3.png)
### 必输项
#### 1、金额输入框有值,进入交易后仍报红
前端:在此交易的pattern.js中找到该字段的type: "number"改为 "string";
(注意:金额输入框参照前端界面组件规范中使用的标签c-input-currency/c-input-currency-min,标签自动限定了输入数字,且保留两位小数,注意pattern的修改)
#### 2、天数输入框有值,进入交易后仍报红
前端:在此交易的pattern.js中找到该字段的type: "string"改为 "number",max为最大值;
示例:gitcrq交易的pattern.js
```html
"payday":[ "payday":[
{type: "string", required: false, message: "必输项"}, 改为 {type: "number", required: false, message: "请输入数字"},
{max: 2,message:"长度不能超过2"} {type: "number", max: 99,message:"不能超过99"}
], ],
```
前端页面该字段处v-model改为v-model.number
(**注意:**当字段有自动触发默认方法时,在浏览器控制台检查下更改后是否仍能触发默认方法或在后端代码打断点检查)
```html
<c-col :span="24">
<c-col :span="12">
<el-form-item label="我行将于" prop="payday">
<c-input v-model.number="model.payday" placeholder=""> </c-input>
</el-form-item>
</c-col>
<c-col :span="12">
<el-form-item label="个工作日后付款" label-width="120px">
</el-form-item>
</c-col>
</c-col>
```
### 字数超过限制
#### 输入框字数限制
1、在TD上查看该输入框字数限制,在前端标签中调整maxlength长度限制,或pattern.js中该字段的长度限制(二选一即可,如两者都加,注意对应)
![debug1](../assets/images/debug1.png)
![debug2](../assets/images/debug2.png)
#### 文本框字数限制
名称地址栏等多行文本框中英文字数限制问题:
使用提供的4x35这种格式的基础控件<c-mul-row-input>,支持过滤纯英文,swift、swiftz字符集,默认中文算两个长度,
达到每行规定输入的字符数后会自动换行。
*传参:*
`:rows`:可输入行数;
`:cols`:每行最多字符数,中文占两个字符,空格及标点符号占一个字符;
示例:
```
<c-col :span="24">
<el-form-item label="地址信息" prop="cpdgrp.orc.pts.adrblk">
<c-mul-row-input
v-model="model.cpdgrp.orc.pts.adrblk"
:rows="4"
:cols="35"
placeholder="请输入地址信息"
></c-mul-row-input>
</el-form-item>
</c-col>
```
### 输入框是否可编辑(灰显)
1、在TD上查看对应的输入框
![disabled1](../assets/images/disabled1.png)
2、在前端对应输入框加上disabled属性
![disabled2](../assets/images/disabled2.png)
### check检验不通过
#### check检验无提示
通不过,在浏览器控制台查看哪些字段check未通过,在该字段的前端页面用el-form-item标签包起来,且检查是否有prop.
![check4](../assets/images/check4.png)
![check5](../assets/images/check5.png)
#### check隐藏的字段
td上找到该字段的check方法
![check6](../assets/images/check6.png)
![check7](../assets/images/check7.png)
![check8](../assets/images/check8.png)
在TD及新国结进入此check方法的入口条件处打断点,新国结与TD都触发,走到断点处,对比新国结和TD不同之处,为什么新国结进入了此check方法,常见有invisable在TD上有置不可见的字段为disabled功能,而新国结上此功能未实现。
### 金额输入框
1、金额输入框只能输入非负数且靠右显示,保留两位小数:前端标签用`c-input-currency`
若要靠左显示,加`class="input-currency-left"`
示例:(`maxlength="xx"`可根据Td上的长度限制自己调整,或可在pattern.js中调整max,二选一即可,如两者都加,注意对应)
```html
<c-col :span="24">
<el-form-item label="承兑金额" prop="cnybop.fexchangeamt">
<c-input-currency
v-model="model.cnybop.fexchangeamt"
maxlength="20"
placeholder="请输入承兑金额"
></c-input-currency>
</el-form-item>
</c-col>
```
(因`c-input-currency`标签限定了非负及保留两位小数,故在pattern.js中此字段
```html
{type: "string", required: false, message: "必输项"},
{max: 18,message:"整数位不能超过14位"},
{pattern: /(^\d+$)|(^\.\d{1,3}$)|(^\d+\.\d{1,3}$)/, message: "小数位不能超过3位" }`
```
应调为整:
```html
{type: "string", required: false, message: "必输项"},
{max: 17,message:"整数位不能超过14位"}`
```
2、金额输入框可输入正负及0且靠右显示,保留两位小数:前端标签用`c-input-currency-min`
若要靠左显示,加`class="input-currency-left"`
3、金额输入框失去焦点会自动触发默认default方法,想要其回车时也触发:
若直接在前端页面该字段手动加回车触发default,当回车后会触发,但失去焦点后又会重复触发,为避免重复触发,可用
`@keyup.enter.native="$event.target.blur()"` :回车触发失去焦点发生的事件(若失去焦点手动触发其他事件,可再加`@blur="xxxxxxx"`)
示例:
```html
<c-col :span="12">
<c-form-item previewLabel="信用证金额" style="text-align: left" label-width="5px" prop="gcdgrp.cbs.max.amt">
<c-input-currency v-model="model.gcdgrp.cbs.max.amt" placeholder="请输入信用证金额" @keyup.enter.native="
defaultFunction('gcdgrp.cbs.max.amt', model.gcdgrp.cbs.max.amt)
"></c-input-currency>
</c-form-item>
</c-col>
上面加了回车手动触发自己字段的默认default方法,当失去焦点后会重复出发。改为使用@keyup.enter.native="$event.target.blur()",如下:
<c-col :span="12">
<c-form-item previewLabel="信用证金额" style="text-align: left" label-width="5px" prop="gcdgrp.cbs.max.amt">
<c-input-currency v-model="model.gcdgrp.cbs.max.amt" placeholder="请输入信用证金额" @keyup.enter.native="$event.target.blur()"></c-input-currency>
<!-- @keyup.enter.native="$event.target.blur()" :回车触发失去焦点发生的事件 -->
</c-form-item>
</c-col>
```
4、金额修改时输入一个数后,自动补齐小数点,且光标立即跳到输入框最后面,输入不正常:
一般为同一个交易中,同一页面或不同页面有两个一样的此金额字段,一个能输入修改,另一个为disabled,两个都用了`c-input-currency``c-input-currency-min`标签
将disabled的那个金额输入框改用`c-input`标签,加`class="m-input-currency"`,如:
```html
<c-col :span="9">
<c-form-item previewLabel="信用证金额" style="text-align: left" label-width="5px" prop="gcdgrp.cbs.max.amt">
<c-input disabled v-model="model.gcdgrp.cbs.max.amt" placeholder="请输入" class="m-input-currency"></c-input>
</c-form-item>
</c-col>
```
### 数据丢失
#### 交易数据丢失
新国结进入交易后,切换page页,进入交易携带的数据丢失:在index.vue中加name值
![进入交易后缓存数据](../assets/images/进入交易后缓存数据.png)
#### 输入框数据丢失
检查控制台该字段是否有值
![dataloss1](../assets/images/dataloss1.png)
若是前端未将后端返回的数据更新到页面上:检查方法是否缺少updateModel方法;检查前端该字段是否写错,该交易的index.js中是否正确定义了该字段
![dataloss2](../assets/images/dataloss2.png)
若是控制台上后端返回的该字段的数据不正确,检查后端VO,前端页面及index.js该字段是否写错;对照TD找到该字段的值是什么时机出现的,找到触发的方法,
哪句代码给该字段赋值,在新国结后端代码上找到对应代码,打上断点,debug模式下,触发该方法,对照TD看新国结后端代码该处哪个数据和TD对不上,往回找值的源头,
为什么对不上,找到新国结上哪行代码,哪个方法出了问题,然后解决,改正后,验证是否解决问题。
**注意:**由转换工具生成且会覆盖的代码修改时,在bdproject中找到对应的script文件,在script文件中修改,具体参考:
* 修改代码位置参考
![修改代码位置参考](../assets/images/修改代码位置参考.png)
* TD上定位该字段值什么时候赋值,哪行代码赋的值:
![dataloss3](../assets/images/dataloss3.png)
![dataloss4](../assets/images/dataloss4.png)
![dataloss5](../assets/images/dataloss5.png)
![dataloss6](../assets/images/dataloss6.png)
![dataloss7](../assets/images/dataloss7.png)
![dataloss8](../assets/images/dataloss8.png)
![dataloss9](../assets/images/dataloss9.png)
![dataloss10](../assets/images/dataloss10.png)
* 后端debug模式下调试代码
![dataloss11](../assets/images/dataloss11.png)
![单元测试_BeforeEach](../assets/images/单元测试_BeforeEach.png)
测试用例:
![单元测试用例](../assets/images/单元测试用例.png)
# 后端项目整体介绍
# 后端项目整体介绍
......@@ -277,3 +277,241 @@ if (rtnmsg.respCode == SUCCESS) {
​ 2.请求为executeDefault,则作为每个executeDefault方法的最后一个步骤被执行
​ 3.请求为executeRule,则作为每个executeRule方法中invokeExpress产生的队列被执行前的前一个步骤执行
## 核心类介绍
### 1、DCR说明
#### (1)默认说明
​ DCR开关用于控制set方法是否会触发相应的defaultRule。DCR开关默认情况下为关闭状态,
#### (2)开关状态
​ 1.Init结束后,DCR开关打开
​ 2.Vo向模型赋值时将DCR开关打开
​ 3.执行executeDefault时,若DCR开关为打开状态,则需要将开关临时关闭。结束后将开关恢复成执行前状态
​ 4.进行数据库查询操作时,若DCR开关为打开状态,则需要将开关临时关闭。结束后将开关恢复成执行前状态
​ 5.调用copyValues方法实现模型深拷贝时,若DCR开关为打开状态,则需要将开关临时关闭。结束后将开关恢复成执行前状态
​ 6.调用ObjectMapper.readValue方法实现json字符串转Java对象时,若DCR开关为打开状态,则需要将开关临时关闭。结束后将开关恢复成执行前状态
### 2、MdaDriver说明
| 函数 | 描述 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| IBaseObject getBaseObject(IModule root, String target, boolean helpLoad) | 通过路径获取上下文中的IBaseObject 对象 |
| IBaseObject getBaseObject(IModule root,String target) | 通过路径获取上下文中的IBaseObject 对象 |
| DatafieldImpl getDatafield(IModule root, String target) | 通过路径获取上下文中的DatafieldImpl 对象 |
| DatafieldImpl getDatafield(String target) | 通过路径获取上下文中的DatafieldImpl 对象 |
| IModule getModule(IModule root,String target) | 通过路径获取上下文中的IModule 对象 |
| IModule getModule(String target) | 通过路径获取上下文中的IModule 对象 |
| T getModule(String target,Class<T> clazz) | 通过路径获取上下文中的clazz对象 |
| IModuleList getModuleList(IModule root,String target) | 通过路径获取上下文中的IModuleList 对象 |
| IModuleList getModuleList(String target) | 通过路径获取上下文中的IModuleList 对象 |
| void copyValues(Object target,Object src) | 深拷贝src 到target |
| void clearModule(IModule module) | 清空模型信息 |
| Object zero(Class<?> clazz) | 清空基本类型和BigDecimal |
| String getModuleType(IModule m) | 获取模型类型 |
| void setFieldValue(Field field,Object value) | 空方法 |
| void setValueFromVO(IContext ctx,T vo) | 将VO中的值设置到上下文 |
| setValueFromVoByClass(IModule root,Object vo,Class clazz,String parent) | 根据class将VO的值赋予IModule |
| void setValueFromVO(IModule root,Object vo,String parent) | 将VO中的值赋予IModule |
| Field getParentField(Object parent,String fieldstr) | 获取parent字段中的fieldstr字段 |
| List<String> getSubModuleTypes(IModule module) | 获取模型中所有属于自己子类的类型 |
| setFieldValue(Field from,Object fromObj,Field to,Object toObj, boolean voToMod) | 将对象字段设置到对应的字段中,true 表示从VO到ctx(写入上下文) |
| T getFieldValue(Object o,String fieldName) | 获取对应字段的值 |
| void setValueToVO(IContext ctx,T vo) | 将ctx内容写入到VO |
| void setValueToVoByClass(IModule root,Object vo,Class clazz,String parent) | 根据class将IModule的值赋予VO |
| void setValueToVO(IModule root,Object vo,String parent) | 将IModule的值赋予VO |
| Map<String,T> translateMapByVoAnotation(String transKey, boolean pathToAlias, Map<String,T> map) | 将map中所有键转化为别名 |
| String getPathAlias(String trnnam,String path) | 获取路径别名 |
| String getAliasPath(String trnnam,String aliasName) | 根据别名获取路径 |
| void createVoSetterAndGetter(Set<Class<?>> clsSet) | 根据交易上的注解,生成别名与长路径的映射 |
| void processTransAnotion(Class<?> trnCls) | 根据交易上的注解,生成别名与长路径的映射 |
| void processTransAnotion(Class<?> trnCls,Class<?> voCls,String modPath,String voPath) | 根据交易上的注解,生成别名与长路径的映射 |
| boolean isFieldPathExist(Class<?> trnCls,String path) | 空方法 |
| String getTransName(Class<?> trnCls) | 获取交易名 |
| String firstUpper(String s) | 将首字母大写 |
| Map<String,String> getErrorMap(IContext ctx,Class voClazz) | 获取错误信息map |
| String getModuleInfo(IModule module, int getterType) | 获取模型信息 |
| void getModuleInfoStream(IStream out,IModule module,int getterType) | 将模型信息写入IStream |
| void getModuleInfoStream(IStream infostream, IModule mod, int type, String match) | 将模型信息写入IStream |
| void getModuleInfoStreamMethodWalk(IStream infostream, Class moduleClass,String prefixPath,String match) | 将模型信息写入IStream |
### 3、MdaEnv说明
| 函数 | 描述 |
| -------------------------------------------------- | -------------------------- |
| T getBean(String beanId,Class<T> clazz) | 根据类型和名称获取对应Bean |
| T getBean(Class<T> clazz) | 根据类型获取Bean |
| Object getBean(String beanId) | 根据名称获取Bean |
| void setContext(IContext ctx) | 设置上下文 |
| IContext getContext() | 获取上下文 |
| void rmContext() | 删除上下文 |
| void clearContext() | 清空所有上下文和认证信息 |
| void clear(Object object) | 清空对象 |
| boolean isEmpty(Object obj) | 判空 |
| void setAuthInfo(IAuthInfo authInfo) | 设置认证信息 |
| IAuthInfo getAuthInfo() | 获取认证信息 |
| String getLoginUser() | 获取登录用户信息 |
| String getEncoding() | 获取编码格式 |
| void setEncoding(String encoding) | 设置编码格式 |
| void setLang(String lang) | 设置语言 |
| String getLang() | 获取语言 |
| T getSessionContextValue(String key) | 获取用户上下文 |
| void setSessionContext(String key , Object... obj) | 设置用户会话上下文 |
| void clearSessionContext(String uid) | 清空指定用户会话上下文 |
| String getRootPath() | 获取根路径 |
| void removeAuthInfo() | 移除认证信息 |
| void removeAttrs() | 移除所有属性 |
| void setAttribute(String key,Object obj) | 设置属性 |
| Object getAttribute(String key) | 获取对应属性值 |
| String getSessionId() | 获取会话id |
### 4、自定义注解使用说明
#### Init
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Init {
int order() default 100;
}
```
##### 使用说明
@Init 用于修饰模块中的方法,表明该方法执行于初始化,order表示初始化的顺序。
#### Check
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
int order() default 100;
String target() default "";
String[] value() default {};
}
```
##### 使用说明
@Check 用于修饰模块中的方法,表明该方法执行于校验阶段,验证修改的字段属性是否合法,order表示校验的顺序,target、value表示验证的属性,
#### Default
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Default {
int order() default 100;
String target() default "";
String[] value() default {};
}
```
##### 使用说明
@Default 用于修饰模块中的方法,表明该方法执行于默认规则阶段,模块指定的字段值发生改变时会触发,order表示执行的顺序,target、value表示指定的属性,
#### Rule
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Rule {
String target() default "";
int order() default 100;
String[] value() default {};
}
```
##### 使用说明
@Rule 用于修饰模块中的方法,表明该方法执行于触发事件阶段,order表示执行的顺序,target、value表示执行的的属性。
#### RelPath
##### 定义
```java
@Documented
@Retention(RUNTIME)
@Target({TYPE,FIELD})
public @interface RelPath {
String value() default ""; //路径
DirType dir() default DirType.BOTH ; //输入输出方向
boolean recursion() default false; //是否递归关联
}
```
##### 使用说明
@RelPath 用于修饰VO中的字段,value表明该字段对应交易模块下的属性,dir声明该字段是否能被读取或是赋值。
#### TDMethod
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TDMethod {
boolean value() default true;
}
```
##### 使用说明
@TDMethod 用于修饰模块中的方法,表明该方法是TD的上Method
#### TDStatic
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TDStatic {
boolean value() default true;
}
```
##### 使用说明
@TDStatic 用于修饰模块中的方法,表明该方法是TD的上Static
#### Transaction
##### 定义
```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Transaction {
String value() default "";
Class<?> vo() default EmptyVO.class;
}
```
##### 使用说明
@Transaction 修饰于模块类上,表明该类是个交易类
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment