写点什么

如何在 SpringBoot 项目中,实现记录用户登录的 IP 地址及归属地信息?

作者:wljslmz
  • 2022-11-15
    江苏
  • 本文字数:5067 字

    阅读完需:约 17 分钟

如何在SpringBoot项目中,实现记录用户登录的IP地址及归属地信息?

本文我将从我们的系统中划分出来一个简单的小功能:登录日志。


让我们直接开始!

一、说在前面

因为本身系统很庞大,加上代码的隐私性,我这边不会介绍非常多的属性,不过我能保证的是,代码你抄上去肯定能用。

二、数据库设计

按照开发流程,首先肯定是数据库的设计,我这边直接给出数据库建表语句:


CREATE TABLE `login_log` (  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',  `name` varchar(255) DEFAULT NULL COMMENT '登陆人姓名',  `ip` varchar(255) DEFAULT NULL COMMENT '登录ip',  `ip_attribution` varchar(255) DEFAULT NULL COMMENT 'ip归属地信息',  `create_time` datetime DEFAULT NULL COMMENT '创建时间',  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
复制代码


在建表前,请保证你的数据库是创建好的,这个前提有点搞笑了。


由表可以看到,我们创建了 login_log 表,即登录日志表,字段名有 id(主键)、name(登陆人姓名)、ip(登录 ip)、ip_attribution(ip 归属地信息)、create_time(创建时间)。


在真实的企业环境中,登录日志肯定远远不止这些,我是从我们的表中挑出了最基本的字段,像 ip 所属经纬度,因为需要调用第三方服务,我这边没有展示,当然市面上也有开源的根据 ip 获取经纬度的库,只不过不准确,想要准确还是要购买第三方服务。

三、代码编写

3.1 框架选型

我选用的框架是:


  • SpringBoot:2.7.0

  • jdk:1.8

  • mybatis-plus:3.4.2

  • lombok:1.18.22


还有其他基础的到时候报红的时候自行解决。

3.2 pom 依赖

跟 ip 归属地相关的依赖:


<dependency>  <groupId>org.lionsoul</groupId>  <artifactId>ip2region</artifactId>  <version>1.7.2</version></dependency>
复制代码


跟加载 ip 归属地数据库文件的依赖:


<dependency>  <groupId>commons-io</groupId>  <artifactId>commons-io</artifactId>  <version>2.5</version></dependency>
复制代码

3.3 实体类

LoginLogEntity:


package com.wljlsmz.transitcenter.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;
/** * @author: wljlsmz * @date: 2022/11/15 10:28 * @description: 用户登录日志实体类 */@Data@Builder@AllArgsConstructor@NoArgsConstructor@TableName("login_log")public class LoginLogEntity {
/** * 主键 */ @TableId(type = IdType.AUTO) private Integer id;
/** * 登陆人姓名 */ @TableField("name") private String name;
/** * 登录ip */ @TableField("ip") private String ip;
/** * ip归属地信息 */ @TableField("ip_attribution") private String ipAttribution;
/** * 创建时间 */ @TableField("create_time") private LocalDateTime createTime;}
复制代码

3.4 Mapper 类

LoginLogMapper:


package com.wljlsmz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.wljlsmz.transitcenter.model.entity.LoginLogEntity;
/** * @author: wljlsmz * @date: 2022/11/15 10:28 * @description: 登录日志mapper接口类 */public interface LoginLogMapper extends BaseMapper<LoginLogEntity> {}
复制代码

3.5 service 类

ILoginLogService:


package com.wljlsmz.transitcenter.service;
import com.wljlsmz.transitcenter.model.dto.LoginLogDTO;
/** * @author: wljlsmz * @date: 2022/11/15 10:28 * @description: 登录日志服务接口类 */public interface ILoginLogService {
int saveLoginLog(LoginLogDTO loginLogDTO);}
复制代码


LoginLogServiceImpl:


package com.wljlsmz.transitcenter.service.impl;
import com.wljlsmz.transitcenter.mapper.LoginLogMapper;import com.wljlsmz.transitcenter.model.dto.LoginLogDTO;import com.wljlsmz.transitcenter.model.entity.LoginLogEntity;import com.wljlsmz.transitcenter.service.ILoginLogService;import com.wljlsmz.transitcenter.util.CopyUtil;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/** * @author: wljlsmz * @date: 2022/11/15 10:28 * @description: 登录日志服务接口实现类 */@Servicepublic class LoginLogServiceImpl implements ILoginLogService {
@Autowired private LoginLogMapper mapper;
@Override public int saveLoginLog(LoginLogDTO loginLogDTO) { LoginLogEntity loginLogEntity = CopyUtil.copy(loginLogDTO, LoginLogEntity.class); loginLogEntity.setCreateTime(LocalDateTime.now()); return mapper.insert(loginLogEntity); }}
复制代码

3.6 IP 工具类

IpUtils:


package com.wljlsmz.transitcenter.util;
import org.apache.commons.io.FileUtils;import org.apache.commons.lang3.StringUtils;import org.lionsoul.ip2region.DataBlock;import org.lionsoul.ip2region.DbConfig;import org.lionsoul.ip2region.DbSearcher;import org.lionsoul.ip2region.Util;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import java.io.File;import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

/** * @author: wljlsmz * @date: 2022/11/15 10:28 * @description: ip工具类 */@Slf4jpublic class IpUtils {
/** * 本地环回地址 */ private static final String LOCAL_IP = "127.0.0.1";
/** * 未知 */ private static final String UNKNOWN = "unknown";
public static String getIpAddr(HttpServletRequest request) {
if (request == null) { return UNKNOWN; }
String ip = request.getHeader("x-forwarded-for"); if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); }
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); }
return "0:0:0:0:0:0:0:1".equals(ip) ? LOCAL_IP : ip; }
public static String getCityInfo(String ip) throws Exception {
if (!Util.isIpAddress(ip)) { log.error("错误: 无效的ip地址"); return null; }
InputStream is = new PathMatchingResourcePatternResolver().getResources("ip2region.db")[0].getInputStream(); File target = new File("ip2region.db"); FileUtils.copyInputStreamToFile(is, target); is.close();
if (StringUtils.isEmpty(String.valueOf(target))) { log.error("错误: 无效的ip2region.db文件"); return null; }
DbSearcher searcher = new DbSearcher(new DbConfig(), String.valueOf(target));
try { DataBlock dataBlock = (DataBlock) searcher.getClass().getMethod("btreeSearch", String.class).invoke(searcher, ip);
String ipInfo = dataBlock.getRegion(); if (!StringUtils.isEmpty(ipInfo)) { ipInfo = ipInfo.replace("|0", ""); ipInfo = ipInfo.replace("0|", ""); }
return ipInfo; } catch (Exception e) { e.printStackTrace(); } return null; }}
复制代码


上诉代码中提到了 ip2region.db 文件,这个文件需要放到 resources 目录下:



ip2region.db 文件我放在网盘中,大家可以按需下载:


链接:https://pan.quark.cn/s/a5e187b7a91b提取码:pjqQ
复制代码


点击下载即可:


3.7 Controller 类

这个类就使用到了以上所有的准备代码:


UserController:


package com.wljlsmz.transitcenter.controller;
import com.wljlsmz.transitcenter.common.response.ResponseResult;import com.wljlsmz.transitcenter.model.dto.LoginDTO;import com.wljlsmz.transitcenter.model.dto.LoginLogDTO;import com.wljlsmz.transitcenter.service.ILoginLogService;import com.wljlsmz.transitcenter.service.IUserService;import com.wljlsmz.transitcenter.util.IpUtils;
import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
/** * @author: wljlsmz * @date: 2022/11/15 10:28 * @description: 用户接口类 */@RestController@Slf4j@Api(tags = "用户接口")@RequestMapping("${url.user.prefix}${url.user.version}")public class UserController {
@Autowired private IUserService userService;
@Autowired private ILoginLogService loginLogService;
@ApiOperation("登录") @PostMapping("login") public ResponseResult login(@ApiParam @RequestBody LoginDTO loginDTO, HttpServletRequest request) {
String ip = IpUtils.getIpAddr(request); String cityInfo = null; try { cityInfo = IpUtils.getCityInfo(ip); } catch (Exception e) { log.error("获取ip归属地信息失败!"); }
loginLogService.saveLoginLog( LoginLogDTO.builder() .ip(ip) .ipAttribution(cityInfo) .name(loginDTO.getUserName()) .build());
return userService.login(loginDTO); }}
复制代码


以上代码被我删减了好多,其实大家在使用的时候,值需要把下面这段代码摘出来放到自己的测试代码中即可:


String ip = IpUtils.getIpAddr(request);String cityInfo = null;try {    cityInfo = IpUtils.getCityInfo(ip);} catch (Exception e) {    log.error("获取ip归属地信息失败!");}
loginLogService.saveLoginLog( LoginLogDTO.builder() .ip(ip) .ipAttribution(cityInfo) .name(loginDTO.getUserName()) .build());
复制代码

测试代码

我们用 swagger 测试一下接口:



测试成功后,我们看下数据库:



成功记录了 ip 信息,在真实的企业环境中,你也再增加一些字段。


至此代码全部演示完毕。

总结

本文主要介绍了如何在登录接口增加登录日志的记录,文中的代码大家可以参考,有任何问题可以在下方评论区与我讨论,最后感谢您的阅读,您的点赞和转发就是我不断创作的动力!

发布于: 刚刚阅读数: 6
用户头像

wljslmz

关注

极致主义者,追求技术的路上,勇往直前! 2021-05-24 加入

公众号:网络技术联盟站 👍InfoQ签约作者 👍阿里云社区签约作者 👍华为云 云享专家 👍BOSS直聘 创作王者 👍腾讯课堂创作领航员 博客+论坛:https://www.wljslmz.cn 工程师导航:https://www.wljslmz.com

评论

发布
暂无评论
如何在SpringBoot项目中,实现记录用户登录的IP地址及归属地信息?_日志_wljslmz_InfoQ写作社区