NAME: cherryc
DATE: 2017/11/20
目前做Gxp项目,客户需要审计日志模块,即对用户的操作进行记录,主要是增删改操作。
由于框架用的是Spring+SpringMVC+Mybatis,其中Mybatis中的拦截器可以选择在被拦截的方法前后执行自己的逻辑。所以我们通过拦截器实现了审计功能,当用户对某个实体类进行增删改操作时,拦截器可以拦截,然后将操作的数据记录在审计表中,便于用户以后审计。
拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。mybatis拦截器一般用于分页插件、输出日志、sql等。
对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
这个接口中共定义了三个方法:intercept、plugin、setProperties。
intercept:进行拦截的时候要执行的方法。
plugin:用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回它的一个代理。当返回的是代理的时候,我们可以对其中的方法进行拦截来调用intercept方法,或者调用其他方法。
我们可以通过实现该接口来定义自己的拦截器,处理自己想要的逻辑。但是实现自己的interceptor有两个很重要的注解,@Intercepts和@Signature。下边会详细介绍这两个注解:
1)@Intercepts 在实现Interceptor接口的类声明,使该类注册成为拦截器
2)@Signature则表明要拦截的接口、方法以及对应的参数类型
@Intercepts( {
@Signature(
type = Executor.class,
method = "query",
args = {
MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class
}
)
})
type:表示拦截的接口类型,这里是Executor的实现类。
Mybatis拦截器只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。这是在Mybatis的Configuration中写死了的,如果要支持拦截其他接口就需要我们重写Mybatis的Configuration。Mybatis可以对这四个接口中所有的方法进行拦截。
method:表示拦截的方法,这里是拦截Executor的query方法。若为method = “update”,则拦截Executor的insert、update、delete方法。
args:表示拦截的参数类型,有MappedStatement、Object、RowBounds和ResultHandler。。。
以上拦截器将拦截Executor类中参数类型MappedStatement、Object、RowBounds、ResultHandler的查询方法。并且接口中可以定义多个@Signature注解。
注册拦截器是通过在Mybatis配置文件中plugins元素下的plugin元素来进行的。一个plugin对应着一个拦截器,在plugin元素下面我们可以指定若干个property子元素。Mybatis在注册定义的拦截器时会先把对应拦截器下面的所有property通过Interceptor的setProperties方法注入给对应的拦截器。
<plugins>
<plugin interceptor="com.demo.mybatis.interceptor.MyInterceptor">
<property name="prop1" value="prop1"/>
<property name="prop2" value="prop2"/>
plugin>
plugins>
如下是审计拦截器AuditInterceptor,注意拦截Exceptor接口中的增删改方法。
通过判断实体类dto是否有@AuditEnabled注解来决定是否拦截并将拦截的数据存入审计表中。其中@AuditEnabled是自定义注解。
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class AuditInterceptor implements Interceptor {
。。。。。。
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object param = invocation.getArgs()[1];
Object targetDto = null;
if (param instanceof Map) {
Map temp = (Map) param;
for (Map.Entry entry : temp.entrySet()) {
if (entry.getKey().equals("dto")) {
targetDto = temp.get("dto");
break;
}
}
} else {
targetDto = param;
}
if (null == targetDto || !(targetDto instanceof BaseDTO)) {
return invocation.proceed();
}
// only enable for @AuditEnabled
if (targetDto.getClass().getAnnotation(AuditEnabled.class) == null) {
return invocation.proceed();
}
BaseDTO dto = (BaseDTO) targetDto;
Object result;
SqlCommandType type = mappedStatement.getSqlCommandType();
switch (type) {
case INSERT:
case UPDATE:
result = invocation.proceed();
doAudit(dto, type.name(), mappedStatement, param, invocation);
break;
case DELETE:
doAudit(dto, type.name(), mappedStatement, param, invocation);
result = invocation.proceed();
break;
default:
result = invocation.proceed();
break;
}
return result;
}
。。。。。
}
参数invocation包含了操作的方法类型method。如下表示对数据库的增删改操作。
还包含了参数值args,args[0] 表示拦截的操作是insert 可以通过SqlCommandType获得
SqlCommandType type = mappedStatement.getSqlCommandType();
args[1] 获得拦截的操作数据,封装在dto对象中。
Object param = invocation.getArgs()[1]; 得到拦截的数据,如上图,该数据是封装在一个dto对象中。
然后判断dto是否继承了HAP封装好的BaseDTO对象,若没有继承则不拦截,继续执行之前的逻辑。
若继承了BaseDTO,再通过getAnnotation() 判断是否在dto上添加了@AuditEnabled注解,只有添加注解并且继承BaseDTO的实体类增删改操作才会被拦截。
if (null == targetDto || !(targetDto instanceof BaseDTO)) {
return invocation.proceed();
}
// only enable for @AuditEnabled
if (targetDto.getClass().getAnnotation(AuditEnabled.class) == null) {
return invocation.proceed();
}
DTO:GxpMqDiagnosisScope
@ExtensionAttribute(disable=true)
@Table(name = "cux_gxp_mq_diagnosis_scope")
@AuditEnabled
public class GxpMqDiagnosisScope extends BaseDTO {
@NotNull
@Where
private Long basicId;
@NotEmpty
@OrderBy("ASC")
private String categoryCode;
。。。。。
}
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class
然后拦截增删改操作方法,调用doAudit方法执行逻辑判断并将拦截的数据插入到数据库审计表中。
private void doAudit(BaseDTO dto, String type, MappedStatement mappedStatement, Object param, Invocation invocation) throws Exception {
Class clazz = dto.getClass();
Table tbl = checkTable(clazz);
if (tbl == null) {
return;
}
String tableName = tbl.name();
EntityField[] ids = DTOClassInfo.getIdFields(clazz);
if (ids.length == 0) {
if (logger.isDebugEnabled()) {
logger.debug("Can not get PrimaryKey(s) of dto class:" + clazz);
}
return;
}
generateAndSetAuditSessionId();
String asid = LOCAL_AUDIT_SESSION.get();
// if (asid == null) {,
// if (logger.isDebugEnabled()) {
// logger.debug("audit session is null, skip.");
// }
// return;
// }
AuditEnabled auditEnabled = (AuditEnabled) clazz.getAnnotation(AuditEnabled.class);
String auditTableName = StringUtils.defaultIfEmpty(auditEnabled.auditTable(),
auditTableNameProvider.getAuditTableName(tableName));
boolean hasExtensionAttribute = true;
if (clazz.isAnnotationPresent(ExtensionAttribute.class)) {
ExtensionAttribute extensionAttribute = (ExtensionAttribute) clazz.getAnnotation(ExtensionAttribute.class);
if (extensionAttribute.disable()) {
hasExtensionAttribute = false;
} else {
hasExtensionAttribute = true;
}
}
List cols = new ArrayList<>();
for (Map.Entry entry : DTOClassInfo.getEntityFields(clazz).entrySet()) {
if (!entry.getValue().isAnnotationPresent(Transient.class)) {
if (entry.getValue().getName().startsWith("attribute") && !hasExtensionAttribute) {
continue;
} else {
cols.add(DTOClassInfo.getColumnName(entry.getValue()));
}
}
}
EntityField[] entityFields = DTOClassInfo.getMultiLanguageFields(clazz);
// 判别是否存在 多语言表 判别是否使用多语言注解
MultiLanguage multiLanguageTable = (MultiLanguage) clazz.getAnnotation(MultiLanguage.class);
// 获取当前语言
IRequest request = RequestHelper.getCurrentRequest(true);
String localLanguage = request.getLocale();
// 多语言字段 前用别名
List dynFields = new ArrayList<>();
boolean isTrue = false;
for (int i = 0, j = cols.size(); i < j; i++) {
String s = cols.get(i);
isTrue = false;
if (null != multiLanguageTable) {
for (EntityField entityField : entityFields) {
if (DTOClassInfo.camelToUnderLine(entityField.getName()).toLowerCase().equals(s.toLowerCase())) {
isTrue = true;
s = "TL." + s;
break;
}
}
}
dynFields.add(isTrue ? s : "B." + s);
}
Map auditParam = new HashMap<>();
String majorField = "";
// 获取主键
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (null != f.getAnnotation(Id.class)) {
majorField = DTOClassInfo.camelToUnderLine(f.getName());
break;
}
}
//获取where_clause表达式及占位符实参
Map wps = getWhereClause(mappedStatement, param, tableName);
for (Map.Entry entry : wps.entrySet()) {
auditParam.put(entry.getKey(), entry.getValue());
}
String tableTlName = tableNameProvider.getTlTableName(tableName);
auditParam.put("_AUDIT_TABLE_NAME", auditTableName);
auditParam.put("_COLS", cols);
auditParam.put("_AUDIT_TYPE", type);
auditParam.put("_AUDIT_SESSION_ID", asid);
auditParam.put("_AUDIT_ID", IDGenerator.getInstance().generate());
auditParam.put("_MAJOR_FIELD", majorField);
auditParam.put("_DYN_FIELDS", dynFields);
auditParam.put("_BASE_TABLE_NAME", tableName);
auditParam.put("_TABLE_NAME", tableTlName);
String clause = wps.get("WHERE_CLAUSE").toString();
auditParam.put("_WHERE_CLAUSE", clause);
// 获取request语言列表
Set languageCodes = new HashSet<>();
Map> map = dto.get__tls();
for (Map.Entry entry : map.entrySet()) {
Map m = (Map) entry.getValue();
for (Map.Entry item : m.entrySet()) {
languageCodes.add(item.getKey());
}
}
Map updateParam = new HashMap<>();
updateParam.put("_TABLE_NAME", tableName + "_a");
updateParam.put("_MAJOR_FIELD", majorField);
String id = auditParam.get(majorField.replace("_", "")).toString();
updateParam.put("_ID", id);
List languages = languageProvider.getSupportedLanguages();
if ( null != multiLanguageTable) {
for (Language language : languages) {
auditParam.put("_AUDIT_ID", IDGenerator.getInstance().generate());
auditParam.put("_IS_MULTI_LANGUAGE", multiLanguageTable != null);
auditParam.put("_LANGUAGE", language.getLangCode());
updateParam.put("_LANG", language.getLangCode());
doDB(auditParam, updateParam);
}
} else {
auditParam.put("_LANGUAGE", localLanguage);
auditParam.put("_IS_MULTI_LANGUAGE", multiLanguageTable != null);
updateParam.put("_LANG", localLanguage);
doDB(auditParam, updateParam);
}
}
获得审计表中每个字段的值。其中被审计表的主键”_MAJOR_FIELD”是通过
EntityField[] ids = DTOClassInfo.getIdFields(clazz);获得,基表主键可能有多个字段,所以通过数组来接收。
auditParam.put("_AUDIT_TABLE_NAME", auditTableName);
auditParam.put("_COLS", cols);
auditParam.put("_AUDIT_TYPE", type);
auditParam.put("_AUDIT_SESSION_ID", asid);
auditParam.put("_AUDIT_ID", IDGenerator.getInstance().generate());
auditParam.put("_MAJOR_FIELD", majorField);
auditParam.put("_DYN_FIELDS", dynFields);
auditParam.put("_BASE_TABLE_NAME", tableName);
auditParam.put("_TABLE_NAME", tableTlName);
String clause = wps.get("WHERE_CLAUSE").toString();
auditParam.put("_WHERE_CLAUSE", clause);
准备好审计表需要存入的数据之后,调用doDB方法存入数据库审计表。doDB(auditParam, updateParam);
private void doDB(Map insertMap, Map updateMap) throws SQLException {
try {
AuditMapper mapper = getTemplateMapper();
// 将其他审计记录标记 AUDIT_TAG设为0
int count2 = mapper.auditUpdateTag(updateMap);
int count = mapper.auditInsert(insertMap);
if (count == 1 && count2 >= 1) {
if (logger.isDebugEnabled()) {
logger.debug("audit result:1, normal.");
}
} else if (logger.isDebugEnabled()) {
logger.debug("audit result:{}, abnormal.", count);
}
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error("audit error:" + e.getMessage());
}
throw e;
}
}
调用mapper.xml文件更新或者插入数据。数据库中所有的历史纪录中最新的纪录为AUDIT_TAG=1,非最新为0。
所以插入数据之前需要先把之前的最新记录设置为0,代码如下:
<update id="auditUpdateTag" parameterType="java.util.Map">
UPDATE ${_TABLE_NAME} SET AUDIT_TAG = 0 WHERE ${_MAJOR_FIELD} = #{_ID} AND LANG = #{_LANG}
update>
然后插入数据。
<update id="auditInsert" parameterType="java.util.Map">
INSERT INTO ${_AUDIT_TABLE_NAME}
(AUDIT_ID,AUDIT_TRANSACTION_TYPE,AUDIT_TIMESTAMP,AUDIT_SESSION_ID, LANG, MAJOR_FIELD, AUDIT_TAG,
"_COLS" index="i" item="value" separator=",">
${value}
)
SELECT #{_AUDIT_ID},#{_AUDIT_TYPE},CURRENT_TIMESTAMP,#{_AUDIT_SESSION_ID},
#{_LANGUAGE}, #{_MAJOR_FIELD}, 1,
"_DYN_FIELDS" index="i" item="value" separator=",">
${value}
FROM ${_BASE_TABLE_NAME} B
<if test="_IS_MULTI_LANGUAGE">
, ${_TABLE_NAME} TL
WHERE ${_WHERE_CLAUSE}
AND TL.${_MAJOR_FIELD} = B.${_MAJOR_FIELD}
AND TL.LANG = #{_LANGUAGE}
if>
<if test="!_IS_MULTI_LANGUAGE">
WHERE ${_WHERE_CLAUSE}
if>
update>
至此,拦截的数据已经全部插入数据库中。
根据以上介绍的审计实现功能,我们在HAP框架中使用审计时需要做以下操作。以物料资质行信息为例:
a.审计表的名称模板:基表名_a
b.审计表字段:在基表的基础上添加7个字段。
audit_id VARCHAR2(80) not null,
audit_transaction_type VARCHAR2(10),
audit_timestamp DATE,
audit_session_id VARCHAR2(64),
lang VARCHAR2(20),
major_field VARCHAR2(20),
audit_tag NUMBER(1)
c. 基表主键类型不能为字符,需要设置为数字类型 number
@ExtensionAttribute(disable=true)
@AuditEnabled
@Table(name = "cux_gxp_mq_info")
public class GxpMqInfo extends BaseDTO {。。。。}
a. 注入实现类@Qualifier(“gxpMqInfoAServiceImpl”)。 注意实现类得第一个字符小写。
b. gxpMqInfoAService.selectAuditRecord(requestContext,dto, page, pageSize)
若查询条件不为空,则传入dto,否则传null。
c. 两个方法查询快照( selectAuditRecord ) 和 查询历史纪录( selectAuditRecordDetail ) 都要传入实体类作为参数 如:GxpMqInfoA dto。
d. 使用@PostMapping
@Controller
public class GxpMqInfoAController extends BaseController{
@Autowired
private IAuditService auditService;
@Autowired
@Qualifier("gxpMqInfoAServiceImpl")
private IAuditDTOService gxpMqInfoAService;
/**
*物料资质行审计查询
* @param dto
* @param page
* @param pageSize
* @param request
* @return
*/
@PostMapping(value = "/cux/gxp/mq/info/a/query/{basicId}")
@ResponseBody
public ResponseData query(GxpMqInfoA dto,
@RequestParam(defaultValue = DEFAULT_PAGE) int page,
@RequestParam(defaultValue = DEFAULT_PAGE_SIZE) int pageSize,
HttpServletRequest request,
@PathVariable int basicId) {
IRequest requestContext = createRequestContext(request);
//设置资质头Id
dto.setBasicId((long) basicId);
return new ResponseData(gxpMqInfoAService.selectAuditRecord(requestContext, dto, page, pageSize));
}
/**
*物料资质行历史记录查询
* @param dto
* @param request
* @param page
* @param pageSize
* @param infoId
* @return
*/
@PostMapping("/cux/gxp/mq/info/a/detail/{infoId}")
@ResponseBody
public ResponseData queryDetail(GxpMqInfoA dto,HttpServletRequest request,
@RequestParam(defaultValue = DEFAULT_PAGE) int page,
@RequestParam(defaultValue = DEFAULT_PAGE_SIZE) int pageSize,
@PathVariable int infoId){
IRequest requestContext = createRequestContext(request);
//根据主键id 查询历史纪录
dto.setInfoId((long) infoId);
return new ResponseData(gxpMqInfoAService.selectAuditRecordDetail(requestContext, dto, page, pageSize));
}
}
selectAuditRecord:调用工具类AuditRecordUtils的operateAuditRecord方法,查询审计表快照,即最新的记录。
operateAuditRecordSingleDetail:调用工具类的方法,查询审计表历史纪录,并且依次判断比上一条记录都修改了哪些字段,在修改的字段后加上‘ & ’符号。
@Service
@Transactional(rollbackFor = Exception.class)
public class GxpMqInfoAServiceImpl implements IAuditDTOService{
@Autowired
private GxpMqInfoAMapper gxpMqInfoAMapper;
@Override
public List
public interface GxpMqInfoAMapper extends Mapper<GxpMqInfoA>{
List
a. 查询出来的数据放在map中。resultType=”java.util.Map”
b. 必须加上ORDER BY cgmi.AUDIT_TIMESTAMP DESC
c. 必须查询出Audit_id(审计表主键) 和 info_id(基表主键) 否则回报空指针异常。
select
(select case when count(0) = 0 then 'Y' else 'N' end
from cux_gxp_mq_info cgm where cgm.info_id = cgmi.info_id) delete_flag,
cgmi.audit_id,
cgmi.audit_timestamp,
cgmi.audit_transaction_type,
cgmi.lang,
cgmi.major_field,
cgmi.object_version_number,
e.name as operator_name,
sy.user_name as operator_user,
cgmi.info_id,
qualif_tpye,
qualif_name,
certificate_no,
start_date,
end_date,
cgmi.effective_end_date,
cgmi.warning_lead_time ,
cgmi.modify_reason,
cgmi.remark,
cgmi.product_name
from cux_gxp_mq_info_a cgmi
JOIN sys_user sy ON cgmi.last_updated_by = sy.user_id
JOIN hr_employee e ON sy.employee_id = e.employee_id
sql>
<select id="selectMqInfoA" parameterType="java.lang.Integer" resultType="java.util.Map">
"MqInfoAPart">
where cgmi.audit_tag = 1
<if test="_parameter!=0">
and cgmi.basic_id = #{_parameter}
if>
ORDER BY
cgmi.AUDIT_TIMESTAMP DESC
select>
<select id="selectMqInfoADetail" parameterType="java.lang.Integer" resultType="java.util.Map">
"MqInfoAPart">
<where>
<if test="_parameter!=0">
and cgmi.info_id = #{_parameter}
if>
where>
ORDER BY
cgmi.AUDIT_TIMESTAMP DESC
select>
dataSource1 = new kendo.data.DataSource({
transport: {
read: {
url: BaseUrl + "/cux/gxp/mq/info/a/query/"+basicId,
type: "POST",
dataType: "json"
},
},
batch: true,
serverPaging: true,
pageSize: 10,
schema: {
data: 'rows',
total: 'total',
model: {
id: "auditId",
fields: {
startDate:{type: "date"},
endDate:{type: "date"},
effectiveEndDate:{type: "date"},
}
}
}
});
$("#grid1").kendoGrid({
dataSource: dataSource1,
resizable: true,
scrollable: true,
navigatable: false,
dataBound: function () {
if (parent.autoResizeIframe) {
parent.autoResizeIframe('${RequestParameters.functionCode!}')
}
},
pageable: {
pageSizes: [5, 10, 20, 50],
refresh: true,
buttonCount: 5
},
columns: [
{
title: '审计',
width: 120,
headerAttributes: {
style: "text-align: center"
},
attributes: {style: "text-align:center"},
template: function (rowdata) {
if (!!rowdata.infoId) {
return '<a href="#" class="btn btn-primary" onclick="history1(' + rowdata.infoId + ')">' +
'历史记录a>'
}
return ''
},
sortable: false,
locked:true
},
//资质类型
{
field: "qualifTpye",
title: "资质编码",
width: 90,
headerAttributes: {
style: "text-align: center"
},
},
{
field: "qualifName",
title: '资质名称',
width: 180,
headerAttributes: {
style: "text-align: center"
},
},
//证件编号
{
field: "certificateNo",
title: '证件编号',
width: 200,
headerAttributes: {
style: "text-align: center"
},
},
//开始日期
{
field: "startDate",
title: '开始日期',
width: 100,
format: "{0:yyyy-MM-dd}",
headerAttributes: {
style: "text-align: center"
},
},
//结束日期
{
field: "endDate",
title: '结束日期',
width: 100,
format: "{0:yyyy-MM-dd}",
headerAttributes: {
style: "text-align: center"
},
},
//失效日期
{
field: "effectiveEndDate",
title: '失效日期',
width: 100,
format: "{0:yyyy-MM-dd}",
headerAttributes: {
style: "text-align: center"
},
},
//修改原因
{
field: "modifyReason",
title: "<@spring.message 'gxpmqbasic.modifyReason'/>",
width: 120,
headerAttributes: {
style: "text-align: center"
},
},
//提前预警期
{
field: "warningLeadTime",
title: '预警提前期',
width: 100,
headerAttributes: {
style: "text-align: center"
},
},
//产品名称
{
field: "productName",
title: '产品名称',
width: 250,
headerAttributes: {
style: "text-align: center"
},
},
//备注
{
field: "remark",
title: '备注',
width: 100,
headerAttributes: {
style: "text-align: center"
},
},
{
field: "deleteFlag",
title: '是否已删除',
width: 90,
headerAttributes: {
style: "text-align: center"
},
template:function(dataItem){
if(dataItem.deleteFlag == 'Y'){
/*若已经删除,设置背景颜色为灰色*/
var uid = dataItem.uid;
setTimeout(function () {
$("tr[data-uid="+uid+"]").css("background","#EBEBEB");
},0);
return '是';
}
else{
return '否'
}
}
},
{
field: "auditTimestamp",
title: '操作时间',
headerAttributes: {
style: "text-align: center"
},
width: 150,
template:function(dataItem){
if(dataItem.deleteFlag == 'Y'){
/*若已经删除,显示操作时间*/
return dataItem.auditTimestamp.substring(0, 19);
}
else{
return ''
}
}
},
],
editable: false
});
$("#historyModel1").kendoWindow({
width: "1000px",
height:"500px",
title: '物料资质行历史记录',
modal:true,
resizable: false,
visible:false,
iframe:true
});
/*查看物料行 历史记录*/
function history1(infoId) {
var detailModel1 = $("#historyModel1").data("kendoWindow");
detailModel1.refresh('gxp_mq_info_a_detail.html?basicId=' + basicId+'&infoId='+infoId);
if(parent.autoResizeIframe){
parent.autoResizeIframe('${RequestParameters.functionCode!}', 700, function(){
detailModel1.center().open();
})
}else {
detailModel1.center().open();
}
}
界面如下:
a. 通过dealAuditSingleLanguageData函数判断某个字段被修改,然后设置颜色为红色。
每一列加上:
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.modifyReason);
}
<#include "../include/header.html" >
<body>
<script type="text/javascript">
/**
* 单语言审计记录 处理 显示不同样式
* @param data
* @returns {string|*|string}
*/
function dealAuditSingleLanguageData(data) {
if(data !== undefined) {
var color = "";
if(data.indexOf("&") >= 0) {
data = data.substring(0, data.length - 1);
color = "color: red;";
}
result = ''">' + data + '';
} else result = '';
return result;
}
var basicId = "${RequestParameters.basicId!'0'}";
var infoId = "${RequestParameters.infoId!'0'}";
var viewModel = kendo.observable({
model: {
basicId: basicId,
infoId:infoId
},
});
script>
<div id="page-content">
<div style="clear:both">
<div id="grid">div>
div>
div>
<script type="text/javascript">
$(document).ready(function () {
var baseUrl = "${base.contextPath}/cux/gxp/mq/info/a/detail/";
window.dataSource = new kendo.data.DataSource({
transport: {
read: {
url: baseUrl +infoId,
type: "POST",
dataType: "json"
}
},
batch: true,
serverPaging: true,
pageSize: 10,
schema: {
data: 'rows',
total: 'total',
model: {
id: 'surId',
fields: {
loginRequire: {defaultValue: 'Y'},
accessCheck: {defaultValue: 'Y'},
type: {defaultValue: 'HTML'},
url: {validation: {required: true}},
auditTimestamp:{type:"date"},
/*startDate:{type: "date"},
endDate:{type: "date"},
effectiveEndDate:{type: "date"}*/
}
}
}
});
window.grid = $("#grid").kendoGrid({
dataSource: dataSource,
resizable: true,
scrollable: true,
navigatable: false,
height: 470,
sortable: false,
pageable: {
pageSizes: [5, 10, 20, 50],
refresh: true,
buttonCount: 5
},
columns: [
{
field: "auditTimestamp",
title: '操作时间',
attributes: {style: "background-color: WhiteSmoke"},
width: 160,
format: "{0:yyyy-MM-dd HH:mm:ss}",
headerAttributes: {
style: "text-align: center"
},
},
{
field: "operatorUser",
title: '操作人用户',
width: 100,
attributes: {style: "background-color: WhiteSmoke"},
headerAttributes: {
style: "text-align: center"
},
},
{
field: "operatorName",
title: '操作人姓名',
width: 100,
attributes: {style: "background-color: WhiteSmoke"},
headerAttributes: {
style: "text-align: center"
},
},
{
field: "auditTransactionType",
title: '操作类型',
attributes: {style: "background-color: WhiteSmoke"},
headerAttributes: {
style: "text-align: center"
},
width: 80,
},
//资质类型
{
field: "qualifTpye",
title: "资质编码",
headerAttributes: {
style: "text-align: center"
},
width: 90,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.qualifTpye);
}
},
{
field: "qualifName",
title: '资质名称',
headerAttributes: {
style: "text-align: center"
},
width: 180,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.qualifName);
}
},
//证件编号
{
field: "certificateNo",
title: '证件编号',
headerAttributes: {
style: "text-align: center"
},
width: 200,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.certificateNo);
}
},
//开始日期
{
field: "startDate",
title: '开始日期',
headerAttributes: {
style: "text-align: center"
},
width: 100,
template : function(rowdata) {
var data = rowdata.startDate;
var result = '';
if(data !== undefined) {
var color = "";
if(data.indexOf("&") >= 0) {
color = "color: red;";
data = data.substring(0, data.length - 12);
}else {
data = data.substring(0, data.length - 11);
}
result = ''">' + data + '';
} else result = '';
return result;
}
},
//结束日期
{
field: "endDate",
title: '结束日期',
headerAttributes: {
style: "text-align: center"
},
headerAttributes: {
style: "text-align: center"
},
width: 100,
template : function(rowdata) {
var data = rowdata.endDate;
var result = '';
if(data !== undefined) {
var color = "";
if(data.indexOf("&") >= 0) {
color = "color: red;";
data = data.substring(0, data.length - 12);
}else {
data = data.substring(0, data.length - 11);
}
result = ''">' + data + '';
} else result = '';
return result;
}
},
//失效日期
{
field: "effectiveEndDate",
title: '失效日期',
headerAttributes: {
style: "text-align: center"
},
width: 100,
template : function(rowdata) {
var data = rowdata.effectiveEndDate;
var result = '';
if(data !== undefined) {
var color = "";
if(data.indexOf("&") >= 0) {
color = "color: red;";
data = data.substring(0, data.length - 12);
}else {
data = data.substring(0, data.length - 11);
}
result = ''">' + data + '';
} else result = '';
return result;
}
},
//修改原因
{
field: "modifyReason",
title: '修改原因',
headerAttributes: {
style: "text-align: center"
},
width: 120,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.modifyReason);
}
},
//提前预警期
{
field: "warningLeadTime",
title: '预警提前期',
headerAttributes: {
style: "text-align: center"
},
width: 100,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.warningLeadTime);
}
},
//产品名称
{
field: "productName",
title: '产品名称',
headerAttributes: {
style: "text-align: center"
},
width: 250,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.productName);
}
},
{
field: "remark",
title: '备注',
headerAttributes: {
style: "text-align: center"
},
width: 120,
template : function(rowdata) {
return dealAuditSingleLanguageData(rowdata.remark);
}
},
],
editable: false
}).data("kendoGrid");
});
script>
body>
界面如下:日期被修改,修改原因被修改会被标红。
1)执行删除操作,审计表没有删除纪录
删除时 ,审计表中没有数据。由于拦截器是通过request来捕捉的,所以一定要有request参数
HAP框架封装好的方法batchUpdate()没有request,所以auditUpdate设置audit_tag的时候更新记录是0.
故我们需要自己写方法删除或者调用mapper.delete方法删除。把request传递过去即可。
Controller:
public ResponseData delete(HttpServletRequest request,@RequestBody List dto){
IRequest iRequest = createRequestContext(request);
// service.batchDelete(dto);
for(GxpMqDiagnosisScope scope : dto){
service.delete(iRequest,scope);
}
return new ResponseData();
}
Service:
@Override
public void delete(IRequest iRequest, GxpMqDiagnosisScope scope) {
mapper.delete(scope);
}
2)审计表操作人为-1
审计表操作人为空,是因为没有调用service中的requestCtx。操作人id没有存入数据库中。