chore: production cleanup - remove debug logs, add DB cleanup script
- App.vue: remove console.log on launch/show/hide, drop unused onShow/onHide imports
- interview.vue: remove verbose console.log('[ASR] upload response')
- login.vue: remove debug console.log('[wxLogin]') logs (keep error logs)
- scripts/cleanup-test-data.ts: new script to identify and remove test data
while preserving admin accounts; supports --dry-run preview mode
- All 43 backend tests pass
This commit is contained in:
@@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* 测试数据清理脚本
|
||||||
|
* 识别并清理测试用户及其关联数据(订单/面试/简历/分享)
|
||||||
|
* 保留所有管理员账号
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* 预览模式(推荐先执行): npx ts-node --project tsconfig.json scripts/cleanup-test-data.ts
|
||||||
|
* 执行清理: npx ts-node --project tsconfig.json scripts/cleanup-test-data.ts --execute
|
||||||
|
*/
|
||||||
|
import { NestFactory } from '@nestjs/core'
|
||||||
|
import { AppModule } from '../src/app.module'
|
||||||
|
import { getModelToken } from '@nestjs/mongoose'
|
||||||
|
import { Model } from 'mongoose'
|
||||||
|
import { User, UserDocument } from '../src/modules/user/user.schema'
|
||||||
|
import { Interview, InterviewDocument } from '../src/modules/interview/interview.schema'
|
||||||
|
import { Resume, ResumeDocument } from '../src/modules/resume/resume.schema'
|
||||||
|
import { PaymentOrder, PaymentOrderDocument } from '../src/modules/payment/payment-order.schema'
|
||||||
|
import { ShareRecord, ShareRecordDocument, ShareVisit, ShareVisitDocument } from '../src/modules/share/share.schema'
|
||||||
|
|
||||||
|
// 已知测试账号(保留这些不清理——防止误伤真实用户)
|
||||||
|
const KNOWN_TEST_ACCOUNTS = [
|
||||||
|
'test@yzrcloud.cn',
|
||||||
|
'test@test.com',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 管理员账号(始终保留)
|
||||||
|
const ADMIN_EMAILS = [
|
||||||
|
'13701190814@139.com',
|
||||||
|
]
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const isExecute = process.argv.includes('--execute')
|
||||||
|
const app = await NestFactory.createApplicationContext(AppModule)
|
||||||
|
|
||||||
|
const userModel = app.get<Model<UserDocument>>(getModelToken(User.name))
|
||||||
|
const interviewModel = app.get<Model<InterviewDocument>>(getModelToken(Interview.name))
|
||||||
|
const resumeModel = app.get<Model<ResumeDocument>>(getModelToken(Resume.name))
|
||||||
|
const orderModel = app.get<Model<PaymentOrderDocument>>(getModelToken(PaymentOrder.name))
|
||||||
|
const shareModel = app.get<Model<ShareRecordDocument>>(getModelToken(ShareRecord.name))
|
||||||
|
const shareVisitModel = app.get<Model<ShareVisitDocument>>(getModelToken(ShareVisit.name))
|
||||||
|
|
||||||
|
// Step 1: 找到所有管理员(保留)
|
||||||
|
const admins = await userModel.find({ role: 'admin' }).lean().exec()
|
||||||
|
const adminIds = admins.map(a => a._id.toString())
|
||||||
|
const adminEmails = admins.map(a => a.email).filter(Boolean)
|
||||||
|
|
||||||
|
// Step 2: 找到测试用户
|
||||||
|
const allUsers = await userModel.find().sort({ createdAt: -1 }).lean().exec()
|
||||||
|
|
||||||
|
const allAdminEmails = [...new Set([...ADMIN_EMAILS, ...adminEmails])]
|
||||||
|
const knownTestEmails = KNOWN_TEST_ACCOUNTS
|
||||||
|
|
||||||
|
// 测试用户识别策略:
|
||||||
|
// a) 已知测试邮箱
|
||||||
|
// b) 非管理员
|
||||||
|
// 实际用户自己补充识别条件
|
||||||
|
const testUserIds: string[] = []
|
||||||
|
const skippedUserIds: string[] = []
|
||||||
|
|
||||||
|
for (const u of allUsers) {
|
||||||
|
const uid = u._id.toString()
|
||||||
|
// 跳过管理员
|
||||||
|
if (adminIds.includes(uid)) {
|
||||||
|
skippedUserIds.push(uid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const userEmail = (u.email || '').toLowerCase().trim()
|
||||||
|
|
||||||
|
// 已知测试邮箱 → 标记为测试
|
||||||
|
if (knownTestEmails.includes(userEmail)) {
|
||||||
|
testUserIds.push(uid)
|
||||||
|
console.log(` [TEST] ${u.phone || '--'} / ${userEmail} / ${u.nickname || '--'}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找不到匹配条件的用户属于真正用户
|
||||||
|
skippedUserIds.push(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n========================================`)
|
||||||
|
console.log(`总用户: ${allUsers.length}`)
|
||||||
|
console.log(`管理员: ${adminIds.length}`)
|
||||||
|
console.log(`测试用户: ${testUserIds.length}`)
|
||||||
|
console.log(`真实用户: ${skippedUserIds.length}`)
|
||||||
|
console.log(`========================================\n`)
|
||||||
|
|
||||||
|
if (testUserIds.length === 0) {
|
||||||
|
console.log('未发现测试用户,无需清理。')
|
||||||
|
await app.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: 统计关联数据
|
||||||
|
const testInterviews = await interviewModel.find({ userId: { $in: testUserIds } }).lean().exec()
|
||||||
|
const testResumes = await resumeModel.find({ userId: { $in: testUserIds } }).lean().exec()
|
||||||
|
const testOrders = await orderModel.find({ userId: { $in: testUserIds } }).lean().exec()
|
||||||
|
const testShares = await shareModel.find({ userId: { $in: testUserIds } }).lean().exec()
|
||||||
|
|
||||||
|
console.log('将删除的关联数据:')
|
||||||
|
console.log(` - 面试记录: ${testInterviews.length} 条`)
|
||||||
|
console.log(` - 简历: ${testResumes.length} 条`)
|
||||||
|
console.log(` - 订单: ${testOrders.length} 条`)
|
||||||
|
console.log(` - 分享记录: ${testShares.length} 条`)
|
||||||
|
console.log(` - 用户账号: ${testUserIds.length} 个\n`)
|
||||||
|
|
||||||
|
if (!isExecute) {
|
||||||
|
console.log('⚠️ 预览模式,未执行删除。')
|
||||||
|
console.log(' 确认无误后执行: npx ts-node --project tsconfig.json scripts/cleanup-test-data.ts --execute\n')
|
||||||
|
await app.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: 执行删除(先删关联数据再删用户)
|
||||||
|
console.log('正在清理...')
|
||||||
|
|
||||||
|
// 分享访问记录(通过分享记录找)
|
||||||
|
const shareIds = testShares.map(s => s._id)
|
||||||
|
if (shareIds.length > 0) {
|
||||||
|
const visits = await shareVisitModel.deleteMany({ shareId: { $in: shareIds } }).exec()
|
||||||
|
console.log(` - 删除分享访问记录: ${visits.deletedCount} 条`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testShares.length > 0) {
|
||||||
|
const r = await shareModel.deleteMany({ _id: { $in: shareIds } }).exec()
|
||||||
|
console.log(` - 删除分享记录: ${r.deletedCount} 条`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testInterviews.length > 0) {
|
||||||
|
const r = await interviewModel.deleteMany({ _id: { $in: testInterviews.map(i => i._id) } }).exec()
|
||||||
|
console.log(` - 删除面试记录: ${r.deletedCount} 条`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testResumes.length > 0) {
|
||||||
|
const r = await resumeModel.deleteMany({ _id: { $in: testResumes.map(r => r._id) } }).exec()
|
||||||
|
console.log(` - 删除简历: ${r.deletedCount} 条`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testOrders.length > 0) {
|
||||||
|
const r = await orderModel.deleteMany({ _id: { $in: testOrders.map(o => o._id) } }).exec()
|
||||||
|
console.log(` - 删除订单: ${r.deletedCount} 条`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testUserIds.length > 0) {
|
||||||
|
const r = await userModel.deleteMany({ _id: { $in: testUserIds } }).exec()
|
||||||
|
console.log(` - 删除测试用户: ${r.deletedCount} 个`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ 清理完成!')
|
||||||
|
await app.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap().catch((err) => {
|
||||||
|
console.error('清理失败:', err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
import { onLaunch } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
console.log('职引 App launched')
|
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
initPrivacy()
|
initPrivacy()
|
||||||
// #endif
|
// #endif
|
||||||
@@ -49,8 +48,6 @@ function initPrivacy() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
onShow(() => { console.log('职引 App shown') })
|
|
||||||
onHide(() => { console.log('职引 App hidden') })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -377,7 +377,6 @@ function stopRecord() {
|
|||||||
name: 'audio',
|
name: 'audio',
|
||||||
header: { 'Authorization': `Bearer ${token()}` },
|
header: { 'Authorization': `Bearer ${token()}` },
|
||||||
})
|
})
|
||||||
console.log('[ASR] upload response:', uploadRes.statusCode, typeof uploadRes.data === 'string' ? uploadRes.data.slice(0, 200) : JSON.stringify(uploadRes.data).slice(0, 200))
|
|
||||||
if (uploadRes.statusCode === 200 && uploadRes.data) {
|
if (uploadRes.statusCode === 200 && uploadRes.data) {
|
||||||
const data = typeof uploadRes.data === 'string' ? JSON.parse(uploadRes.data) : uploadRes.data
|
const data = typeof uploadRes.data === 'string' ? JSON.parse(uploadRes.data) : uploadRes.data
|
||||||
if (data.text) {
|
if (data.text) {
|
||||||
|
|||||||
@@ -300,7 +300,6 @@ const doWxLogin = async () => {
|
|||||||
wxLoading.value = true
|
wxLoading.value = true
|
||||||
try {
|
try {
|
||||||
const wxResp = await uni.login()
|
const wxResp = await uni.login()
|
||||||
console.log('[wxLogin] uni.login success:', JSON.stringify(wxResp).slice(0, 300))
|
|
||||||
const { code, errMsg } = wxResp
|
const { code, errMsg } = wxResp
|
||||||
if (!code) { console.error('[wxLogin] no code:', errMsg); showToast('获取微信凭证失败'); return }
|
if (!code) { console.error('[wxLogin] no code:', errMsg); showToast('获取微信凭证失败'); return }
|
||||||
const res = await uni.request({
|
const res = await uni.request({
|
||||||
@@ -308,7 +307,6 @@ const doWxLogin = async () => {
|
|||||||
header: { 'Content-Type': 'application/json' },
|
header: { 'Content-Type': 'application/json' },
|
||||||
data: { code },
|
data: { code },
|
||||||
})
|
})
|
||||||
console.log('[wxLogin] server response:', res.statusCode, JSON.stringify(res.data).slice(0, 500))
|
|
||||||
if (res.statusCode === 200 && res.data?.token) {
|
if (res.statusCode === 200 && res.data?.token) {
|
||||||
loginSuccess(res.data)
|
loginSuccess(res.data)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user