NestJs权限管理系统五、登录日志功能开发

214 阅读1分钟

功能列表

  • 记录登录日志: 用户id、登录ip、系统信息
  • 计算登录日志总数
  • 登录日志列表(分页)
  • 清空所有登录日志

1. 记录登录日志

准备:

1.1 获取登录ip

这里使用whois来获取当前ip

需要请求地址http://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true

getLocation写入到shared/serives/util.service.ts中,先注册HttpModuleshared.module.ts中。

import { HttpModule } from '@nestjs/axios';

@Global()
@Module({
  imports: [
    HttpModule.register({ timeout: 5000, maxRedirects: 5 }),
  ],
  ...
})
/**

* 获取ip

*/

getReqIP(req: FastifyRequest): string {

return (

// 判断是否有反向代理 IP

(

(req.headers['x-forwarded-for'] as string) ||

// 判断后端的 socket 的 IP

req.socket.remoteAddress

).replace('::ffff:', '')

);

}

  


/**

* 判断IP是不是内网

*/

IsLAN(ip: string) {

ip.toLowerCase();

if (ip === 'localhost') return true;

let a_ip = 0;

if (ip === '') return false;

const aNum = ip.split('.');

if (aNum.length != 4) return false; // 如果aNum的长度不等于4,返回false。因为一个有效的IP地址应该由4个数字组成

// 将IP地址的每一部分转换为整数,然后左移相应的位数,然后累加到a_ip上。这样,a_ip就存储了IP地址的二进制表示

a_ip += parseInt(aNum[0]) << 24;

a_ip += parseInt(aNum[1]) << 16;

a_ip += parseInt(aNum[2]) << 8;

a_ip += parseInt(aNum[3]) << 0;

a_ip = (a_ip >> 16) & 0xffff; // 将a_ip右移16位,然后与0xffff进行位与运算。这样,a_ip就只保留了最后16位

// a_ip是否满足局域网IP地址的条件

return (

a_ip >> 8 == 0x7f ||

a_ip >> 8 == 0xa ||

a_ip == 0xc0a8 ||

(a_ip >= 0xac10 && a_ip <= 0xac1f)
);
}

/**

* 通过IP获取地理位置

* @param ip IP

*/

async getLocation(ip: string) {

if (this.IsLAN(ip)) return '内网IP';

// {

// "ip": "122.240.181.217",

// "pro": "浙江省",

// "proCode": "330000",

// "city": "温州市",

// "cityCode": "330300",

// "region": "",

// "regionCode": "0",

// "addr": "浙江省温州市 电信",

// "regionNames": "",

// "err": ""

// }

let { data } = await this.httpService.axiosRef.get(

`http://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`,

{

responseType: 'arraybuffer',

},

);

data = JSON.parse(new TextDecoder('gbk').decode(data))

.addr.trim()

.split(' ')

.at(0);

return data;

}

提供保存登录服务

@Injectable()

export class LogService {

constructor(

@InjectRepository(LoginLog)

private loginLogRepository: Repository<LoginLog>,

private readonly utilService: UtilService,

) {}

  


/**

* 记录登录日志

* @param uid

* @param ip

* @param ua

*/

async saveLoginLog(uid: number, ip: string, ua: string): Promise<void> {

const loginLocation = await this.utilService.getLocation(

ip.split(',').at(-1).trim(),

);

await this.loginLogRepository.save({

ip,

userId: uid,

ua,

loginLocation,

});

}

}

登录时调用saveLoginLog

先注册 logService

@Injectable()
export class LoginService {
    constructor(
        private logService: LogService,
    ) {}
}
/**
* 获取登录jwt
* @param username
* @param password
*/
async getLoginSign(
    username: string,
    password: string,
    ip: string,
    ua: string,
): Promise<string> {
    ....
    // 保存登录日志
    await this.logService.saveLoginLog(user.id, ip, ua);
    return jwtSign;
}

2.计算登录日志总数

/**
* 计算登录日志总数
*/
async countLoginLog(): Promise<number> {
  const userIds = await this.userRepository
    .createQueryBuilder('user')
    .select(['user.id'])
    .getMany();
  return await this.loginLogRepository.count({
    where: { userId: In(userIds.map((n) => n.id)) },
  });
}