改造发消息的方法,通过redis消息广播把消息发给各个进程,各个进程监听对应频道,如果收到消息,通过userId找到用户websocket
连接,然后把消息发出去。
后端redis发布订阅方法和普通redis不能使用同一个redis实例,发布订阅也不能使用同一个实例,所以我们需要配置三个实例。
改造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 })
);
}
}
midway中可以从请求上下文获取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配置反向代理的时候,这两个配置不要忘记加了。
封装一个统一获取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获取地址可以使用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个字段
可以从请求头上获取浏览器信息
打印出来的结果如下:
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
表示操作系统。
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;
}
status设置位true,message为成功。登录失败时把status设置位false,message为错误消息。最后在finally中把数据添加到数据库,这里不要用await,做成异步的,不影响正常接口响应速度。
就是一个正常的表格展示,没啥好说的。
到此我们把上篇文章中留下的坑和登录日志功能搞定了。如果文章对你有帮忙,帮忙点个赞吧,谢谢了。
阅读量:859
点赞量:0
收藏量:0