aviator 应用(1)

1.项目结构:

└─src
   └─main
       └─java
           └─com
               └─yan
                   ├─abstractinterface
                   │  └─bean
                   ├─utils
                   │  └─object
                   └─aop
                      ├─aspect
                      └─aviator

2.pom

<properties>
        <java.version>8</java.version>
        <project-module.version>0.0.1-SNAPSHOT</project-module.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
	   <hutool.version>5.8.18</hutool.version>
        <aop.version>1.9.19</aop.version>
        <lombok.version>1.18.20</lombok.version>
        <aviator.version>5.2.0</aviator.version>
        <commons-beanutils.version>1.9.4</commons-beanutils.version>
</properties>   

<dependencies>
        <!--aviator-->
        <dependency>
            <groupId>com.googlecode.aviator</groupId>
            <artifactId>aviator</artifactId>
            <version>${aviator.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${commons-beanutils.version}</version> <!-- 请使用最新稳定版本 -->
       </dependency>
       <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>  
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
       <!--aop-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aop.version}</version>
        </dependency>
    </dependencies>

2.1 com.yan.abstractinterface.bean

2.1.1 interface AbstractBean

package com.yan.abstractinterface.bean;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * @Author yan
 * @Date 2024/9/22 上午10:46:15
 * @Description
 */
public interface AbstractBean {
    @Data @Accessors(chain = true)
    @NoArgsConstructor
    @AllArgsConstructor
    class LogBean{
       private org.slf4j.Logger logger;
       private Class<?> aClass;
    }
    @JsonIgnore
    default LogBean getLogBean(){
        LogBean logBean = new LogBean()
                .setAClass(this.getClass())
                .setLogger(LoggerFactory.getLogger(this.getClass()));
        return logBean;
    }
    @JsonIgnore
    default Logger getLogger(){
        return getLogBean().getLogger();
    }
    @JsonIgnore
    default Class<?> getAClass(){
        return getLogBean().getAClass();
    }
    /**
     * 初始化
     */
    @PostConstruct
    default void init(){
        LogBean logBean = getLogBean();
        logBean.getLogger().info("init {} ...",logBean.getAClass().getSimpleName());
    }

    /**
     * 销毁
     */
    @PreDestroy
    default void destroy() {
        LogBean logBean = getLogBean();
        logBean.getLogger().info("destroy {} ...",logBean.getAClass().getSimpleName());
    }
}

3.com.yan.aop.aviator

3.1 @interface Aviator

package com.yan.aop.aviator;

import java.lang.annotation.*;

/**
 * @Author yan
 * @Date 2024/11/16 下午7:43:04
 * @Description
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Aviator {
    /**
     * 解析异常是否抛出异常
     * @return
     */
    boolean throwException() default true;
    String defaultErrorMessage() default "参数不符合要求"; // 错误信息
    boolean isStr() default false;
    String[] keys() default {};
    String[] expressions() default {}; // Aviator表达式
    String[] errorMessages() default {"参数不符合要求"}; // 错误信息
}

3.2 @interface AviatorValid

package com.yan.aop.aviator;

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

/**
 * @Author yan
 * @Date 2024/11/12 上午1:30:05
 * @Description
 */
@Aviator
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AviatorValid {
    /**
     * 解析异常是否抛出异常
     * @return
     */
    boolean throwException() default true;
    String expression(); // Aviator表达式
    String errorMessage() default "参数不符合要求"; // 错误信息
}

3.3 @interface AviatorNotBlank

package com.yan.aop.aviator;

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

/**
 * @Author yan
 * @Date 2024/11/12 下午2:53:34
 * @Description
 */
@Aviator
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AviatorNotBlank {
    /**
     * 解析异常是否抛出异常
     * @return
     */
    boolean throwException() default true;
    String key();
    String errorMessage() default "参数不符合要求"; // 错误信息
}

3.4 @interface AviatorValids

package com.yan.aop.aviator;

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

/**
 * @Author yan
 * @Date 2024/11/12 上午1:30:05
 * @Description
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AviatorValids {
    Aviator[] aviator() default {};
    AviatorValid[] values() default {};
    AviatorNotBlank[] notBlanks() default {};
}

3.5 class AviatorValidInfo

package com.yan.aop.aviator;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

/**
 * @Author yan
 * @Date 2024/11/12 下午2:59:52
 * @Description
 */
@Data @Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class AviatorValidInfo {
    //抛出异常
    private boolean throwException = true;
    //expression
    private String expression;
    //异常信息
    private String errorMessage;
}

4.com.yan.utils

4.1 class ObjectUtils

package com.yan.utils.object;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * @Author yan
 * @Date 2024/11/2 下午9:20:38
 * @Description
 */
public class ObjectUtils extends ObjectUtil {
    public static <T> T defaultIfEmpty(T object, T defaultValue) {
        return ObjectUtil.isEmpty(object) ? defaultValue : object;
    }

    /**
     * @param compareTo
     * @return
     */
    public static Map<String, Object> mapKeyLengthReversed(Map<String, Object> compareTo) {
        TreeMap<String, Object> treeMap = new TreeMap<>(Comparator.comparingInt(String::length).reversed().thenComparing(String::compareTo));
        treeMap.putAll(compareTo);
        return treeMap;
    }

    /**
     * @param compareTo
     * @return
     */
    public static Map<String, Object> mapKeyLengthSort(Map<String, Object> compareTo) {
        TreeMap<String, Object> treeMap = new TreeMap(Comparator.comparingInt(String::length).thenComparing(String::compareTo));
        treeMap.putAll(compareTo);
        return treeMap;
    }

    public static Map<String, Object> toMap(Object obj) {
        return toMap(null, obj, null, null);
    }

    /**
     * obj 转 map
     * obj:{"a":"a","b":"{\"a\":\"a\",\"b\":\"a\"}","c":"[{\"a\":\"a\",\"b\":\"a\"}]"}
     * ==>
     * map:{"a":"a","b.a":"a","b.b":"a","c[0].a":"a","c[0].b":"a"}
     *
     * @param map
     * @param obj
     * @param prx
     * @param level
     * @return
     */
    public static Map<String, Object> toMap(Map<String, Object> map, Object obj, String prx, Integer level) {
        JSONConfig jsonConfig = JSONConfig.create().setIgnoreNullValue(false);
        prx = StrUtil.isBlank(prx) ? StrUtil.EMPTY : prx;
        map = ObjectUtils.defaultIfNull(map, new LinkedHashMap<>());
        level = ObjectUtils.defaultIfNull(level, 0);

        String jsonObjectJsonStr = JSONUtil.toJsonStr(obj, jsonConfig);
        if (JSONUtil.isTypeJSON(jsonObjectJsonStr)) {
            JSONObject jsonObject = new JSONObject(jsonObjectJsonStr);
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                String fullKey = StrUtil.isBlank(prx) ? key : prx + "." + key;

                String toJsonStr = JSONUtil.toJsonStr(value, jsonConfig);
                if (JSONUtil.isTypeJSONArray(toJsonStr)) {
                    // Handle JSONArray type
                    JSONArray array = new JSONArray(toJsonStr);
                    for (int i = 0; i < array.size(); i++) {
                        Object arrayValue = array.get(i);
                        level++;
                        toMap(map, arrayValue, fullKey + "[" + i + "]", level);
                        level--;
                    }
                } else if (JSONUtil.isTypeJSON(toJsonStr)) {
                    // Handle nested JSON object
                    level++;
                    toMap(map, value, fullKey, level);
                    level--;
                } else {
                    // Handle simple value
                    map.put(fullKey, value);
                    prx = removeLastSegment(prx);
                }
            }
        } else {
            // Handle non-JSON value
            map.put(prx, obj);
            prx = StrUtil.EMPTY;
        }

        return map;
    }

    public static String removeLastSegment(String prx) {
        String separator = ".";
        if (prx.contains(separator)) {
            int lastIndex = prx.lastIndexOf(separator);
            return prx.substring(0, lastIndex);
        }
        return prx;
    }

    public static Map<String, Object> blankReplace(Map<String, Object> objMap) {
        objMap = ObjectUtils.defaultIfEmpty(objMap, Maps.newLinkedHashMap());
        for (Map.Entry<String, Object> entry : objMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (ObjectUtils.equals(StrUtil.EMPTY, value)) {
                objMap.put(key, "''");
            }
        }
        return objMap;
    }

    public static void main(String[] args) {
        Map<String, Object> hashMap = Maps.newLinkedHashMap();
        hashMap.put("as", "");
        System.out.println(hashMap);
        hashMap = blankReplace(hashMap);
        System.out.println(hashMap);
    }
}

5.com.yan.aop.aspect

5.1 interface AbstractAviatorValidAspect

package com.yan.aop.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.yan.aop.aviator.*;

import com.yan.utils.object.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import com.yan.abstractinterface.bean.AbstractBean;

import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author yan
 * @Date 2024/11/12 下午3:09:40
 * @Description
 */
public interface AbstractAviatorValidAspect extends AbstractBean {
    JSONConfig jsonConfig = JSONConfig.create().setIgnoreNullValue(false);
    @SneakyThrows
    default <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        T annotation = null;
        if (ObjectUtils.isNotEmpty(method)) {
            annotation = method.getAnnotation(annotationClass);
        }
        return annotation;
    }
    /**
     *
     */
    @Pointcut(value = "@annotation(com.yan.aop.aviator.AviatorValids)"
            + " || @annotation(com.yan.aop.aviator.AviatorValid) "
            + " || @annotation(com.yan.aop.aviator.AviatorNotBlank) "
            + " || @annotation(com.yan.aop.aviator.Aviator) "
    )
    default void SysLog() {
    }

    default void check(ProceedingJoinPoint joinPoint) throws Exception {
        Logger log = getLogger();
        Aviator aviator = getAnnotation(joinPoint, Aviator.class);
        AviatorValids aviatorValids = getAnnotation(joinPoint, AviatorValids.class);
        AviatorValid aviatorValid = getAnnotation(joinPoint, AviatorValid.class);
        AviatorNotBlank aviatorNotBlank = getAnnotation(joinPoint, AviatorNotBlank.class);

        Aviator[] aviators = null; 
        AviatorValid[] valids = null;
        AviatorNotBlank[] notBlanks = null;
        List<AviatorValidInfo> validInfos = CollUtil.newArrayList();
        if (ObjectUtils.isNotEmpty(aviator)) {
            aviators = new Aviator[]{aviator};
        }else if (ObjectUtils.isNotEmpty(aviatorValids)) {
            aviators = aviatorValids.aviator();
            valids = aviatorValids.values();
            notBlanks = aviatorValids.notBlanks();
        } else if (ObjectUtils.isNotEmpty(aviatorValid)) {
            valids = new AviatorValid[]{aviatorValid};
        } else if (ObjectUtils.isNotEmpty(aviatorNotBlank)) {
            notBlanks = new AviatorNotBlank[]{aviatorNotBlank};
        }

        if (ObjectUtils.isNotEmpty(aviators)) {
          Arrays.stream(aviators)
                  .map(this::aviatorToValidInfos)
                  .forEach(validInfos::addAll);
            //List<AviatorValidInfo> infoList = aviatorToValidInfos(aviator);
            //validInfos.addAll(infoList);
        }

        if (ObjectUtils.isNotEmpty(notBlanks)) {
            List<AviatorValidInfo> infos = Arrays.stream(notBlanks)
                    .map(this::notBlankToValidInfo)
                    .collect(Collectors.toList());
            validInfos.addAll(infos);
        }

        if (ObjectUtils.isNotEmpty(valids)) {
            List<AviatorValidInfo> infos = Arrays.stream(valids).map(this::validToValidInfo).collect(Collectors.toList());
            validInfos.addAll(infos);
        }

        if (CollUtil.isNotEmpty(validInfos)) {
            Map<String, Object> variables = new LinkedHashMap<>();
            //Map<String, Class> classMaps = new LinkedHashMap<>();

            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Parameter[] parameters = methodSignature.getMethod().getParameters();
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < parameters.length; i++) {
                Object argValue = args[i];
                String name = parameters[i].getName();
                String argsName = String.format("args[%s]", i);
                argValue = JSONUtil.toJsonStr(argValue, jsonConfig);

                variables.put(argsName, argValue);
                variables.put(name, argValue);
                //classMaps.put(argsName, (Class) parameters[i].getParameterizedType());
            }

            Map<String, Object> variablesMap = ObjectUtils.mapKeyLengthReversed(ObjectUtils.toMap(variables));
            variablesMap = ObjectUtils.blankReplace(variablesMap);
            log.info("parameters参数:{}", JSONUtil.toJsonStr(parameters, jsonConfig));
            log.info("variables参数:{}", variables);
            log.info("variablesMap参数:{}", variablesMap);
            //log.info("classMaps参数:{}", classMaps);
            for (AviatorValidInfo validInfo : validInfos) {
                execute(variablesMap, validInfo);
            }
        }
    }

    default List<AviatorValidInfo> aviatorToValidInfos(Aviator aviator) {
        boolean throwException = aviator.throwException();
        String defaultErrorMessage = aviator.defaultErrorMessage();
        boolean isStr = aviator.isStr();
        List<String> errorMessages = Arrays.stream(aviator.errorMessages()).collect(Collectors.toList());
        List<String> expressions = Arrays.stream(aviator.expressions()).collect(Collectors.toList());
        int errorSize = errorMessages.size();
        int okSize;

        if (isStr) {
            List<String> keys = Arrays.stream(aviator.keys()).collect(Collectors.toList());
            okSize = keys.size();
            expressions = keys.stream().map(key -> String.format("%s!=nil&&%s!=''", key, key)).collect(Collectors.toList());
        } else {
            okSize = expressions.size();
        }

        if (okSize > errorSize) {
            for (int i = 0; i < (okSize - errorSize); i++) {
                errorMessages.add(defaultErrorMessage);
            }
        }

        List<AviatorValidInfo> infoList = CollUtil.newArrayList();

        for (int i = 0; i < okSize; i++) {
            String expression = replaceNullToNil(expressions.get(i));
            String errorMessage = errorMessages.get(i);
            infoList.add(toAviatorValidInfo(throwException, expression, errorMessage));
        }

        return infoList;
    }

    /**
     * @param notBlank
     * @return
     */
    default AviatorValidInfo notBlankToValidInfo(AviatorNotBlank notBlank) {
        String errorMessage = notBlank.errorMessage();
        String key = notBlank.key();
        boolean throwException = notBlank.throwException();

        String expression = String.format("%s!=null&&%s!=''", key, key);
        expression = replaceNullToNil(expression);
        return toAviatorValidInfo(throwException, expression, errorMessage);
    }


    /**
     * @param valid
     * @return
     */
    default AviatorValidInfo validToValidInfo(AviatorValid valid) {
        boolean throwException = valid.throwException();
        String expression = valid.expression();
        String errorMessage = valid.errorMessage();
        // null AviatorEvaluator 无法识别  null==> nil
        expression = replaceNullToNil(expression);
        return toAviatorValidInfo(throwException, expression, errorMessage);
    }

    default AviatorValidInfo toAviatorValidInfo(boolean throwException, String expression, String errorMessage) {
        return new AviatorValidInfo()
                .setThrowException(throwException)
                .setExpression(expression)
                .setErrorMessage(errorMessage);
    }

    /**
     * @param variablesMap
     * @param validInfo
     * @throws Exception
     */
    default void execute(Map<String, Object> variablesMap, AviatorValidInfo validInfo) throws Exception {
        Logger log = getLogger();
        Boolean validFail = null;
        boolean expressionThrowException = validInfo.isThrowException();
        String expression = validInfo.getExpression();
        String errorMessage = validInfo.getErrorMessage();
        //expression = expressionReplace(expression, variablesMap);
        // 使用 Aviator 进行表达式校验
        try {
            log.info("aviator表达式:{}", expression);
            Boolean bool = !(Boolean) AviatorEvaluator.execute(expression, variablesMap);
            validFail = ObjectUtils.defaultIfEmpty(bool, true);
        } catch (Exception e) {
            log.error("aviator表达式解析异常:{}", e.getMessage());
            if (expressionThrowException) {
                throw new Exception("aviator表达式解析异常:" + e.getMessage());
            }
        }

        validFail = ObjectUtils.defaultIfEmpty(validFail, true);

        if (validFail) {
            log.error("aviator表达式校验失败:{}", errorMessage);
            throw new Exception(errorMessage);
        }
    }

    default String expressionReplace(String expression, Map<String, Object> variablesMap) {
        for (Map.Entry<String, Object> entry : variablesMap.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (ObjectUtils.isNotEmpty(value)) {
                expression = expression.replace(key, JSONUtil.toJsonStr(value, jsonConfig));
            }
        }
        return expression;
    }

    @NotNull
    default String replaceNullToNil(String expression) {
        return expression.replace("null", "nil");
    }
}

5.2 class AviatorValidAspect

package com.yan.aop.aspect;

import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.yan.utils.object.ObjectUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author yan
 * @Date 2024/11/12 上午1:32:53
 * @Description
 */
@Aspect
@Slf4j
@Component
public class AviatorValidAspect implements AbstractAviatorValidAspect {
    /**
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Override
    @Around(value = "SysLog()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        check(joinPoint);
        return joinPoint.proceed();
    }
}

6.使用

package com.yan.controller;

import cn.hutool.extra.spring.SpringUtil;
import com.yan.abstractinterface.service.AbstractLoginService;
import com.yan.aop.aviator.Aviator;
import com.yan.aop.aviator.AviatorNotBlank;
import com.yan.aop.aviator.AviatorValid;
import com.yan.aop.aviator.AviatorValids;
import com.yan.aop.log.SysLog;
import com.yan.dto.LoginDto;
import com.yan.dto.RegisterDto;
import com.yan.pojo.TokenInfo;
import com.yan.pojo.User;
import com.yan.pojo.UserInfo;
import com.yan.result.Result;
import com.yan.service.ValidateCodeService;
import com.yan.utils.shiro.SecurityContextUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;

/**
 * @Author yan
 * @Date 2024/11/5 下午11:04:45
 * @Description
 */
@RestController
@Tag(name = "登陆注册模块")
@RequestMapping(value = {"/api/auth", "/jwt/auth"})
public class AuthController implements AbstractBaseController {
    /**
     * 查询用户信息列表
     */
    @SysLog(title = "登陆")
    @Operation(summary = "登陆")
    @PostMapping("/login")
    @AviatorValids(
            notBlanks = {
                    @AviatorNotBlank(key = "dto.username", errorMessage = "用户名不能为空"),
                    @AviatorNotBlank(key = "dto.password", errorMessage = "密码不能为空"),
                    @AviatorNotBlank(key = "dto.code", errorMessage = "验证码不能为空"),
                    @AviatorNotBlank(key = "dto.uuid", errorMessage = "验证码唯一值不能为空"),
            },
            values = {
                    @AviatorValid(expression = "dto.captchaEnabled==true && dto.code!=null && dto.code!=''", errorMessage = "验证码不能为空"),
            }
    )
    public Result<TokenInfo> login(@RequestBody LoginDto dto, HttpServletResponse response) {
        SpringUtil.getBean(ValidateCodeService.class)
                .checkCaptcha(dto.getCode(), dto.getUuid());

        UserInfo userInfo = new UserInfo().setPassword(dto.getPassword()).setUsername(dto.getUsername());
        TokenInfo tokenInfo = SpringUtil.getBean(AbstractLoginService.class).login(userInfo);

        response.setHeader(tokenInfo.getTokenName(), tokenInfo.getToken());
        if (tokenInfo.getEnableTwoToken()) {
            response.setHeader(tokenInfo.getRefreshTokenName(), tokenInfo.getRefreshToken());
        }
        return ok(tokenInfo);
    }

    @SysLog(title = "注册")
    @Operation(summary = "注册")
    @PostMapping("/register")
    @AviatorValids(values = {
            @AviatorValid(expression = "dto.password != dto.password2", errorMessage = "密码不一致")
    })
    public Result<User> register(@Validated @NotNull @RequestBody RegisterDto dto) {
        User register = SpringUtil.getBean(AbstractLoginService.class)
                .register(dto.getNickname(), dto.getPassword(), dto.getPassword2());
        return ok(register);
    }

    @SysLog(title = "登出")
    @Operation(summary = "登出")
    @PostMapping("/logout")
    public Result logout() {
        SecurityContextUtil.logout();
        return ok();
    }
}