功能列表
- 记录登录日志: 用户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中,先注册HttpModule到shared.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)) },
});
}