Obtenga la ubicación de inicio de sesión del usuario a través de ip para realizar la función de registro de inicio de sesión. ——Cree un marco completo de un sistema de gestión de fondo de alto valor desde cero (11)

Revisión pasada

Construcción de marco frontal: cree un marco completo de sistema de gestión en segundo plano de alto valor desde cero (1)

Construcción de marco back-end: construya un marco completo de sistema de gestión en segundo plano de alto valor desde cero (2)

¿Te das cuenta de la función de inicio de sesión jwt o token+redis? ——Cree un marco completo de sistema de administración en segundo plano de alto valor desde cero (3)

Encapsule axios para que la solicitud sea sedosa: cree un marco completo de sistema de gestión en segundo plano de alto valor desde cero (4)

Realice una implementación de front-end y back-end completamente automatizada, liberando sus manos. ——Cree un marco completo de sistema de administración en segundo plano de alto valor desde cero (5)

Algoritmo de copo de nieve, esquema de archivos adjuntos, verificación de correo electrónico, cambio de contraseña. ——Cree un marco completo de sistema de gestión de fondo de alto valor desde cero (6)

Realice un menú dinámico y un enrutamiento dinámico basado en react-router v6. Contiene la implementación de enrutamiento dinámico de vue. ——Cree un marco completo de un sistema de gestión de fondo de alto valor desde cero (7)

Realice menús dinámicos front-end y back-end y enrutamiento dinámico a través del modelo RBAC: construya un marco completo de sistema de gestión en segundo plano de alto valor desde cero (8)

Es tan elegante usar tecnología negra para realizar el control de permisos de botones frontales. ——Cree un marco completo de un sistema de gestión de fondo de alto valor desde cero (9)

Integre WebSocket para realizar mensajes de cambio de permiso de usuario y actualización automática. ——Cree un marco completo de un sistema de gestión de fondo de alto valor desde cero (10)

prefacio

  • Hay un hoyo en el artículo anterior, pm2 inicia multiproceso, lo que conducirá a la falla de envío de mensajes a los usuarios.La razón específica se mencionó en el artículo anterior. En este artículo, primero resolvemos este problema.

  • Todas las plataformas principales ahora admiten la visualización de direcciones de usuario, lo que en realidad es muy simple de implementar. En este artículo, nos daremos cuenta de cómo obtener la dirección de usuario a través de la ip del usuario.

Use la transmisión de mensajes de redis para resolver el hoyo del artículo anterior

Ideas de implementación

Modifique el método de envío de mensajes, envíe mensajes a cada proceso a través de la transmisión de mensajes redis, y cada proceso monitorea el canal correspondiente.Si se recibe un mensaje, busque la websocketconexión del usuario a través del ID de usuario y luego envíe el mensaje.

Implementación

El método de publicación y suscripción de back-end de redis y el redis normal no pueden usar la misma instancia de redis, ni pueden publicar y suscribirse usar la misma instancia, por lo que necesitamos configurar tres instancias.

imagen.png

  • predeterminado: la instancia predeterminada, utilizada en el código normal.
  • publicar: publicar mensaje usando
  • subscribe:订阅消息使用

改造SocketService代码,代码很简单。其他代码不用改。

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<string, Context[]>();
  // 导入发布消息的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<T>(userId: string, data: SocketMessage<T>) {
    // 通过redis广播消息
    this.publishRedisService.publish(
      socketChannel,
      JSON.stringify({ userId, data })
    );
  }
}

获取登录用户ip

midway中可以从请求上下文获取ip imagen.png imagen.png

不过前面有::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配置反向代理的时候,这两个配置不要忘记加了。

imagen.png

封装一个统一获取ip的方法,this.ctx.req.headers['x-forwarded-for']有可能会返回两个ip地址,中间用隔开,所以需要split一下,取第一个ip就行了。

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这个库,也可以调用一些公共接口获取,这里我们使用第一种方式。

封装公共方法

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个字段

imagen.png

获取浏览器信息

可以从请求头上获取浏览器信息

imagen.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这个库来解析里面的数据,获取用户使用的是什么浏览器,以及操作系统。

封装一个公共方法:

import * as useragent from 'useragent';

export const getUserAgent = (ctx: Context): useragent.Agent => {
  return useragent.parse(ctx.headers['user-agent'] as string);
};

返回这几个属性,family表示浏览器,os表示操作系统。

imagen.png

用户登录日志功能实现

使用下面命令快速创建一个登录日志模块。

node ./script/create-module login.log

改造LoginLogEntity实体

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;
}

在用户登录方法中添加登录日志

imagen.png

登录成功时,把status设置位truemessage为成功。登录失败时把status设置位falsemessage为错误消息。最后在finally中把数据添加到数据库,这里不要用await,做成异步的,不影响正常接口响应速度。

imagen.png

Implementación de consultas front-end

Es solo una pantalla de mesa normal, nada que decir.

Mostrar resultados

imagen.png

Resumir

Hasta ahora hemos completado la función de registro de entrada y pit que se dejó en el artículo anterior. Si el artículo es útil para usted, por favor, dele un pulgar hacia arriba, gracias.

Agregado un usuario ordinario, los hermanos pueden usar esta cuenta para probar la función de autoridad.

Cuenta/contraseña: usuario/123456

Dirección de la experiencia del proyecto: fluxyadmin.cn/user/login

Dirección del almacén de front-end: github.com/dbfu/fluxy-…

Dirección del almacén de back-end: github.com/dbfu/fluxy-…

Supongo que te gusta

Origin juejin.im/post/7257511618824355877
Recomendado
Clasificación