Научите, как элегантно использовать Aop для записи сложных журналов веб-интерфейса с параметрами.

Spring Boot
Научите, как элегантно использовать Aop для записи сложных журналов веб-интерфейса с параметрами.

предисловие

Не так давно из-за требований необходимо было ввести журнал операций. После вызова почти каждого интерфейса в базу данных записывается определенный журнал, связанный с этим параметром. Например, например, при отключении звука, журнал должен записывать причину отключения звука, идентификатор отключения звука и различную информацию. Это удобно для последующих запросов.

Таких интерфейсов много, и большинство из них имеют разные параметры. Идея, которую может легко придумать, состоит в том, чтобы реализовать класс инструмента ведения журнала, а затем добавить строку кода в интерфейс, который должен вести журнал. Этот класс инструментов журнала определяет, какие параметры должны обрабатываться в данный момент.

Но с этим есть большая проблема. Если количество интерфейсов, которые необходимо логировать, очень велико, давайте не будем обсуждать, сколько необходимо оценивать типов в этом классе инструментов, просто добавление такой строки кода ко всем интерфейсам — это, на мой взгляд, неприемлемое поведение. Во-первых, это слишком навязчиво для кода. Во-вторых, если в более поздний период произойдут изменения, обслуживающему персоналу будет очень некомфортно. Представьте, что вы ищете один и тот же код по всему миру и модифицируете его один за другим.

Поэтому я отказался от этого несколько примитивного подхода. Наконец, я принял метод Aop для записи журнала, перехватив запрос. Но даже при таком подходе остается проблема, как поступить с большим количеством параметров. И как соответствовать каждому интерфейсу.

Вместо того, чтобы перехватывать все контроллеры, я настроил аннотацию журнала. Все методы, отмеченные этой аннотацией, будут протоколироваться. В то же время аннотация будет иметь тип для указания конкретного содержимого журнала и параметров для текущего интерфейса.

Итак, как указать соответствующие параметры для текущего журнала из множества возможных параметров. Мое решение состоит в том, чтобы поддерживать класс параметров, в котором перечислены все имена параметров, которые необходимо регистрировать в журнале. Затем при перехвате запроса все параметры и значения в запросе и ответе запроса получаются через отражение.Если параметр существует в классе параметров, который я поддерживаю, ему присваивается соответствующее значение.

Затем после завершения запроса замените все зарезервированные параметры в шаблоне параметрами с присвоенными значениями. Таким образом, требования выполняются, а ремонтопригодность кода также обеспечивается без значительного вмешательства в бизнес.

Ниже я перечислю подробный процесс реализации.

Перед началом операции

В конце статьи я приведу весь исходный код этого демонстрационного проекта. Поэтому, если вы не хотите видеть процесс, Xiongtai может перейти к концу и посмотреть исходный код напрямую. (Я слышал, что читать статью вкуснее, когда она соответствует исходному коду...)

начать

Новый проект

Вы можете обратиться к другой статье, которую я написал ранее,Научит вас создавать фреймворк бэкенд-проекта SpringBoot с нуля.. Пока вы можете запросить простой интерфейс. Зависимости этого проекта следующие.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>


<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.1.14</version>
</dependency>

Создайте новый класс Aop

новыйLogAspectДобрый. код показывает, как показано ниже.

package spring.aop.log.demo.api.util;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * LogAspect
 *
 * @author Lunhao Hu
 * @date 2019-01-30 16:21
 **/
@Aspect
@Component
public class LogAspect {
    /**
     * 定义切入点
     */
    @Pointcut("@annotation(spring.aop.log.demo.api.util.Log)")
    public void operationLog() {
    }
    
    /**
     * 新增结果返回后触发
     *
     * @param point
     * @param returnValue
     */
    @AfterReturning(returning = "returnValue", pointcut = "operationLog() && @annotation(log)")
    public void doAfterReturning(JoinPoint point, Object returnValue, Log log) {
        System.out.println("test");
    }
}

PointcutПередается аннотация, указывающая, что любой метод, помеченный этой аннотацией, вызоветPointcutдекоративныйoperationLogфункция. иAfterReturningОн запускается после возврата запроса.

пользовательская аннотация

Пользовательская аннотация, упомянутая на предыдущем шаге, будет отмечена для каждого метода контроллера. создать новыйannotationтип. код показывает, как показано ниже.

package spring.aop.log.demo.api.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Log
 *
 * @author Lunhao Hu
 * @date 2019-01-30 16:19
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String type() default "";
}

TargetиRetentionВсе являются мета-аннотациями. Всего есть 4 вида, которые@Retention,@Target,@Document,@Inherited.

TargetАннотация указывает область действия, измененную этой аннотацией. Многие типы могут быть переданы, и параметрыElementType. НапримерTYPE, используемый для описания класса, интерфейса или класса перечисления;FIELDиспользуется для описания свойств;METHODиспользуется для описания методов;PARAMETERиспользуется для описания параметров;CONSTRUCTORИспользуется для описания конструкторов;LOCAL_VARIABLEИспользуется для описания локальных переменных;ANNOTATION_TYPEИспользуется для описания аннотаций;PACKAGEИспользуется для описания пакетов и т. д.

RetentionАннотация определяет, как долго сохраняется аннотация. ПараметрыRetentionPolicy. НапримерSOURCEУказывает, что он существует только в исходном коде и не будет существовать в скомпилированном файле класса;CLASSявляется параметром по умолчанию для этой аннотации. Он существует в исходном коде и в скомпилированном файле класса, но не будет загружен в виртуальную машину;RUNTIMEОн существует в исходном коде, файле класса и виртуальной машине, говоря простым языком, его можно получить путем отражения во время выполнения.

добавить обычные аннотации

Добавьте в интерфейс, который должен записывать журналLogаннотация.

package spring.aop.log.demo.api.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import spring.aop.log.demo.api.util.Log;

/**
 * HelloController
 *
 * @author Lunhao Hu
 * @date 2019-01-30 15:52
 **/
@RestController
public class HelloController {
    @Log
    @GetMapping("test/{id}")
    public String test(@PathVariable(name = "id") Integer id) {
        return "Hello" + id;
    }
}

После добавления каждый вызовtest/{id}Этот интерфейс вызовет перехватчик вdoAfterReturningкод в методе.

с аннотациями типа

Метод записи обычных журналов описан выше, а метод записи специальных журналов описан далее. Что такое конкретный журнал, то есть информация, которая будет записываться каждым интерфейсом, разная. Для этого нам нужно реализовать класс перечисления типа операции. код показывает, как показано ниже.

Перечисление шаблона типа действия

Создайте новый класс перечисленияType. код показывает, как показано ниже.

package spring.aop.log.demo.api.util;

/**
 * Type
 *
 * @author Lunhao Hu
 * @date 2019-01-30 17:12
 **/
public enum Type {
    /**
     * 操作类型
     */
    WARNING("警告", "因被其他玩家举报,警告玩家");

    /**
     * 类型
     */
    private String type;

    /**
     * 执行操作
     */
    private String operation;

    Type(String type, String operation) {
        this.type = type;
        this.operation = operation;
    }

    public String getType() { return type; }

    public String getOperation() { return operation; }
}

добавить тип к аннотации

Добавьте тип к аннотации в контроллере выше. код показывает, как показано ниже.

package spring.aop.log.demo.api.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import spring.aop.log.demo.api.util.Log;

/**
 * HelloController
 *
 * @author Lunhao Hu
 * @date 2019-01-30 15:52
 **/
@RestController
public class HelloController {
    @Log(type = "WARNING")
    @GetMapping("test/{id}")
    public String test(@PathVariable(name = "id") Integer id) {
        return "Hello" + id;
    }
}

Изменить класс aop

Поместите в класс aopdoAfterReturningследующее.

@AfterReturning(returning = "returnValue", pointcut = "operationLog() && @annotation(log)")
public void doAfterReturning(JoinPoint point, Object returnValue, Log log) {
    // 注解中的类型
    String enumKey = log.type();
    System.out.println(Type.valueOf(enumKey).getOperation());
}

После добавления каждый вызов добавляет@Log(type = "WARNING")Этот аннотированный интерфейс будет печатать журнал, указанный этим интерфейсом. Например, приведенный выше код напечатает следующий код.

因被其他玩家举报,警告玩家

Получить параметры запроса, перехваченные aop

Нетрудно указать журнал для каждого интерфейса, просто укажите тип для каждого интерфейса. Но вы также должны были заметить, что журнал интерфейса записывает только因被其他玩家举报,警告玩家Такая информация не имеет никакого смысла.

Человек, который записывал журнал, так не думал, а человек, который, наконец, проверял журнал, каждый день дважды думал о себе. Отчитался за что? Кого я предупреждаю?

Такие журналы выполняют слишком много бесполезной работы, и нет возможности отследить источник после возникновения проблемы. Итак, наш следующий шаг — добавить определенные параметры для каждого интерфейса. Тогда у вас может возникнуть проблема.Если параметры каждого интерфейса почти разные, то этому классу инструмента необходимо передать множество параметров, как его реализовать, и даже организовать параметры, которые будут занимать много бизнес-кода, и будет добавлено много избыточного кода.

Вы можете подумать, реализовать метод для ведения журнала, вызвать его в интерфейсе для ведения журнала и передать параметры. Если типов много, параметры тоже будут увеличиваться, причем параметры каждого интерфейса разные. Это громоздко иметь дело и слишком навязчиво для бизнеса. Код, связанный с логированием, встроен почти везде. После внесения изменений становится очень трудно поддерживать.

Поэтому я напрямую использую рефлексию, чтобы получить все параметры в запросе, перехваченном aop.Если в моем классе параметров есть параметры в запросе (все параметры должны быть записаны), то я пропишу значение параметра в класс параметров. Наконец, замените зарезервированное поле параметра в шаблоне журнала параметром в запросе.

Блок-схема показана ниже.

Новый класс параметров

создать новый классParam, который содержит все параметры, которые могут появиться в журнале операций. почему ты хочешь сделать это? Поскольку параметры, требуемые для каждого интерфейса, могут быть совершенно разными, лучше поддерживать большое количество логики суждения, чем поддерживать большое количество логики суждения.贪心Одна точка, передать все возможные параметры напрямую. Конечно, если есть новые параметры, которые необходимо записать позже, код нужно модифицировать.

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * Param
 *
 * @author Lunhao Hu
 * @date 2019-01-30 17:14
 **/
@Data
public class Param {
    /**
     * 所有可能参数
     */
    private String id;
    private String workOrderNumber;
    private String userId;
}

Изменить шаблон

Поместите перечисление шаблона в классWARNINGИзменено следующим образом.

WARNING("警告", "因 工单号 [(%workOrderNumber)] /举报 ID [(%id)] 警告玩家 [(%userId)]");

Параметры — это параметры, которые необходимо получить и заменить на этапе перехвата aop.

изменить контроллер

Добавляем параметры вышеуказанного шаблона China в предыдущий контроллер. Часть кода выглядит следующим образом.

@Log(type = "WARNING")
@GetMapping("test/{id}")
public String test(
        @PathVariable(name = "id") Integer id,
        @RequestParam(name = "workOrderNumber") String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestParam(name = "name") String name
) {
    return "Hello" + id;
}

Получить параметры запроса через отражение

Здесь есть два случая, один — простой тип параметра, а другой — сложный тип параметра, то есть случай, когда DTO запроса включен в параметр.

Получить простой тип параметра

Добавьте несколько частных переменных в класс aop.

/**
 * 请求中的所有参数
 */
private Object[] args;

/**
 * 请求中的所有参数名
 */
private String[] paramNames;

/**
 * 参数类
 */
private Param params;

потомdoAfterReturningИзмените код на следующий.

try {
    // 获取请求详情
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    HttpServletResponse response = attributes.getResponse();
    // 获取所有请求参数
    Signature signature = point.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    this.paramNames = methodSignature.getParameterNames();
    this.args = point.getArgs();

    // 实例化参数类
    this.params = new Param();
    // 注解中的类型
    String enumKey = log.type();
    String logDetail = Type.valueOf(enumKey).getOperation();

    // 从请求传入参数中获取数据
    this.getRequestParam();
} catch (Exception e) {
    System.out.println(e.getMessage());
}

Первое, что нужно сделать, это перехватить запросы с пользовательскими аннотациями. Мы можем получить детали запроса, а также все имена параметров и параметры в запросе. Далее мы реализуем приведенный выше кодgetRequestParamметод.

getRequestParam

/**
 * 获取拦截的请求中的参数
 * @param point
 */
private void getRequestParam() {
    // 获取简单参数类型
    this.getSimpleParam();
}

getSimpleParam

/**
 * 获取简单参数类型的值
 */
private void getSimpleParam() {
    // 遍历请求中的参数名
    for (String reqParam : this.paramNames) {
        // 判断该参数在参数类中是否存在
        if (this.isExist(reqParam)) {
            this.setRequestParamValueIntoParam(reqParam);
        }
    }
}

В приведенном выше коде проходим имена параметров, переданные в запросе, а затем реализуемisExistметод, чтобы определить, находится ли этот параметр в нашемParamСуществует ли класс, если он существует, мы вызовем его сноваsetRequestParamValueIntoParamметода, запишите значение параметра, соответствующее имени этого параметра, вParamв экземпляре класса.

isExist

isExistКод выглядит следующим образом.

/**
 * 判断该参数在参数类中是否存在(是否是需要记录的参数)
 * @param targetClass
 * @param name
 * @param <T>
 * @return
 */
private <T> Boolean isExist(String name) {
    boolean exist = true;
    try {
        String key = this.setFirstLetterUpperCase(name);
        Method targetClassGetMethod = this.params.getClass().getMethod("get" + key);
    } catch (NoSuchMethodException e) {
        exist = false;
    }
    return exist;
}

Как мы упоминали выше, геттер и сеттер будут добавлены при компиляции, поэтому первая буква имени параметра будет заглавной, поэтому нам нужно реализовать его самостоятельно.setFirstLetterUpperCaseчтобы сделать первую букву имени параметра, который мы передаем, заглавной.

setFirstLetterUpperCase

код показывает, как показано ниже.

/**
 * 将字符串的首字母大写
 *
 * @param str
 * @return
 */
private String setFirstLetterUpperCase(String str) {
    if (str == null) {
        return null;
    }
    return str.substring(0, 1).toUpperCase() + str.substring(1);
}

setRequestParamValueIntoParam

код показывает, как показано ниже.

/**
 * 从参数中获取
 * @param paramName
 * @return
 */
private void setRequestParamValueIntoParam(String paramName) {
    int index = ArrayUtil.indexOf(this.paramNames, paramName);
    if (index != -1) {
        String value = String.valueOf(this.args[index]);
        this.setParam(this.params, paramName, value);
    }
}

ArrayUtilдаhutoolПолезная функция в . Используется для определения индекса элемента в массиве.

setParam

код показывает, как показано ниже.

/**
 * 将数据写入参数类的实例中
 * @param targetClass
 * @param key
 * @param value
 * @param <T>
 */
private <T> void setParam(T targetClass, String key, String value) {
    try {
        Method targetClassParamSetMethod = targetClass.getClass().getMethod("set" + this.setFirstLetterUpperCase(key), String.class);
        targetClassParamSetMethod.invoke(targetClass, value);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

Эта функция использует метод отражения для получения заданного метода параметра, который будетParamСоответствующий параметр в классе устанавливается на входящее значение.

бегать

Запустите проект и запросите метод в контроллере. И передать определенные параметры.

http://localhost:8080/test/8?workOrderNumber=3231732&userId=748327843&name=testName

ДолженGETВсего в запрос передано 4 параметра, которыеid,workOrderNumber,userId, name. Как видите, вParamкласс не определенnameэто поле. Это преднамеренное добавление параметра, который не нужно записывать для проверки надежности нашего интерфейса.

После запуска вы можете увидеть информацию, напечатанную на консоли, следующим образом.

Param(id=8, workOrderNumber=3231732, userId=748327843)

Мы хотим записать все параметры, записанные aop вParamэкземпляр класса, и передача неожиданных параметров не приводила к сбою программы. Далее нам нужно только заменить эти параметры зарезервированными полями параметров ранее определенного шаблона.

Подстановочные параметры

существуетdoAfterReturningсерединаgetRequestParamПосле функции добавьте следующий код.

if (!logDetail.isEmpty()) {
    // 将模板中的参数全部替换掉
    logDetail = this.replaceParam(logDetail);
}
System.out.println(logDetail);

Ниже мы реализуемreplaceParamметод.

replaceParam

код показывает, как показано ниже.

/**
 * 将模板中的预留字段全部替换为拦截到的参数
 * @param template
 * @return
 */
private String replaceParam(String template) {
    // 将模板中的需要替换的参数转化成map
    Map<String, String> paramsMap = this.convertToMap(template);
    for (String key : paramsMap.keySet()) {
        template = template.replace("%" + key, paramsMap.get(key)).replace("(", "").replace(")", "");
    }
    return template;
}

convertToMapМетод извлекает все зарезервированные поля в шаблоне как ключ карты.

convertToMap

код показывает, как показано ниже.

/**
 * 将模板中的参数转换成map的key-value形式
 * @param template
 * @return
 */
private Map<String, String> convertToMap(String template) {
    Map<String, String> map = new HashMap<>();
    String[] arr = template.split("\\(");
    for (String s : arr) {
        if (s.contains("%")) {
            String key = s.substring(s.indexOf("%"), s.indexOf(")")).replace("%", "").replace(")", "").replace("-", "").replace("]", "");
            String value = this.getParam(this.params, key);
            map.put(key, "null".equals(value) ? "(空)" : value);
        }
    }
    return map;
}

один из нихgetParamметод, аналогичныйsetParam, который также использует метод отражения для получения соответствующего значения через входящий класс и ключ.

getParam

код показывает, как показано ниже.

/**
 * 通过反射获取传入的类中对应key的值
 * @param targetClass
 * @param key
 * @param <T>
 */
private <T> String getParam(T targetClass, String key) {
    String value = "";
    try {
        Method targetClassParamGetMethod = targetClass.getClass().getMethod("get" + this.setFirstLetterUpperCase(key));
        value = String.valueOf(targetClassParamGetMethod.invoke(targetClass));
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    return value;
}

бежать снова

Запросите указанный выше URL еще раз, и вы увидите вывод консоли следующим образом.

因 工单号 [3231732] /举报 ID [8] 警告玩家 [748327843]

Как видите, все параметры, которые нам нужно записать, заменены корректно. Параметры, которые не нужно записывать, также не влияют на программу.

Попробуем передать без необязательных параметров, как это будет выглядеть. Измените контроллер следующим образом и измените workOrderNumber на необязательный параметр.

@Log(type = "WARNING")
@GetMapping("test/{id}")
public String test(
        @PathVariable(name = "id") Integer id,
        @RequestParam(name = "workOrderNumber", required = false) String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestParam(name = "name") String name
) {
    return "Hello" + id;
}

Запросите следующий URL.

http://localhost:8080/test/8?userId=748327843&name=testName

Затем вы можете увидеть, что вывод консоли выглядит следующим образом.

因 工单号 [空] /举报 ID [8] 警告玩家 [748327843]

На нормальную работу программы это не повлияет.

Получить сложный тип параметра

Следующее, о чем нужно рассказать, — как регистрировать сложные типы параметров. На самом деле общая идея остается прежней. Посмотрим, есть ли во входящем классе параметры, которые нужно записать. Если таковые имеются, замените параметры записи по методике записи простых параметров выше.

Определение типа тестового комплекса

новыйTestDTO. код показывает, как показано ниже.

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * TestDto
 *
 * @author Lunhao Hu
 * @date 2019-02-01 15:02
 **/
@Data
public class TestDTO {
    private String name;
    
    private Integer age;

    private String email;
}

Изменить параметр

Добавьте все вышеперечисленные параметры вParamВ классе все определены как строковые типы.

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * Param
 *
 * @author Lunhao Hu
 * @date 2019-01-30 17:14
 **/
@Data
public class Param {
    /**
     * 所有可能参数
     */
    private String id;
    private String age;
    private String workOrderNumber;
    private String userId;
    private String name;
    private String email;
}

Изменить шаблон

будетWARNINGШаблон модифицируется следующим образом.

/**
 * 操作类型
 */
WARNING("警告", "因 工单号 [(%workOrderNumber)] /举报 ID [(%id)] 警告玩家 [(%userId)], 游戏名 [(%name)], 年龄 [(%age)]");

изменить контроллер

@Log(type = "WARNING")
@PostMapping("test/{id}")
public String test(
        @PathVariable(name = "id") Integer id,
        @RequestParam(name = "workOrderNumber", required = false) String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestBody TestDTO testDTO
) {
    return "Hello" + id;
}

Изменить getRequestParam

/**
 * 获取拦截的请求中的参数
 * @param point
 */
private void getRequestParam() {
    // 获取简单参数类型
    this.getSimpleParam();

    // 获取复杂参数类型
    this.getComplexParam();
}

Реализовать следующийgetComplexParamметод.

getComplexParam

/**
 * 获取复杂参数类型的值
 */
private void getComplexParam() {
    for (Object arg : this.args) {
        // 跳过简单类型的值
        if (arg != null && !this.isBasicType(arg)) {
           this.getFieldsParam(arg);
        }
    }
}

getFieldsParam

/**
 * 遍历一个复杂类型,获取值并赋值给param
 * @param target
 * @param <T>
 */
private <T> void getFieldsParam(T target) {
    Field[] fields = target.getClass().getDeclaredFields();
    for (Field field : fields) {
        String paramName = field.getName();
        if (this.isExist(paramName)) {
            String value = this.getParam(target, paramName);
            this.setParam(this.params, paramName, value);
        }
    }
}

бегать

Стартовый проект. Используйте postman, чтобы инициировать POST-запрос к указанному выше URL-адресу. Внесите его в тело запросаTestDTOпараметры в . После успешного завершения запроса вы увидите вывод консоли, как показано ниже.

因 工单号 [空] /举报 ID [8] 警告玩家 [748327843], 游戏名 [tom], 年龄 [12]

Затем вы можете записать вышеуказанный журнал в соответствующее место в соответствии с вашими потребностями.

В этот момент некоторым приятелям может показаться, что все в порядке, все на месте, только восточный ветер должен. Тем не менее, есть еще несколько проблем с этой реализацией.

Например, что делать, если запрос не выполнен? При неудачном запросе вообще не нужно записывать лог операции, но даже при неудачном запросе будет возвращаемое значение, означающее, что лог будет успешно записан. Это создает много проблем при последующем просмотре журнала.

Другой пример, что, если нужный мне параметр находится в возвращаемом значении? Если вы не используете унифицированный сервис, генерирующий уникальные идентификаторы, вы столкнетесь с этой проблемой. Например, мне нужно вставить новый кусок данных в базу данных, мне нужно получить id автоинкремента базы данных, а наш перехват журнала перехватывает только параметры в запросе. Вот этим мы и займемся дальше.

Определить, был ли запрос успешным

выполнитьsuccessфункция, код выглядит следующим образом.

/**
 * 根据http状态码判断请求是否成功
 *
 * @param response
 * @return
 */
private Boolean success(HttpServletResponse response) {
    return response.getStatus() == 200;
}

потомgetRequestParamвсе последующие операции, включаяgetRequestParamсебя, сsuccessЗаверните. следующее.

if (this.success(response)) {
    // 从请求传入参数中获取数据
    this.getRequestParam();
    if (!logDetail.isEmpty()) {
        // 将模板中的参数全部替换掉
        logDetail = this.replaceParam(logDetail);
    }
}

Таким образом, можно гарантировать, что журнал будет записан только в случае успешного выполнения запроса.

Получить возвращенные параметры через отражение

Создайте новый класс результатов

В проекте мы используем класс для унификации возвращаемых значений.

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * Result
 *
 * @author Lunhao Hu
 * @date 2019-02-01 16:47
 **/
@Data
public class Result {
    private Integer id;

    private String name;

    private Integer age;

    private String email;
}

изменить контроллер

@Log(type = "WARNING")
@PostMapping("test")
public Result test(
        @RequestParam(name = "workOrderNumber", required = false) String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestBody TestDTO testDTO
) {
    Result result = new Result();
    result.setId(1);
    result.setAge(testDTO.getAge());
    result.setName(testDTO.getName());
    result.setEmail(testDTO.getEmail());
    return result;
}

бегать

Запустите проект и инициируйте запрос POST, чтобы убедиться, что возвращаемое значение выглядит следующим образом.

{
    "id": 1,
    "name": "tom",
    "age": 12,
    "email": "test@test.com"
}

И вывод консоли выглядит следующим образом.

因 工单号 [39424] /举报 ID [空] 警告玩家 [748327843], 游戏名 [tom], 年龄 [12]

можно увидеть,idне получено. Поэтому нам также нужно добавить функцию для получения данных идентификатора из возвращаемого значения.

getResponseParam

существуетgetRequestParamПосле этого добавьте методgetResponseParam, напрямую вызвать ранее написанную функцию. код показывает, как показано ниже.

/**
 * 从返回值从获取数据
 */
private void getResponseParam(Object value) {
    this.getFieldsParam(value);
}

бегать

Снова инициируйте запрос POST, и вы обнаружите, что вывод консоли выглядит следующим образом.

因 工单号 [39424] /举报 ID [1] 警告玩家 [748327843], 游戏名 [tom], 年龄 [12]

Получив эту информацию, мы можем регистрировать ее где угодно.

Адрес исходного кода проекта

Если вы хотите обратиться к исходному коду, пожалуйста, нажмите->здесь

Прошлые статьи:

Связанный:

  • Персональный сайт:Lunhao Hu
  • Официальная учетная запись WeChat: заметки о полном стеке SH (или прямой поиск WeChat LunhaoHu в интерфейсе добавления официальной учетной записи)