test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl - 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE - 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查 - 所有1266个测试用例通过 - 覆盖率: 指令81.89%, 行88.48%, 分支51.55% docs: 添加项目状态报告 - 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态 - 包含质量指标、已完成功能、待办事项和技术债务
This commit is contained in:
74
frontend/admin/src/services/api/ApiDataService.ts
Normal file
74
frontend/admin/src/services/api/ApiDataService.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
const baseUrl = import.meta.env.VITE_MOSQUITO_API_BASE_URL ?? ''
|
||||
const apiKey = import.meta.env.VITE_MOSQUITO_API_KEY ?? ''
|
||||
const userToken = import.meta.env.VITE_MOSQUITO_USER_TOKEN ?? ''
|
||||
|
||||
const requestJson = async (url: string) => {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': apiKey,
|
||||
...(userToken ? { Authorization: `Bearer ${userToken}` } : {})
|
||||
}
|
||||
})
|
||||
const payload = await response.json().catch(() => ({}))
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.message || '请求失败')
|
||||
}
|
||||
return payload?.data ?? []
|
||||
}
|
||||
|
||||
export const apiDataService = {
|
||||
async getDashboard() {
|
||||
return {
|
||||
updatedAt: '刚刚',
|
||||
kpis: [],
|
||||
activities: [],
|
||||
alerts: []
|
||||
}
|
||||
},
|
||||
async getActivities() {
|
||||
return []
|
||||
},
|
||||
async getActivityById(_id: number) {
|
||||
return null
|
||||
},
|
||||
async getUsers() {
|
||||
return []
|
||||
},
|
||||
async getInvites() {
|
||||
return []
|
||||
},
|
||||
async getRoleRequests() {
|
||||
return []
|
||||
},
|
||||
async getRewards() {
|
||||
return []
|
||||
},
|
||||
async getRiskItems() {
|
||||
return []
|
||||
},
|
||||
async getRiskAlerts() {
|
||||
return []
|
||||
},
|
||||
async getAuditLogs() {
|
||||
return []
|
||||
},
|
||||
async getNotifications() {
|
||||
return []
|
||||
},
|
||||
async addNotification(_payload: { title: string; detail: string }) {
|
||||
return null
|
||||
},
|
||||
async getConfig() {
|
||||
return []
|
||||
},
|
||||
async getInvitedFriends(activityId: number, userId: number, page: number, size: number) {
|
||||
const params = new URLSearchParams({
|
||||
activityId: String(activityId),
|
||||
userId: String(userId),
|
||||
page: String(page),
|
||||
size: String(size)
|
||||
})
|
||||
return requestJson(`${baseUrl}/api/v1/me/invited-friends?${params}`)
|
||||
}
|
||||
}
|
||||
309
frontend/admin/src/services/demo/DemoDataService.ts
Normal file
309
frontend/admin/src/services/demo/DemoDataService.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import type { AdminRole } from '../../auth/roles'
|
||||
|
||||
export type DemoKpi = {
|
||||
label: string
|
||||
value: number
|
||||
status: string
|
||||
hint: string
|
||||
}
|
||||
|
||||
export type DemoActivity = {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
participants: number
|
||||
status: string
|
||||
config: {
|
||||
audience: string
|
||||
conversion: string
|
||||
reward: string
|
||||
budget: string
|
||||
}
|
||||
metrics: {
|
||||
visits: number
|
||||
shares: number
|
||||
conversions: number
|
||||
budgetUsed: number
|
||||
}
|
||||
}
|
||||
|
||||
export type DemoAlert = {
|
||||
title: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
export type DemoUser = {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
role: AdminRole
|
||||
status: '正常' | '冻结'
|
||||
managerName: string
|
||||
}
|
||||
|
||||
export type DemoInvite = {
|
||||
id: string
|
||||
email: string
|
||||
role: AdminRole
|
||||
status: '待接受' | '已接受' | '已拒绝' | '已过期'
|
||||
invitedAt: string
|
||||
acceptedAt?: string
|
||||
expiredAt?: string
|
||||
}
|
||||
|
||||
export type DemoReward = {
|
||||
id: string
|
||||
userName: string
|
||||
points: number
|
||||
status: string
|
||||
issuedAt: string
|
||||
batchId: string
|
||||
batchStatus: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
export type DemoRiskItem = {
|
||||
id: string
|
||||
type: string
|
||||
target: string
|
||||
status: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type DemoRiskAlert = {
|
||||
id: string
|
||||
title: string
|
||||
detail: string
|
||||
status: '未处理' | '处理中' | '已关闭'
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type DemoAuditLog = {
|
||||
id: string
|
||||
actor: string
|
||||
action: string
|
||||
resource: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type DemoNotification = {
|
||||
id: string
|
||||
title: string
|
||||
detail: string
|
||||
read: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type DemoNotificationInput = {
|
||||
title: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
export type DemoRoleRequest = {
|
||||
id: string
|
||||
userId: string
|
||||
currentRole: AdminRole
|
||||
targetRole: AdminRole
|
||||
reason: string
|
||||
status: '待审批' | '已通过' | '已拒绝'
|
||||
requestedAt: string
|
||||
}
|
||||
|
||||
export type DemoConfig = {
|
||||
key: string
|
||||
value: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const isoDays = (offset: number) => new Date(now.getTime() + offset * 86400000).toISOString()
|
||||
|
||||
const demoActivities: DemoActivity[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '裂变增长计划',
|
||||
description: '邀请好友注册,获取双倍奖励。',
|
||||
startTime: isoDays(-7),
|
||||
endTime: isoDays(21),
|
||||
participants: 1280,
|
||||
status: '进行中',
|
||||
config: {
|
||||
audience: '新注册用户与邀请达人',
|
||||
conversion: '完成注册并绑定手机号',
|
||||
reward: '每邀请 1 人奖励 20 积分',
|
||||
budget: '总预算 50,000 积分'
|
||||
},
|
||||
metrics: {
|
||||
visits: 48210,
|
||||
shares: 12800,
|
||||
conversions: 3840,
|
||||
budgetUsed: 32000
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '新用户召回活动',
|
||||
description: '召回沉默用户,提升活跃度。',
|
||||
startTime: isoDays(-21),
|
||||
endTime: isoDays(-2),
|
||||
participants: 640,
|
||||
status: '已结束',
|
||||
config: {
|
||||
audience: '30 天未登录用户',
|
||||
conversion: '完成首次分享',
|
||||
reward: '每邀请 1 人奖励 10 积分',
|
||||
budget: '总预算 20,000 积分'
|
||||
},
|
||||
metrics: {
|
||||
visits: 18200,
|
||||
shares: 6200,
|
||||
conversions: 1200,
|
||||
budgetUsed: 15000
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const demoKpis: DemoKpi[] = [
|
||||
{ label: '访问', value: 48210, status: '已同步', hint: '近 7 天访问次数' },
|
||||
{ label: '分享', value: 12800, status: '已同步', hint: '累计分享次数' },
|
||||
{ label: '转化', value: 3840, status: '已同步', hint: '累计转化人数' },
|
||||
{ label: '新增', value: 920, status: '已同步', hint: '新增访问用户' }
|
||||
]
|
||||
|
||||
const demoAlerts: DemoAlert[] = [
|
||||
{ title: '回调失败率升高', detail: '最近 1 小时失败率 3.2%,建议检查回调服务。' }
|
||||
]
|
||||
|
||||
const demoUsers: DemoUser[] = [
|
||||
{ id: 'u-1001', name: '王晨', email: 'wangchen@demo.com', role: 'operator', status: '正常', managerName: '演示管理员' },
|
||||
{ id: 'u-1002', name: '李雪', email: 'lixue@demo.com', role: 'operator', status: '正常', managerName: '演示管理员' },
|
||||
{ id: 'u-1003', name: '周宁', email: 'zhouning@demo.com', role: 'viewer', status: '冻结', managerName: '王晨' }
|
||||
]
|
||||
|
||||
const demoRewards: DemoReward[] = [
|
||||
{ id: 'r-2001', userName: '王晨', points: 120, status: '已发放', issuedAt: isoDays(-1), batchId: 'batch-01', batchStatus: '已完成' },
|
||||
{ id: 'r-2002', userName: '李雪', points: 80, status: '待发放', issuedAt: isoDays(0), batchId: 'batch-02', batchStatus: '排队中' },
|
||||
{ id: 'r-2003', userName: '周宁', points: 50, status: '发放失败', issuedAt: isoDays(-2), batchId: 'batch-02', batchStatus: '异常' }
|
||||
]
|
||||
|
||||
const demoRiskItems: DemoRiskItem[] = [
|
||||
{ id: 'risk-1', type: '黑名单', target: '138****1234', status: '生效', updatedAt: isoDays(-2) },
|
||||
{ id: 'risk-2', type: '异常转化', target: 'IP: 10.10.2.24', status: '待核查', updatedAt: isoDays(-1) }
|
||||
]
|
||||
|
||||
const demoRiskAlerts: DemoRiskAlert[] = [
|
||||
{ id: 'alert-1', title: '回调失败率升高', detail: '最近 1 小时失败率 3.2%,建议检查回调服务。', status: '未处理', updatedAt: isoDays(-1) },
|
||||
{ id: 'alert-2', title: '异常积分发放', detail: '检测到单日发放异常增长,需复核。', status: '处理中', updatedAt: isoDays(-2) }
|
||||
]
|
||||
|
||||
const demoAuditLogs: DemoAuditLog[] = [
|
||||
{ id: 'audit-1', actor: '演示管理员', action: '更新活动', resource: '活动 #1', createdAt: isoDays(-1) },
|
||||
{ id: 'audit-2', actor: '演示管理员', action: '调整奖励规则', resource: '奖励方案 A', createdAt: isoDays(-3) }
|
||||
]
|
||||
|
||||
const demoNotifications: DemoNotification[] = [
|
||||
{ id: 'notice-1', title: '活动即将结束', detail: '裂变增长计划 3 天后结束', read: false, createdAt: isoDays(-1) },
|
||||
{ id: 'notice-2', title: '回调异常提醒', detail: '请检查回调配置与重试策略', read: true, createdAt: isoDays(-4) }
|
||||
]
|
||||
|
||||
const demoConfig: DemoConfig[] = [
|
||||
{ key: 'callback.retry.max', value: '3', description: '回调最大重试次数' },
|
||||
{ key: 'reward.batch.size', value: '200', description: '奖励批量发放大小' }
|
||||
]
|
||||
|
||||
export const demoDataService = {
|
||||
async getDashboard() {
|
||||
return {
|
||||
updatedAt: now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
|
||||
kpis: demoKpis,
|
||||
activities: demoActivities,
|
||||
alerts: demoAlerts
|
||||
}
|
||||
},
|
||||
async getActivities() {
|
||||
return demoActivities
|
||||
},
|
||||
async getActivityById(id: number) {
|
||||
return demoActivities.find((item) => item.id === id) ?? null
|
||||
},
|
||||
async getUsers() {
|
||||
return demoUsers
|
||||
},
|
||||
async getInvites(): Promise<DemoInvite[]> {
|
||||
return [
|
||||
{
|
||||
id: 'invite-1',
|
||||
email: 'newuser@demo.com',
|
||||
role: 'operator',
|
||||
status: '待接受',
|
||||
invitedAt: isoDays(-1)
|
||||
},
|
||||
{
|
||||
id: 'invite-2',
|
||||
email: 'expired@demo.com',
|
||||
role: 'viewer',
|
||||
status: '已过期',
|
||||
invitedAt: isoDays(-5),
|
||||
expiredAt: isoDays(-2)
|
||||
},
|
||||
{
|
||||
id: 'invite-3',
|
||||
email: 'accepted@demo.com',
|
||||
role: 'admin',
|
||||
status: '已接受',
|
||||
invitedAt: isoDays(-6),
|
||||
acceptedAt: isoDays(-4)
|
||||
}
|
||||
]
|
||||
},
|
||||
async getRoleRequests(): Promise<DemoRoleRequest[]> {
|
||||
return [
|
||||
{
|
||||
id: 'role-1',
|
||||
userId: 'u-1002',
|
||||
currentRole: 'operator',
|
||||
targetRole: 'admin',
|
||||
reason: '需要管理活动权限',
|
||||
status: '待审批',
|
||||
requestedAt: isoDays(-2)
|
||||
}
|
||||
]
|
||||
},
|
||||
async getRewards() {
|
||||
return demoRewards
|
||||
},
|
||||
async getRiskItems() {
|
||||
return demoRiskItems
|
||||
},
|
||||
async getRiskAlerts() {
|
||||
return demoRiskAlerts
|
||||
},
|
||||
async getAuditLogs() {
|
||||
return demoAuditLogs
|
||||
},
|
||||
async getNotifications() {
|
||||
return demoNotifications
|
||||
},
|
||||
async addNotification(payload: DemoNotificationInput) {
|
||||
const item: DemoNotification = {
|
||||
id: `notice-${Date.now()}`,
|
||||
title: payload.title,
|
||||
detail: payload.detail,
|
||||
read: false,
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
demoNotifications.unshift(item)
|
||||
return item
|
||||
},
|
||||
async getConfig() {
|
||||
return demoConfig
|
||||
},
|
||||
async getInvitedFriends(_activityId: number, _userId: number, _page: number, _size: number) {
|
||||
return [
|
||||
{ nickname: '邀请用户 A', maskedPhone: '138****1024', status: '已注册' },
|
||||
{ nickname: '邀请用户 B', maskedPhone: '139****2048', status: '未注册' }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { demoDataService } from '../DemoDataService'
|
||||
|
||||
describe('demoDataService', () => {
|
||||
it('adds notification entries', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-02-10T00:00:00Z'))
|
||||
|
||||
const originalLength = (await demoDataService.getNotifications()).length
|
||||
const created = await demoDataService.addNotification({
|
||||
title: '审批通过',
|
||||
detail: '王晨 角色变更已通过'
|
||||
})
|
||||
const nextLength = (await demoDataService.getNotifications()).length
|
||||
|
||||
expect(nextLength).toBe(originalLength + 1)
|
||||
expect(created.title).toBe('审批通过')
|
||||
expect(created.read).toBe(false)
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
8
frontend/admin/src/services/index.ts
Normal file
8
frontend/admin/src/services/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { demoDataService } from './demo/DemoDataService'
|
||||
import { apiDataService } from './api/ApiDataService'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
|
||||
export const useDataService = () => {
|
||||
const auth = useAuthStore()
|
||||
return auth.mode === 'demo' ? demoDataService : apiDataService
|
||||
}
|
||||
Reference in New Issue
Block a user