Kaynağa Gözat

接入登陆日志

YunaiV 4 yıl önce
ebeveyn
işleme
083dac77e1
17 değiştirilmiş dosya ile 236 ekleme ve 52 silme
  1. 5 6
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java
  2. 2 6
      src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java
  3. 4 0
      src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/SysLoginLogController.java
  4. 4 8
      src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/loginlog/SysLoginLogBaseVO.java
  5. 1 2
      src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/loginlog/SysLoginLogCreateReqVO.java
  6. 15 0
      src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysLoginLogConvert.java
  7. 9 0
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysLoginLogMapper.java
  8. 17 11
      src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysLoginLogDO.java
  9. 2 0
      src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java
  10. 21 0
      src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysLoginLogTypeEnum.java
  11. 6 0
      src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysLoginResultEnum.java
  12. 44 19
      src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java
  13. 20 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/common/SysCaptchaService.java
  14. 10 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/common/impl/SysCaptchaServiceImpl.java
  15. 17 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysLoginLogService.java
  16. 27 0
      src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysLoginLogServiceImpl.java
  17. 32 0
      src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java

+ 5 - 6
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java

@@ -1,6 +1,7 @@
 package com.ruoyi.web.controller.monitor;
 
 import java.util.List;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.DeleteMapping;
@@ -24,15 +25,14 @@ import com.ruoyi.system.service.ISysLogininforService;
  */
 @RestController
 @RequestMapping("/monitor/logininfor")
-public class SysLogininforController extends BaseController
-{
+public class SysLogininforController extends BaseController {
+
     @Autowired
     private ISysLogininforService logininforService;
 
     @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')")
     @GetMapping("/list")
-    public TableDataInfo list(SysLogininfor logininfor)
-    {
+    public TableDataInfo list(SysLogininfor logininfor) {
         startPage();
         List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
         return getDataTable(list);
@@ -41,8 +41,7 @@ public class SysLogininforController extends BaseController
     @Log(title = "登录日志", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')")
     @GetMapping("/export")
-    public AjaxResult export(SysLogininfor logininfor)
-    {
+    public AjaxResult export(SysLogininfor logininfor) {
         List<SysLogininfor> list = logininforService.selectLogininforList(logininfor);
         ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class);
         return util.exportExcel(list, "登录日志");

+ 2 - 6
src/main/java/cn/iocoder/dashboard/framework/logger/operatelog/core/aop/OperateLogAspect.java

@@ -24,9 +24,6 @@ import org.aspectj.lang.reflect.MethodSignature;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.context.request.RequestAttributes;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
@@ -196,11 +193,10 @@ public class OperateLogAspect {
 
     private static void fillRequestFields(SysOperateLogCreateReqVO operateLogVO) {
         // 获得 Request 对象
-        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
-        if (!(requestAttributes instanceof ServletRequestAttributes)) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        if (request == null) {
             return;
         }
-        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
         // 补全请求信息
         operateLogVO.setRequestMethod(request.getMethod());
         operateLogVO.setRequestUrl(request.getRequestURI());

+ 4 - 0
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/SysLoginLogController.java

@@ -0,0 +1,4 @@
+package cn.iocoder.dashboard.modules.system.controller.logger;
+
+public class SysLoginLogController {
+}

+ 4 - 8
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/loginlog/SysLoginLogBaseVO.java

@@ -15,27 +15,23 @@ import javax.validation.constraints.Size;
 @Data
 public class SysLoginLogBaseVO {
 
+    @ApiModelProperty(value = "日志类型", required = true, example = "1", notes = "参见 SysLoginLogTypeEnum 枚举类")
+    @NotNull(message = "日志类型不能为空")
+    private Integer logType;
+
     @ApiModelProperty(value = "链路追踪编号", required = true, example = "89aca178-a370-411c-ae02-3f0d672be4ab")
     @NotEmpty(message = "链路追踪编号不能为空")
     private String traceId;
 
-    @ApiModelProperty(value = "用户编号", required = true, example = "1024")
-    @NotNull(message = "用户编号不能为空")
-    private Long userId;
-
     @ApiModelProperty(value = "用户账号", required = true, example = "yudao")
     @NotBlank(message = "用户账号不能为空")
     @Size(max = 30, message = "用户账号长度不能超过30个字符")
     private String username;
 
-
     @ApiModelProperty(value = "登陆结果", required = true, example = "1", notes = "参见 SysLoginResultEnum 枚举类")
     @NotNull(message = "登陆结果不能为空")
     private Integer result;
 
-    @ApiModelProperty(value = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。")
-    private String content;
-
     @ApiModelProperty(value = "用户 IP", required = true, example = "127.0.0.1")
     @NotEmpty(message = "用户 IP 不能为空")
     private String userIp;

+ 1 - 2
src/main/java/cn/iocoder/dashboard/modules/system/controller/logger/vo/loginlog/SysLoginLogCreateReqVO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog;
 
-import cn.iocoder.dashboard.modules.system.controller.logger.vo.operatelog.SysOperateLogCreateReqVO;
 import io.swagger.annotations.ApiModel;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -11,5 +10,5 @@ import lombok.ToString;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class SysLoginLogCreateReqVO extends SysOperateLogCreateReqVO {
+public class SysLoginLogCreateReqVO extends SysLoginLogBaseVO {
 }

+ 15 - 0
src/main/java/cn/iocoder/dashboard/modules/system/convert/logger/SysLoginLogConvert.java

@@ -0,0 +1,15 @@
+package cn.iocoder.dashboard.modules.system.convert.logger;
+
+import cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog.SysLoginLogCreateReqVO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysLoginLogDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SysLoginLogConvert {
+
+    SysLoginLogConvert INSTANCE = Mappers.getMapper(SysLoginLogConvert.class);
+
+    SysLoginLogDO convert(SysLoginLogCreateReqVO bean);
+
+}

+ 9 - 0
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/logger/SysLoginLogMapper.java

@@ -0,0 +1,9 @@
+package cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger;
+
+import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysLoginLogDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SysLoginLogMapper extends BaseMapperX<SysLoginLogDO> {
+}

+ 17 - 11
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/logger/SysLoginLogDO.java

@@ -1,16 +1,24 @@
 package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger;
 
 import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
+import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginLogTypeEnum;
 import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginResultEnum;
 import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
 
 /**
- * 系统访问记录表
+ * 登陆日志表
+ *
+ * 注意,包括登陆和登出两种行为
  *
  * @author ruoyi
  */
-@TableName("用户登陆日志")
+@TableName("sys_login_log")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
 public class SysLoginLogDO extends BaseDO {
 
     /**
@@ -18,15 +26,15 @@ public class SysLoginLogDO extends BaseDO {
      */
     private Long id;
     /**
-     * 链路追踪编号
+     * 日志类型
+     *
+     * 枚举 {@link SysLoginLogTypeEnum}
      */
-    private String traceId;
+    private Integer logType;
     /**
-     * 用户编号
-     *
-     * 外键 {@link SysUserDO#getId()}
+     * 链路追踪编号
      */
-    private Long userId;
+    private String traceId;
     /**
      * 用户账号
      *
@@ -39,12 +47,10 @@ public class SysLoginLogDO extends BaseDO {
      * 枚举 {@link SysLoginResultEnum}
      */
     private Integer result;
-
     /**
      * 用户 IP
      */
     private String userIp;
-
     /**
      * 浏览器 UA
      */

+ 2 - 0
src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java

@@ -13,6 +13,8 @@ public interface SysErrorCodeConstants {
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1002000000, "登录失败,账号密码不正确");
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1002000001, "登录失败,账号被禁用");
     ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1002000002, "登录失败"); // 登陆失败的兜底,位置原因
+    ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
+    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
 
     // ========== TOKEN 模块 1002001000 ==========
     ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");

+ 21 - 0
src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysLoginLogTypeEnum.java

@@ -0,0 +1,21 @@
+package cn.iocoder.dashboard.modules.system.enums.logger;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 登陆日志的类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum SysLoginLogTypeEnum {
+
+    LOGIN(1),
+    LOGOUT(2);
+
+    /**
+     * 日志类型
+     */
+    private final Integer type;
+
+}

+ 6 - 0
src/main/java/cn/iocoder/dashboard/modules/system/enums/logger/SysLoginResultEnum.java

@@ -11,6 +11,12 @@ import lombok.Getter;
 public enum SysLoginResultEnum {
 
     SUCCESS(0), // 成功
+    BAD_CREDENTIALS(10), // 账号或密码不正确
+    USER_DISABLED(20), // 账号或密码不正确
+    CAPTCHA_NOT_FOUND(30), // 验证码不存在
+    CAPTCHA_CODE_ERROR(31), // 验证码不正确
+
+    UNKNOWN_ERROR(100), // 未知异常
     ;
 
     /**

+ 44 - 19
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java

@@ -3,16 +3,24 @@ package cn.iocoder.dashboard.modules.system.service.auth.impl;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
 import cn.iocoder.dashboard.framework.security.core.LoginUser;
+import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils;
+import cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog.SysLoginLogCreateReqVO;
 import cn.iocoder.dashboard.modules.system.convert.auth.SysAuthConvert;
 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
 import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO;
+import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginLogTypeEnum;
+import cn.iocoder.dashboard.modules.system.enums.logger.SysLoginResultEnum;
 import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService;
 import cn.iocoder.dashboard.modules.system.service.auth.SysTokenService;
+import cn.iocoder.dashboard.modules.system.service.common.SysCaptchaService;
+import cn.iocoder.dashboard.modules.system.service.logger.SysLoginLogService;
 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
 import cn.iocoder.dashboard.util.date.DateUtils;
+import cn.iocoder.dashboard.util.servlet.ServletUtils;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.JwtException;
 import lombok.extern.slf4j.Slf4j;
@@ -55,6 +63,10 @@ public class SysAuthServiceImpl implements SysAuthService {
     private SysUserService userService;
     @Resource
     private SysPermissionService permissionService;
+    @Resource
+    private SysCaptchaService captchaService;
+    @Resource
+    private SysLoginLogService loginLogService;
 
     @Resource
     private SysLoginUserRedisDAO loginUserRedisDAO;
@@ -87,7 +99,7 @@ public class SysAuthServiceImpl implements SysAuthService {
     @Override
     public String login(String username, String password, String captchaUUID, String captchaCode) {
         // 判断验证码是否正确
-        this.verifyCaptcha(captchaUUID, captchaCode);
+        this.verifyCaptcha(username, captchaUUID, captchaCode);
 
         // 使用账号密码,进行登陆。
         LoginUser loginUser = this.login0(username, password);
@@ -102,18 +114,20 @@ public class SysAuthServiceImpl implements SysAuthService {
         return tokenService.createToken(sessionId);
     }
 
-    private void verifyCaptcha(String captchaUUID, String captchaCode) {
-        //        String verifyKey = Constants.CAPTCHA_CODE_KEY + captchaUUID;
-//        String captcha = redisCache.getCacheObject(verifyKey);
-//        redisCache.deleteObject(verifyKey);
-//        if (captcha == null) {
-//            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
-//            throw new CaptchaExpireException();
-//        }
-//        if (!code.equalsIgnoreCase(captcha)) {
-//            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
-//            throw new CaptchaException();
-//        }
+    private void verifyCaptcha(String username, String captchaUUID, String captchaCode) {
+        String code = captchaService.getCaptchaCode(captchaUUID);
+        // 验证码不存在
+        if (code == null) {
+            this.createLoginLog(username, SysLoginResultEnum.CAPTCHA_NOT_FOUND);
+            throw ServiceExceptionUtil.exception(AUTH_LOGIN_CAPTCHA_NOT_FOUND);
+        }
+        // 验证码不正确
+        if (!code.equals(captchaCode)) {
+            this.createLoginLog(username, SysLoginResultEnum.CAPTCHA_CODE_ERROR);
+            throw ServiceExceptionUtil.exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR);
+        }
+        // 正确,所以要删除下验证码
+        captchaService.deleteCaptchaCode(captchaUUID);
     }
 
     private LoginUser login0(String username, String password) {
@@ -124,22 +138,33 @@ public class SysAuthServiceImpl implements SysAuthService {
             // 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
             authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
         } catch (BadCredentialsException badCredentialsException) {
-            // TODO 日志优化
-//            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+            this.createLoginLog(username, SysLoginResultEnum.BAD_CREDENTIALS);
             throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
         } catch (DisabledException disabledException) {
-            // TODO 日志优化
+            this.createLoginLog(username, SysLoginResultEnum.USER_DISABLED);
             throw exception(AUTH_LOGIN_USER_DISABLED);
         } catch (AuthenticationException authenticationException) {
-            // TODO 日志优化
+            log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
+            this.createLoginLog(username, SysLoginResultEnum.UNKNOWN_ERROR);
             throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
         }
-        // TODO 需要优化
-//        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        // 登陆成功
         Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
+        this.createLoginLog(username, SysLoginResultEnum.SUCCESS);
         return (LoginUser) authentication.getPrincipal();
     }
 
+    private void createLoginLog(String username, SysLoginResultEnum loginResult) {
+        SysLoginLogCreateReqVO reqVO = new SysLoginLogCreateReqVO();
+        reqVO.setLogType(SysLoginLogTypeEnum.LOGIN.getType());
+        reqVO.setTraceId(TracerUtils.getTraceId());
+        reqVO.setUsername(username);
+        reqVO.setUserAgent(ServletUtils.getUserAgent());
+        reqVO.setUserIp(ServletUtils.getClientIP());
+        reqVO.setResult(loginResult.getResult());
+        loginLogService.createLoginLog(reqVO);
+    }
+
     /**
      * 获得 User 拥有的角色编号数组
      *

+ 20 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/common/SysCaptchaService.java

@@ -7,6 +7,26 @@ import cn.iocoder.dashboard.modules.system.controller.common.vo.SysCaptchaImageR
  */
 public interface SysCaptchaService {
 
+    /**
+     * 获得验证码图片
+     *
+     * @return 验证码图片
+     */
     SysCaptchaImageRespVO getCaptchaImage();
 
+    /**
+     * 获得 uuid 对应的验证码
+     *
+     * @param uuid 验证码编号
+     * @return 验证码
+     */
+    String getCaptchaCode(String uuid);
+
+    /**
+     * 删除 uuid 对应的验证码
+     *
+     * @param uuid 验证码编号
+     */
+    void deleteCaptchaCode(String uuid);
+
 }

+ 10 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/common/impl/SysCaptchaServiceImpl.java

@@ -35,4 +35,14 @@ public class SysCaptchaServiceImpl implements SysCaptchaService {
         return SysCaptchaConvert.INSTANCE.convert(uuid, captcha);
     }
 
+    @Override
+    public String getCaptchaCode(String uuid) {
+        return captchaRedisDAO.get(uuid);
+    }
+
+    @Override
+    public void deleteCaptchaCode(String uuid) {
+        captchaRedisDAO.delete(uuid);
+    }
+
 }

+ 17 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/logger/SysLoginLogService.java

@@ -0,0 +1,17 @@
+package cn.iocoder.dashboard.modules.system.service.logger;
+
+import cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog.SysLoginLogCreateReqVO;
+
+/**
+ * 登陆日志 Service 接口
+ */
+public interface SysLoginLogService {
+
+    /**
+     * 创建登陆日志
+     *
+     * @param reqVO 日志信息
+     */
+    void createLoginLog(SysLoginLogCreateReqVO reqVO);
+
+}

+ 27 - 0
src/main/java/cn/iocoder/dashboard/modules/system/service/logger/impl/SysLoginLogServiceImpl.java

@@ -0,0 +1,27 @@
+package cn.iocoder.dashboard.modules.system.service.logger.impl;
+
+import cn.iocoder.dashboard.modules.system.controller.logger.vo.loginlog.SysLoginLogCreateReqVO;
+import cn.iocoder.dashboard.modules.system.convert.logger.SysLoginLogConvert;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dao.logger.SysLoginLogMapper;
+import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.logger.SysLoginLogDO;
+import cn.iocoder.dashboard.modules.system.service.logger.SysLoginLogService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 登陆日志 Service 实现
+ */
+@Service
+public class SysLoginLogServiceImpl implements SysLoginLogService {
+
+    @Resource
+    private SysLoginLogMapper loginLogMapper;
+
+    @Override
+    public void createLoginLog(SysLoginLogCreateReqVO reqVO) {
+        SysLoginLogDO loginLog = SysLoginLogConvert.INSTANCE.convert(reqVO);
+        loginLogMapper.insert(loginLog);
+    }
+
+}

+ 32 - 0
src/main/java/cn/iocoder/dashboard/util/servlet/ServletUtils.java

@@ -4,6 +4,9 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.extra.servlet.ServletUtil;
 import com.alibaba.fastjson.JSON;
 import org.springframework.http.MediaType;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -54,4 +57,33 @@ public class ServletUtils {
         return ua != null ? ua : "";
     }
 
+    /**
+     * 获得请求
+     *
+     * @return HttpServletRequest
+     */
+    public static HttpServletRequest getRequest() {
+        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+        if (!(requestAttributes instanceof ServletRequestAttributes)) {
+            return null;
+        }
+        return ((ServletRequestAttributes) requestAttributes).getRequest();
+    }
+
+    public static String getUserAgent() {
+        HttpServletRequest request = getRequest();
+        if (request == null) {
+            return null;
+        }
+        return getUserAgent(request);
+    }
+
+    public static String getClientIP() {
+        HttpServletRequest request = getRequest();
+        if (request == null) {
+            return null;
+        }
+        return ServletUtil.getClientIP(request);
+    }
+
 }