SpringBoot+AOP+ThreadLocal切面实现用户操作记录-超级详细

先上效果图

 第一步、导入需要用到的包

        <!-- 获取浏览器信息 -->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.21</version>
        </dependency>

第二步、自定义注解Log

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 编辑的表主键
     * @return
     */
    String editTableId() default "id";

    /**
     * 编辑的表名称
     * @return
     */
    String editTableName() default "未知";
}

第三步、切面类


import com.fasterxml.jackson.databind.ObjectMapper;
import com.sifan.erp.domain.LogDO;
import com.sifan.erp.mapper.SysLogMapper;
import com.sifan.erp.utils.CommonUtil;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class LogAspect {

    @Resource
    private SysLogMapper logMapper;
    @Value("${spring.application.name}")
    private String appNname;
    //定义切入点(有注解@Log的方法为切入点)
    @Pointcut("@annotation(com.sifan.erp.annotation.Log)")
    public void logPoint() {}

    @Around("logPoint()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        Object result = null;
        LogDO logDO = new LogDO();
        try {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = (HttpServletRequest) requestAttributes
                    .resolveReference(RequestAttributes.REFERENCE_REQUEST);
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

            UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
            //浏览器对象
            Browser browser = userAgent.getBrowser();
            //操作系统对象
            OperatingSystem operatingSystem = userAgent.getOperatingSystem();
            logDO.setBrowserName(browser.getName());
            ApiOperation aon = methodSignature.getMethod().getAnnotation(ApiOperation.class);
            if (aon != null) {
                logDO.setModuleName(aon.value());
            }
            logDO.setOsName(operatingSystem.getName());
            logDO.setIpAddr(CommonUtil.getIpAddr(request));
            logDO.setAppName(appNname);
            logDO.setClassName(joinPoint.getTarget().getClass().getName());
            logDO.setMethodName(methodSignature.getMethod().getName());
            logDO.setRequestUrl(request.getRequestURI());
            logDO.setRequestMethod(request.getMethod());
            //获取请求参数
            CommonUtil.getRequestParam(joinPoint, methodSignature, logDO);
            logDO.setResultText(new ObjectMapper().writeValueAsString(result));
            logDO.setStatus(0);
            logDO.setCreateTime(CommonUtil.getCurrentDate());
            logDO.setCreateUserId(CommonUtil.getCurrentUserId());
            logDO.setCreatePhoneNumber(CommonUtil.getCurrentPhoneNumber());
            logDO.setCreateUserName(CommonUtil.getCurrentUserName());
            long startTime = System.currentTimeMillis();
            //方法执行
            result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            logDO.setTakeUpTime(String.format("耗时:%s 毫秒", endTime - startTime));
            if(result != null){
                logDO.setResultText(result.toString());
            }
        } catch (Exception e) {
            log.error("记录用户日志信息出现异常 {}",e);
            logDO.setStatus(1);
                logDO.setErrorText(e.toString());
                  log.info("Controller出现异常移除ThreadLocal中的token:{}", UserUtil.getLoginUser());
            UserUtil.removeUser();
                result = e.toString();
        } finally {
            logMapper.insert(logDO);
            log.info("用户 {} 日志插入成功",logDO.getCreateUserId());
        }
        return result;
    }

}

第四步、建表,表可以根据自己需求增减字段

DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `module_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '模块名称',
  `browser_name` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '浏览器名称',
  `os_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作系统名称',
  `ip_addr` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求ip',
  `app_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '服务名称',
  `class_name` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类名',
  `method_name` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '方法',
  `request_url` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求url',
  `request_method` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求方式,POST、GET',
  `request_param` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '请求参数',
  `result_text` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '响应参数',
  `status` tinyint(1) NULL DEFAULT NULL COMMENT '接口状态(0成功 1失败)',
  `error_text` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '错误信息',
  `take_up_time` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '耗时',
  `edit_table_id` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编辑的表主键,只有修改时才有值',
  `edit_table_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编辑的表名称,只有修改时才有值',
  `create_time` datetime NULL DEFAULT NULL COMMENT '操作时间',
  `create_user_id` int(11) NULL DEFAULT NULL COMMENT '创建人id',
  `create_phone_number` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人手机号',
  `create_user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人姓名',
  `seller_account_id` int(11) NULL DEFAULT NULL COMMENT '店铺id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统操作日志' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

第五步、实体类

idea使用mybatisx插件自动生成实体类

 config name 这里只留下mapperInterface,点击Finish就搞定了。

第六步、工具类UserUtil

这个工具类的作用就是保存用户信息。

package com.sifan.erp.utils;

import com.sifan.erp.domain.User;

/**
 *	存储/获取当前线程的用户信息工具类
 */
public abstract class UserUtil {

    //线程变量,存放user实体类信息,即使是静态的与其他线程也是隔离的
    private static ThreadLocal<User> userThreadLocal = new ThreadLocal<User>();

    //从当前线程变量中获取用户信息
    public static User getLoginUser() {
        User user = userThreadLocal.get();
        return user;
    }

    /**
     * 获取当前登录用户的ID
     * 未登录返回null
     *
     * @return
     */
    public static Integer getLoginUserId() {
        User user = userThreadLocal.get();
        if (user != null && user.getId() != null) {
            return user.getId();
        }
        return null;
    }

    //为当前的线程变量赋值上用户信息
    public static void setLoginUser(User user) {
        userThreadLocal.set(user);
    }

    //清除线程变量
    public static void removeUser() {
        userThreadLocal.remove();
    }
}

 第七步、工具类CommonUtil

package com.sifan.erp.utils;

import com.sifan.erp.annotation.Log;
import com.sifan.erp.domain.LogDO;
import com.sifan.erp.domain.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

/**
 * 公共工具类
 */


public class CommonUtil {

    private static final Logger log = LoggerFactory.getLogger(CommonUtil.class);

    /**
     * 获取ip
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        log.info("ip获取失败");
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) {
                // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }

    /**
     * 获取当前时间戳
     *
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis();
    }

    /**
     * 获取当前日期
     *
     * @return
     */
    public static Date getCurrentDate() {
        return new Date();
    }

    /**
     * 获取当前操作用户的主键id
     *
     * @return
     */
    public static Integer getCurrentUserId() {
        User loginUser = getLoginUser();
        if (loginUser == null) {
            return null;
        }
        return loginUser.getId();
    }

    private static User getLoginUser() {
        return UserUtil.getLoginUser();
    }

    /**
     * 获取当前操作用户的手机号
     *
     * @return
     */
    public static String getCurrentPhoneNumber() {
        User loginUser = getLoginUser();
        if (loginUser == null) {
            return null;
        }
        return loginUser.getPhoneNumber();
    }

    /**
     * 获取当前操作用户的名称
     *
     * @return
     */
    public static String getCurrentUserName() {
        User loginUser = getLoginUser();
        if (loginUser == null) {
            return null;
        }
        return loginUser.getUsername();
    }

    /**
     * 判断当前用户是否管理员
     */
    public static boolean isAdmin() {
        return getLoginUser().getUserRole() == 1;
    }


    /**
     * 获取请求参数
     *
     * @param joinPoint 切入点
     * @param signature 方法签名
     * @param logDO     日志对象
     */
    public static void getRequestParam(ProceedingJoinPoint joinPoint, MethodSignature signature, LogDO logDO) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        Method method = signature.getMethod();
        String[] parameterNames = pnd.getParameterNames(method);
        Map<String, Object> paramMap = new HashMap<>(32);
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        //获取类的字节码对象,通过字节码对象获取方法信息
        Class<?> targetCls=joinPoint.getTarget().getClass();
        try {
            //获取目标方法上的注解指定的操作名称
            Method targetMethod=
                    targetCls.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
            //获取方法上的@Log注解
            Log requiredLog=
                    targetMethod.getAnnotation(Log.class);
            //获取注解中的值
            String editTableId=requiredLog.editTableId();
            String editTableName = requiredLog.editTableName();
            logDO.setEditTableId(editTableId);
            logDO.setEditTableName(editTableName);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        logDO.setRequestParam(paramMap.toString());
    }

}

RedisUtil

package com.sifan.erp.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Resource
    private RedisTemplate redisTemplate;
    /**
     * 给一个指定的 key 值附加过期时间
     *
     * @param key
     * @param time
     * @return
     */
    public boolean expire(String key, long time) {
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }
    /**
     * 根据key 获取过期时间
     *
     * @param key
     * @return
     */
    public long getTime(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * 根据key 获取过期时间
     *
     * @param key
     * @return
     */
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
    /**
     * 移除指定key 的过期时间
     *
     * @param key
     * @return
     */
    public boolean persist(String key) {
        return redisTemplate.boundValueOps(key).persist();
    }

    //- - - - - - - - - - - - - - - - - - - - -  String类型 - - - - - - - - - - - - - - - - - - - -

    /**
     * 根据key获取值
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 将值放入缓存
     *
     * @param key   键
     * @param value 值
     * @return true成功 false 失败
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 将对象放入到redis中
     * @param key 键
     * @param value 值
     * @param time 时间(秒) -1为无期限
     */
    public void setObject(String key, Object value,long time) {
        redisTemplate.opsForValue().set(key,value,time, TimeUnit.SECONDS);
    }


    /**
     * 将值放入缓存并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) -1为无期限
     * @return true成功 false 失败
     */
    public void set(String key, String value, long time) {
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(key, value);
        }
    }

    /**
     * 批量添加 key (重复的键会覆盖)
     *
     * @param keyAndValue
     */
    public void batchSet(Map<String, String> keyAndValue) {
        redisTemplate.opsForValue().multiSet(keyAndValue);
    }

    /**
     * 批量添加 key-value 只有在键不存在时,才添加
     * map 中只要有一个key存在,则全部不添加
     *
     * @param keyAndValue
     */
    public void batchSetIfAbsent(Map<String, String> keyAndValue) {
        redisTemplate.opsForValue().multiSetIfAbsent(keyAndValue);
    }

    /**
     * 对一个 key-value 的值进行加减操作,
     * 如果该 key 不存在 将创建一个key 并赋值该 number
     * 如果 key 存在,但 value 不是长整型 ,将报错
     *
     * @param key
     * @param number
     */
    public Long increment(String key, long number) {
        return redisTemplate.opsForValue().increment(key, number);
    }

    /**
     * 对一个 key-value 的值进行加减操作,
     * 如果该 key 不存在 将创建一个key 并赋值该 number
     * 如果 key 存在,但 value 不是 纯数字 ,将报错
     *
     * @param key
     * @param number
     */
    public Double increment(String key, double number) {
        return redisTemplate.opsForValue().increment(key, number);
    }

    //- - - - - - - - - - - - - - - - - - - - -  set类型 - - - - - - - - - - - - - - - - - - - -

    /**
     * 将数据放入set缓存
     *
     * @param key 键
     * @return
     */
    public void sSet(String key, String value) {
        redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 获取变量中的值
     *
     * @param key 键
     * @return
     */
    public Set<Object> members(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 随机获取变量中指定个数的元素
     *
     * @param key   键
     * @param count 值
     * @return
     */
    public void randomMembers(String key, long count) {
        redisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 随机获取变量中的元素
     *
     * @param key 键
     * @return
     */
    public Object randomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 弹出变量中的元素
     *
     * @param key 键
     * @return
     */
    public Object pop(String key) {
        return redisTemplate.opsForSet().pop("setValue");
    }

    /**
     * 获取变量中值的长度
     *
     * @param key 键
     * @return
     */
    public long size(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 检查给定的元素是否在变量中。
     *
     * @param key 键
     * @param obj 元素对象
     * @return
     */
    public boolean isMember(String key, Object obj) {
        return redisTemplate.opsForSet().isMember(key, obj);
    }

    /**
     * 转移变量的元素值到目的变量。
     *
     * @param key     键
     * @param value   元素对象
     * @param destKey 元素对象
     * @return
     */
    public boolean move(String key, String value, String destKey) {
        return redisTemplate.opsForSet().move(key, value, destKey);
    }

    /**
     * 批量移除set缓存中元素
     *
     * @param key    键
     * @param values 值
     * @return
     */
    public void remove(String key, Object... values) {
        redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 通过给定的key求2个set变量的差值
     *
     * @param key     键
     * @param destKey 键
     * @return
     */
    public Set<Set> difference(String key, String destKey) {
        return redisTemplate.opsForSet().difference(key, destKey);
    }


    //- - - - - - - - - - - - - - - - - - - - -  hash类型 - - - - - - - - - - - - - - - - - - - -

    /**
     * 加入缓存
     *
     * @param key 键
     * @param map 键
     * @return
     */
    public void add(String key, Map<String, String> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 获取 key 下的 所有  hashkey 和 value
     *
     * @param key 键
     * @return
     */
    public Map<Object, Object> getHashEntries(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 验证指定 key 下 有没有指定的 hashkey
     *
     * @param key
     * @param hashKey
     * @return
     */
    public boolean hashKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 获取指定key的值string
     *
     * @param key  键
     * @param key2 键
     * @return
     */
    public String getMapString(String key, String key2) {
        return redisTemplate.opsForHash().get("map1", "key1").toString();
    }

    /**
     * 获取指定的值Int
     *
     * @param key  键
     * @param key2 键
     * @return
     */
    public Integer getMapInt(String key, String key2) {
        return (Integer) redisTemplate.opsForHash().get("map1", "key1");
    }

    /**
     * 弹出元素并删除
     *
     * @param key 键
     * @return
     */
    public String popValue(String key) {
        return redisTemplate.opsForSet().pop(key).toString();
    }

    /**
     * 删除指定 hash 的 HashKey
     *
     * @param key
     * @param hashKeys
     * @return 删除成功的 数量
     */
    public Long delete(String key, String... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    /**
     * 给指定 hash 的 hashkey 做增减操作
     *
     * @param key
     * @param hashKey
     * @param number
     * @return
     */
    public Long increment(String key, String hashKey, long number) {
        return redisTemplate.opsForHash().increment(key, hashKey, number);
    }

    /**
     * 给指定 hash 的 hashkey 做增减操作
     *
     * @param key
     * @param hashKey
     * @param number
     * @return
     */
    public Double increment(String key, String hashKey, Double number) {
        return redisTemplate.opsForHash().increment(key, hashKey, number);
    }

    /**
     * 获取 key 下的 所有 hashkey 字段
     *
     * @param key
     * @return
     */
    public Set<Object> hashKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取指定 hash 下面的 键值对 数量
     *
     * @param key
     * @return
     */
    public Long hashSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    //- - - - - - - - - - - - - - - - - - - - -  list类型 - - - - - - - - - - - - - - - - - - - -

    /**
     * 在变量左边添加元素值
     *
     * @param key
     * @param value
     * @return
     */
    public void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 获取集合指定位置的值。
     *
     * @param key
     * @param index
     * @return
     */
    public Object index(String key, long index) {
        return redisTemplate.opsForList().index("list", 1);
    }

    /**
     * 获取指定区间的值。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 把最后一个参数值放到指定集合的第一个出现中间参数的前面,
     * 如果中间参数值存在的话。
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public void leftPush(String key, String pivot, String value) {
        redisTemplate.opsForList().leftPush(key, pivot, value);
    }

    /**
     * 向左边批量添加参数元素。
     *
     * @param key
     * @param values
     * @return
     */
    public void leftPushAll(String key, String... values) {
//        redisTemplate.opsForList().leftPushAll(key,"w","x","y");
        redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 向集合最右边添加元素。
     *
     * @param key
     * @param value
     * @return
     */
    public void leftPushAll(String key, String value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 向左边批量添加参数元素。
     *
     * @param key
     * @param values
     * @return
     */
    public void rightPushAll(String key, String... values) {
        //redisTemplate.opsForList().leftPushAll(key,"w","x","y");
        redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 向已存在的集合中添加元素。
     *
     * @param key
     * @param value
     * @return
     */
    public void rightPushIfPresent(String key, Object value) {
        redisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 向已存在的集合中添加元素。
     *
     * @param key
     * @return
     */
    public long listLength(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 移除集合中的左边第一个元素。
     *
     * @param key
     * @return
     */
    public void leftPop(String key) {
        redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
     *
     * @param key
     * @return
     */
    public void leftPop(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForList().leftPop(key, timeout, unit);
    }

    /**
     * 移除集合中右边的元素。
     *
     * @param key
     * @return
     */
    public void rightPop(String key) {
        redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
     *
     * @param key
     * @return
     */
    public void rightPop(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForList().rightPop(key, timeout, unit);
    }
}

第八步、过滤器

现在UserUtil中的ThreadLocal是没有用户信息的,需要一个过滤器拿到token,获取到用户信息设置到ThreadLocal,我这个只能从请求头和请求参数中获取token,如果需要从请求体获取token的话需要自己实现。

package com.sifan.erp.filter;

import com.sifan.erp.domain.User;
import com.sifan.erp.utils.RedisUtil;
import com.sifan.erp.utils.UserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * * 将请求头或请求参数中的token对应的用户对象存到ThreadLocal中
 */
@Component
@WebFilter(filterName = "UserFilter",urlPatterns = {"/*"})
public class TokenFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
    @Resource
    private RedisUtil redisUtil;
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = null;
        //获取请求头中的token
        String headerToken = request.getHeader("token");
        //获取请求参数中的token
        String paramToken = request.getParameter("token");

        if(headerToken!= null){
            token = headerToken;
        }else if(paramToken!=null){
            token = paramToken;
        }
        Object obj = redisUtil.get(token);
        User user = null;
        if(obj != null){
            user = (User)obj;
        }
        log.info("过滤器中得到的用户的用户:{}",user);
        UserUtil.setLoginUser(user);
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

第九步、拦截器UserLoginInterceptor

这个拦截器的作用一是检查过滤器是否获取到用户信息,如果没有,则是未登录,不让访问接口。

作用二是接口执行完毕移除用户信息,避免内存泄漏

package com.sifan.erp.interceptor;

import com.sifan.erp.domain.User;
import com.sifan.erp.utils.UserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserLoginInterceptor  implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(UserLoginInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //这里可以直接在拦截器中再次获取用户信息
        User user = UserUtil.getLoginUser();
        if (user == null || user.getId() == null) {
            response.setStatus(401);
            log.info("用户没有登录");
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //controller方法执行完毕
        UserUtil.removeUser();  //其中调用的就是 userThreadLocal.remove();
    }
}

注意:Controller执行过程中发生异常可能导致afterCompletion方法没执行,所以需要写一个全局异常处理器来移除ThreadLocal中的用户。这是没加Log注解的Controller方法会走地方,加了Log注解的在切面方法会catch到异常,哪个地方也需要移除用户。

import com.sifan.erp.utils.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public String handler(RuntimeException exception) {
        log.error(exception.getMessage());
        log.info("Controller出现异常移除ThreadLocal中的token");
        UserUtil.removeUser();
        return "500";
    }
}

第十步、配置拦截器

未登录就能访问的接口也需要自己移除。

package com.sifan.erp.config;

import com.sifan.erp.interceptor.UserLoginInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    /**
     * 配置拦截器*
     *
     * @param interceptorRegistry
     */
    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
        InterceptorRegistration registry = interceptorRegistry.addInterceptor(new UserLoginInterceptor());

        //拦截所有请求,根据自己的需求配置
        registry.addPathPatterns("/**")
                .excludePathPatterns("/user/login", "/favicon.ico", "/error", "/", "/index", "/login", "/user/register",
                        "/views/static/css/**", "/views/static/font/**", "/views/static/images/**", "/views/static/js/**");

    }

    /**
     * 配置静态资源路径,这里是静态资源位置,不需要的删掉即可*
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/views/static/**").addResourceLocations("classpath:/views/static/");
    }
}

第十一步、测试

只需要在使用的方法上面加注解@Log

@ApiOperation(value = "登录接口")这里写接口的名称即可
package com.sifan.erp.controller;

import com.sifan.erp.annotation.Log;
import com.sifan.erp.domain.User;
import com.sifan.erp.service.UserService;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/user")
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);

    @Resource
    private UserService userService;


   @Log(editTableId = "id",editTableName = "user")
   @ApiOperation(value = "登录接口")
    @PostMapping("/login")
    public String login(User loginUser){
        log.info("登录的用户信息:{}",loginUser);
        String token = userService.login(loginUser);
        return token;
    }
    @Log(editTableId = "id",editTableName = "user")
    @ApiOperation(value = "登出接口")
    @GetMapping("/logout")
    public void logout(String token){
        userService.logout(token);
        log.info("登出成功");
    }

}

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值