feat: 管理后台IP归属地——移植ip2region离线库,登录日志/访问埋点/看板最近访问统一显示归属地
This commit is contained in:
+33
-28
@@ -2,6 +2,7 @@ const express = require('express')
|
||||
const router = express.Router()
|
||||
const mongoose = require('mongoose')
|
||||
const { User, Order, Knowledge, ShopItem, AdminLog, AIChatQuota, Feedback, LoginLog, Trend, KnowledgeLit, CoinLedger, VisitLog } = require('../models')
|
||||
const { ipLocation } = require('../utils/iploc')
|
||||
const { authMiddleware, requirePermission } = require('../middleware/auth')
|
||||
const ApiResponse = require('../utils/response')
|
||||
const logger = require('../utils/logger')
|
||||
@@ -74,6 +75,7 @@ router.get('/visits', requirePermission('dashboard:read'), async (req, res) => {
|
||||
VisitLog.countDocuments({}),
|
||||
VisitLog.find({}).sort({ createdAt: -1 }).skip((page - 1) * limit).limit(limit).lean(),
|
||||
])
|
||||
items.forEach(v => { v.ipLoc = ipLocation(v.ip) })
|
||||
return ApiResponse.success(res, { items, total, page, limit, pagination: { total } })
|
||||
} catch (error) {
|
||||
logger.error('访问日志查询失败:', error)
|
||||
@@ -1098,6 +1100,7 @@ router.get('/login-logs', requirePermission('user:read'), async (req, res) => {
|
||||
const formattedLogs = logs.map(log => ({
|
||||
id: log._id,
|
||||
userName: log.userId?.nickName || '未知用户',
|
||||
ipLoc: ipLocation(log.ip),
|
||||
userAvatar: log.userId?.avatarUrl || '',
|
||||
type: log.type,
|
||||
method: log.method,
|
||||
@@ -1221,6 +1224,35 @@ router.get('/dashboard-v2', requirePermission('dashboard:read'), async (req, res
|
||||
const DIM_NAMES = { 1: 'AI 起源', 2: 'AI 发展', 3: 'AI 当前', 4: 'AI 学习', 5: 'AI 趋势' }
|
||||
const ai = aiStatsRaw[0] || { used: 0, users: 0 }
|
||||
|
||||
// 合并最近访问记录(登录 + 访问埋点),统一附 IP 归属地
|
||||
const merged = [
|
||||
...recentLogins.map(l => ({
|
||||
kind: 'login',
|
||||
createdAt: l.createdAt,
|
||||
userName: l.userId?.nickName || l.openid,
|
||||
openid: l.openid,
|
||||
type: l.type === 'register' ? '注册' : '登录',
|
||||
method: l.method === 'wechat_miniapp' ? '微信小程序' : (l.method || ''),
|
||||
ip: l.ip,
|
||||
success: l.success,
|
||||
failReason: l.failReason,
|
||||
})),
|
||||
...recentVisits.map(v => ({
|
||||
kind: 'visit',
|
||||
createdAt: v.createdAt,
|
||||
userName: v.source,
|
||||
openid: '',
|
||||
type: '访问',
|
||||
method: [v.os, v.browser].filter(Boolean).join(' / ') || v.device || '',
|
||||
ip: v.ip,
|
||||
path: v.path,
|
||||
success: true,
|
||||
})),
|
||||
]
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
.slice(0, 10)
|
||||
merged.forEach(r => { r.ipLoc = ipLocation(r.ip) })
|
||||
|
||||
return ApiResponse.success(res, {
|
||||
stats: {
|
||||
totalUsers, newUsers7d,
|
||||
@@ -1231,34 +1263,7 @@ router.get('/dashboard-v2', requirePermission('dashboard:read'), async (req, res
|
||||
aiChatUsed: ai.used, aiChatUsers: ai.users,
|
||||
},
|
||||
litByDim: Object.entries(litByDim).map(([dim, count]) => ({ dim: Number(dim), name: DIM_NAMES[dim], count })),
|
||||
recentLogins: [
|
||||
// 登录记录(显示昵称)
|
||||
...recentLogins.map(l => ({
|
||||
kind: 'login',
|
||||
createdAt: l.createdAt,
|
||||
userName: l.userId?.nickName || l.openid,
|
||||
openid: l.openid,
|
||||
type: l.type === 'register' ? '注册' : '登录',
|
||||
method: l.method === 'wechat_miniapp' ? '微信小程序' : (l.method || ''),
|
||||
ip: l.ip,
|
||||
success: l.success,
|
||||
failReason: l.failReason,
|
||||
})),
|
||||
// 访问埋点(H5 / 小程序 / 直接访问)
|
||||
...recentVisits.map(v => ({
|
||||
kind: 'visit',
|
||||
createdAt: v.createdAt,
|
||||
userName: v.source,
|
||||
openid: '',
|
||||
type: '访问',
|
||||
method: [v.os, v.browser].filter(Boolean).join(' / ') || v.device || '',
|
||||
ip: v.ip,
|
||||
path: v.path,
|
||||
success: true,
|
||||
})),
|
||||
]
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
.slice(0, 10),
|
||||
recentLogins: merged,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('获取增强看板失败:', error)
|
||||
|
||||
Reference in New Issue
Block a user