通过 ip 获取用户登录地点,实现登录日志功能 |
您所在的位置:网站首页 › 微信登陆ip查询不到 › 通过 ip 获取用户登录地点,实现登录日志功能 |
前言上一篇文章中留了一个坑,pm2开启多进程,会导致给用户推送消息失败,具体原因上一篇文章中已经说过了。这一篇我们先解决一下这个问题。现在各大平台都支持显示用户地址,其实实现起来很简单。我们这一篇就实现一下通过用户ip获取用户地址。使用redis消息广播解决上篇文章的坑实现思路 改造发消息的方法,通过redis消息广播把消息发给各个进程,各个进程监听对应频道,如果收到消息,通过userId找到用户websocket连接,然后把消息发出去。 具体实现后端redis发布订阅方法和普通redis不能使用同一个redis实例,发布订阅也不能使用同一个实例,所以我们需要配置三个实例。 image.png default:默认实例,给正常代码中使用。publish:发布消息使用subscribe:订阅消息使用改造SocketService代码,代码很简单。其他代码不用改。 代码语言:javascript复制import { Autoload, Init, InjectClient, Singleton } from '@midwayjs/core'; import { Context } from '@midwayjs/ws'; import { SocketMessage } from './message'; import { RedisService, RedisServiceFactory } from '@midwayjs/redis'; const socketChannel = 'socket-message'; @Singleton() @Autoload() export class SocketService { connects = new Map(); // 导入发布消息的redis实例 @InjectClient(RedisServiceFactory, 'publish') publishRedisService: RedisService; // 导入订阅消息的redis实例 @InjectClient(RedisServiceFactory, 'subscribe') subscribeRedisService: RedisService; @Init() async init() { // 系统启动的时候,这个方法会自动执行,监听频道。 await this.subscribeRedisService.subscribe(socketChannel); // 如果接受到消息,通过userId获取连接,如果存在,通过连接给前端发消息 this.subscribeRedisService.on( 'message', (channel: string, message: string) => { if (channel === socketChannel && message) { const messageData = JSON.parse(message); const { userId, data } = messageData; const clients = this.connects.get(userId); if (clients?.length) { clients.forEach(client => { client.send(JSON.stringify(data)); }); } } } ); } /** * 添加连接 * @param userId 用户id * @param connect 用户socket连接 */ addConnect(userId: string, connect: Context) { const curConnects = this.connects.get(userId); if (curConnects) { curConnects.push(connect); } else { this.connects.set(userId, [connect]); } } /** * 删除连接 * @param connect 用户socket连接 */ deleteConnect(connect: Context) { const connects = [...this.connects.values()]; for (let i = 0; i < connects.length; i += 1) { const sockets = connects[i]; const index = sockets.indexOf(connect); if (index >= 0) { sockets.splice(index, 1); break; } } } /** * 给指定用户发消息 * @param userId 用户id * @param data 数据 */ sendMessage(userId: string, data: SocketMessage) { // 通过redis广播消息 this.publishRedisService.publish( socketChannel, JSON.stringify({ userId, data }) ); } } 获取登录用户ipmidway中可以从请求上下文获取ip 不过前面有::ffff:,我们可以使用replace方法给替换掉。 如果用这个方式获取不到ip,我们还可以this.ctx.req.socket.remoteAddress获取ip。 如果线上使用nginx配置了反向代理,我们可以从请求头上获取ip,使用this.ctx.req.headers['x-forwarded-for']或this.ctx.req.headers['X-Real-IP']这两个方法就行。 nginx配置反向代理的时候,这两个配置不要忘记加了。 image.png 封装一个统一获取ip的方法,this.ctx.req.headers['x-forwarded-for']有可能会返回两个ip地址,中间用,隔开,所以需要split一下,取第一个ip就行了。 代码语言:javascript复制export const getIp = (ctx: Context) => { const ips = (ctx.req.headers['x-forwarded-for'] as string) || (ctx.req.headers['X-Real-IP'] as string) || (ctx.ip.replace('::ffff:', '') as string) || (ctx.req.socket.remoteAddress.replace('::ffff:', '') as string); console.log(ips.split(',')?.[0], 'ip'); return ips.split(',')?.[0]; }; 通过ip获取地址通过ip获取地址可以使用ip2region这个库,也可以调用一些公共接口获取,这里我们使用第一种方式。 封装公共方法 代码语言:javascript复制import IP2Region from 'ip2region'; export const getAddressByIp = (ip: string): string => { if (!ip) return ''; const query = new IP2Region(); const res = query.search(ip); return [res.province, res.city].join(' '); };查询结果中包含国家、省份、城市、供应商4个字段 image.png 获取浏览器信息可以从请求头上获取浏览器信息 image.png 打印出来的结果如下: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 我们可以用useragent这个库来解析里面的数据,获取用户使用的是什么浏览器,以及操作系统。 封装一个公共方法: 代码语言:javascript复制import * as useragent from 'useragent'; export const getUserAgent = (ctx: Context): useragent.Agent => { return useragent.parse(ctx.headers['user-agent'] as string); };返回这几个属性,family表示浏览器,os表示操作系统。 image.png 用户登录日志功能实现使用下面命令快速创建一个登录日志模块。代码语言:javascript复制node ./script/create-module login.log 改造LoginLogEntity实体代码语言:javascript复制import { Entity, Column } from 'typeorm'; import { BaseEntity } from '../../../common/base.entity'; @Entity('sys_login_log') export class LoginLogEntity extends BaseEntity { @Column({ comment: '用户名' }) userName?: string; @Column({ comment: '登录ip' }) ip?: string; @Column({ comment: '登录地点' }) address?: string; @Column({ comment: '浏览器' }) browser?: string; @Column({ comment: '操作系统' }) os?: string; @Column({ comment: '登录状态' }) status?: boolean; @Column({ comment: '登录消息' }) message?: string; } 在用户登录方法中添加登录日志image.png 登录成功时,把status设置位true,message为成功。登录失败时把status设置位false,message为错误消息。最后在finally中把数据添加到数据库,这里不要用await,做成异步的,不影响正常接口响应速度。 image.png 前端查询实现就是一个正常的表格展示,没啥好说的。 效果展示image.png |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |