审计的实现和使用

审计的实现和使用

NAME: cherryc

DATE: 2017/11/20

目前做Gxp项目,客户需要审计日志模块,即对用户的操作进行记录,主要是增删改操作。

由于框架用的是Spring+SpringMVC+Mybatis,其中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方法,或者调用其他方法。

二、注解@Intercepts、@Signature

我们可以通过实现该接口来定义自己的拦截器,处理自己想要的逻辑。但是实现自己的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>
四、审计拦截器
1)intercept 判断是否满足拦截条件

如下是审计拦截器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<String, Object> temp = (Map<String, Object>) param;
            for (Map.Entry<String, Object> 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。如下表示对数据库的增删改操作。

image.png

还包含了参数值args,args[0] 表示拦截的操作是insert 可以通过SqlCommandType获得

SqlCommandType type = mappedStatement.getSqlCommandType();

image.png

args[1] 获得拦截的操作数据,封装在dto对象中。

image.png

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

2)doAudit 获得审计表字段值

然后拦截增删改操作方法,调用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<String> cols = new ArrayList<>();
        for (Map.Entry<String, EntityField> 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<String> 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<String, Object> 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<String, Object> wps = getWhereClause(mappedStatement, param, tableName);
        for (Map.Entry<String, Object> 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<String> languageCodes = new HashSet<>();
        Map<String, Map<String, String>> map = dto.get__tls();
        for (Map.Entry entry : map.entrySet()) {
            Map<String, Object> m = (Map<String, Object>) entry.getValue();
            for (Map.Entry<String, Object> item : m.entrySet()) {
                languageCodes.add(item.getKey());
            }
        }

        Map<String, Object> 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<Language> 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);获得,基表主键可能有多个字段,所以通过数组来接收。

image.png

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);
3)doDB 数据保存到审计表

准备好审计表需要存入的数据之后,调用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,
        <foreach collection="_COLS" index="i" item="value" separator=",">
            ${value}
        </foreach>
        )
        SELECT #{_AUDIT_ID},#{_AUDIT_TYPE},CURRENT_TIMESTAMP,#{_AUDIT_SESSION_ID},
        #{_LANGUAGE}, #{_MAJOR_FIELD}, 1,
        <foreach collection="_DYN_FIELDS" index="i" item="value" separator=",">
            ${value}
        </foreach>
        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框架中使用审计时需要做以下操作。以物料资质行信息为例:

1)设置审计表sql —-cux_gxp_mq_info_a

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)

image.png

image.png

c. 基表主键类型不能为字符,需要设置为数字类型 number

2)dto 加上AuditEnabled注解—-GxpMqInfo
@ExtensionAttribute(disable=true)
@AuditEnabled
@Table(name = "cux_gxp_mq_info")
public class GxpMqInfo extends BaseDTO {。。。。}
3)controller—-GxpMqInfoAController

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));
        }
    }
4)service实现类—-GxpMqInfoAServiceImpl

selectAuditRecord:调用工具类AuditRecordUtils的operateAuditRecord方法,查询审计表快照,即最新的记录。

operateAuditRecordSingleDetail:调用工具类的方法,查询审计表历史纪录,并且依次判断比上一条记录都修改了哪些字段,在修改的字段后加上‘ & ’符号。

@Service
@Transactional(rollbackFor = Exception.class)
public class GxpMqInfoAServiceImpl  implements IAuditDTOService{

    @Autowired
    private GxpMqInfoAMapper gxpMqInfoAMapper;

    @Override
    public List<Map<String, Object>> selectAuditRecord(IRequest iRequest, BaseDTO baseDTO, int... param) {
        PageHelper.startPage(param[0],param[1]);
        return AuditRecordUtils.operateAuditRecord(gxpMqInfoAMapper.selectMqInfoA(((GxpMqInfoA)baseDTO).getBasicId().intValue()));
    }

    @Override
    public List selectAuditRecordDetail(IRequest requestContext, int infoId , int page, int pageSize) {
        PageHelper.startPage(page,pageSize);
        Long infoId = ((GxpMqInfoA) baseDTO).getInfoId();
        return AuditRecordUtils.operateAuditRecordSingleDetail(gxpMqInfoAMapper.selectMqInfoADetail(infoId.intValue()));
    }
}
5)mapper—-GxpMqInfoAMapper
public interface GxpMqInfoAMapper extends Mapper<GxpMqInfoA>{
    List<Map<String,Object>> selectMqInfoA(int basicId);
    List<Map<String,Object>> selectMqInfoADetail(int infoId);
}
6)xml文件—-GxpMqInfoAMapper.xml

a. 查询出来的数据放在map中。resultType=”java.util.Map”

b. 必须加上ORDER BY cgmi.AUDIT_TIMESTAMP DESC

c. 必须查询出Audit_id(审计表主键) 和 info_id(基表主键) 否则回报空指针异常。

<sql id="MqInfoAPart">
        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">
        <include refid="MqInfoAPart"></include>
        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">
        <include refid="MqInfoAPart"></include>
        <where>
            <if test="_parameter!=0">
                and cgmi.info_id = #{_parameter}
            </if>
        </where>
        ORDER BY
        cgmi.AUDIT_TIMESTAMP DESC
    </select>
7)html页面
查看资质行快照
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);
}
<!--
author xiuhong.chen@hand-china.com
物料资质行审计  历史记录界面
2017/9/21
-->
<#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 = '<span style="'+ color + '">' + data + '</span>';
        } 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 = '<span style="'+ color + '">' + data + '</span>';
                        } 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 = '<span style="'+ color + '">' + data + '</span>';
                        } 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 = '<span style="'+ color + '">' + data + '</span>';
                        } 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<GxpMqDiagnosisScope> 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没有存入数据库中。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值