From 85e00e296ffa811f2099f63cca5b816a1740a6af Mon Sep 17 00:00:00 2001 From: xcbyy <2578898740@qq.com> Date: Mon, 19 Oct 2026 09:19:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(companion):=20=E5=8E=BB=E9=99=A4=E5=90=88?= =?UTF-8?q?=E6=88=90=E8=99=9A=E6=8B=9F=E5=A4=A7=E7=A5=9E=E7=9A=84=E5=81=87?= =?UTF-8?q?=E8=AF=84=E5=88=86/=E4=BB=B7=E6=A0=BC/=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=EF=BC=88=E6=94=B9=E4=B8=BA=E8=AF=9A=E5=AE=9E=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getCompanionById 对非真实大神档案不再虚构 rating/orderCount/price/voice/服务包,状态为休息中、审核未申请,并返回 isCompanion=false - 小程序大神详情页对非大神档案隐藏服务与预约入口,显示'该用户尚未认证为大神';移除页面假默认值 - 新增回归护栏 companion-profile-honesty.spec.ts(82 套件/619 用例全绿) --- pwuniapp/pages/expert/detail.vue | 39 ++++++---- server/src/db/repository.ts | 78 ++++++++++--------- .../tests/companion-profile-honesty.spec.ts | 42 ++++++++++ smoke-batch66.ts | 45 +++++++++++ 4 files changed, 152 insertions(+), 52 deletions(-) create mode 100644 server/tests/companion-profile-honesty.spec.ts create mode 100644 smoke-batch66.ts diff --git a/pwuniapp/pages/expert/detail.vue b/pwuniapp/pages/expert/detail.vue index b666bc6..0b17c5e 100644 --- a/pwuniapp/pages/expert/detail.vue +++ b/pwuniapp/pages/expert/detail.vue @@ -70,8 +70,8 @@ {{ expert.bio }} - - + + 服务项目 举报 - - 当前选定: - ¥{{ currentSelectedService ? currentSelectedService.price : expert.price }} + + + 该用户尚未认证为大神,暂不可预约 - @@ -297,23 +302,25 @@ const expert = ref({ nickname: '同城陪伴师', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300', gender: 'female', - levelTag: '平台认证', + levelTag: '', game: '同城陪伴', - rankTag: '专业级', - city: '北京市 · 朝阳/海淀', - rating: 5.0, - orderCount: 86, - isOnline: true, - bio: '专业同城生活陪伴,健康阳光公开。', - voiceDuration: 15, - price: 68, - services: buildDefaultServices('同城陪伴', 68), + rankTag: '', + city: '', + rating: 0, + orderCount: 0, + isOnline: false, + bio: '', + voiceDuration: 0, + price: 0, + services: [], photos: [ 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400', 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400' ], reviews: [] }); +// 是否为真实大神档案(非合成的普通用户资料) +const isCompanion = computed(() => expert.value.isCompanion !== false); const selectedServiceId = ref('srv_1'); const isPlayingVoice = ref(false); let currentExpertTargetId = null; diff --git a/server/src/db/repository.ts b/server/src/db/repository.ts index 0bd354e..658da31 100644 --- a/server/src/db/repository.ts +++ b/server/src/db/repository.ts @@ -770,6 +770,8 @@ export class MysqlRepository { async getCompanionById(id: number | string): Promise { let c = await this.resolveCompanionRow(id); + // 是否为"非真实大神档案"的合成资料(用于对其隐藏虚构评分/价格/服务等) + let synthetic = false; if (!c) { const rawId = String(id).trim(); const numId = Number(rawId); @@ -822,30 +824,31 @@ export class MysqlRepository { c = { id: userRow?.id || postRow?.user_id || cmtRow?.user_id || (!isNaN(numId) && numId > 0 ? numId : 2005), user_id: userRow?.id || postRow?.user_id || cmtRow?.user_id || null, - name: postRow?.author || userRow?.nickname || cmtRow?.author || '同城认证达人', + name: postRow?.author || userRow?.nickname || cmtRow?.author || '同城用户', avatar: postRow?.avatar || userRow?.avatar || cmtRow?.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300', - gender: userRow?.gender === '男' ? '男' : '女', - age: 22, + gender: userRow?.gender === '男' ? '男' : (userRow?.gender === '女' ? '女' : '保密'), + age: 0, track: postRow?.category === 'game' ? '线上电竞' : '同城陪吃', - rank_name: postRow?.tag || userRow?.role || '同城认证达人', - hourly_rate: postRow?.category === 'game' ? 88 : 128, - voice_duration: 16, - voice_title: '真实原声介绍', - rating: 4.96, - order_count: Number(userRow?.order_count) || 86, - status: '接单中', - audit_status: '已通过', - tags: postRow?.topics || JSON.stringify(['同城约伴', '随和健谈', '准时守信']), - custom_bio: postRow?.content || userRow?.signature || cmtRow?.content || '专业同城生活陪伴,热情礼貌,守时诚信。', - custom_city: postRow?.location || '北京市 · 朝阳/海淀', + rank_name: postRow?.tag || userRow?.role || '', + hourly_rate: 0, + voice_duration: 0, + voice_title: '', + rating: 0, + order_count: 0, + status: '休息中', + audit_status: '未申请', + tags: postRow?.topics || JSON.stringify([]), + custom_bio: postRow?.content || userRow?.signature || cmtRow?.content || '', + custom_city: postRow?.location || '', custom_photos: postImages.length > 0 ? postImages : null }; + synthetic = true; } if (!c) return null; const game = c.track === 'OFFLINE_MEAL' ? '美食陪吃' : (c.track === 'OFFLINE_SING' ? '同城陪唱' : (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.track === 'OFFLINE_PLAY' ? '同城陪玩' : (c.track || c.game || '同城生活陪伴')))); const price = Number(c.hourly_rate) || 68; - let parsedTags = ['高颜值', '性格好', '秒回消息', '文明健康']; + let parsedTags = synthetic ? [] : ['高颜值', '性格好', '秒回消息', '文明健康']; if (c.tags) { try { const t = typeof c.tags === 'string' ? JSON.parse(c.tags) : c.tags; @@ -905,32 +908,35 @@ export class MysqlRepository { avatar: c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300', coverImage: (c.custom_photos && c.custom_photos[0]) || c.avatar || 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800', gender: c.gender === '男' ? 'male' : 'female', - age: Number(c.age) || 22, + age: synthetic ? 0 : (Number(c.age) || 22), game: game, track: c.track || 'OFFLINE_MEAL', - city: c.custom_city || '北京市 · 朝阳/海淀', - rankTag: c.rank_name || '认证大神', - levelTag: '金牌认证', - price: price, - hourlyRate: price, - rating: Number(c.rating) || 5.0, - orderCount: Number(c.order_count) || 120, - status: c.status || '接单中', - auditStatus, - certStatus, + city: synthetic ? (c.custom_city || '') : (c.custom_city || '北京市 · 朝阳/海淀'), + rankTag: c.rank_name || (synthetic ? '' : '认证大神'), + levelTag: synthetic ? '普通用户' : '金牌认证', + price: synthetic ? 0 : price, + hourlyRate: synthetic ? 0 : price, + rating: synthetic ? 0 : (Number(c.rating) || 5.0), + orderCount: synthetic ? 0 : (Number(c.order_count) || 120), + status: synthetic ? '休息中' : (c.status || '接单中'), + auditStatus: synthetic ? '未申请' : auditStatus, + certStatus: synthetic ? 0 : certStatus, rejectReason: c.reject_reason || '', - isOnline: c.status === '接单中', - voiceDuration: Number(c.voice_duration) || 15, + isOnline: synthetic ? false : c.status === '接单中', + isCompanion: !synthetic, + voiceDuration: synthetic ? 0 : (Number(c.voice_duration) || 15), voiceType: 'voice', - voiceTitle: c.voice_title || '语音招呼', - bio: c.custom_bio || '专业同城生活陪伴,热情礼貌,守时诚信。', + voiceTitle: synthetic ? '' : (c.voice_title || '语音招呼'), + bio: c.custom_bio || (synthetic ? '' : '专业同城生活陪伴,热情礼貌,守时诚信。'), tags: parsedTags, - photos: c.custom_photos || [ - c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400', - 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400', - 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=400' - ], - services: customServices.length > 0 ? customServices : defaultServices + photos: c.custom_photos || (synthetic + ? [c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400'] + : [ + c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400', + 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400', + 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=400' + ]), + services: synthetic ? [] : (customServices.length > 0 ? customServices : defaultServices) }; } diff --git a/server/tests/companion-profile-honesty.spec.ts b/server/tests/companion-profile-honesty.spec.ts new file mode 100644 index 0000000..ff91347 --- /dev/null +++ b/server/tests/companion-profile-honesty.spec.ts @@ -0,0 +1,42 @@ +// 已读取AGENTS.md +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * 第六十六批回归闸门:去"合成虚拟大神"假数据 + */ +describe('第六十六批大神档案真实性闸门', () => { + const rootDir = path.resolve(__dirname, '../../'); + const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8'); + const detail = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/expert/detail.vue'), 'utf-8'); + + const synthBlock = () => { + const start = repoCode.indexOf('async getCompanionById('); + expect(start).toBeGreaterThan(-1); + return repoCode.slice(start, start + 9000); + }; + + it('合成档案不再虚构评分/单量/价格/语音/状态', () => { + const b = synthBlock(); + expect(b).toContain('let synthetic = false;'); + expect(b).toContain('synthetic = true;'); + expect(b).toContain('rating: 0'); + expect(b).toContain('order_count: 0'); + expect(b).toContain('hourly_rate: 0'); + expect(b).toContain("status: '休息中'"); + expect(b).toContain("audit_status: '未申请'"); + // 合成档案不返回服务包 + expect(b).toContain('services: synthetic ? []'); + // 标记是否为真实大神 + expect(b).toContain('isCompanion: !synthetic'); + }); + + it('小程序对非大神档案隐藏服务与预约并去除假默认值', () => { + expect(detail).toContain('const isCompanion = computed('); + expect(detail).toContain('v-if="isCompanion"'); + expect(detail).toContain('该用户尚未认证为大神,暂不可预约'); + expect(detail).not.toContain('rating: 5.0'); + expect(detail).not.toContain('orderCount: 86'); + expect(detail).not.toContain('price: 68'); + }); +}); diff --git a/smoke-batch66.ts b/smoke-batch66.ts new file mode 100644 index 0000000..c145f22 --- /dev/null +++ b/smoke-batch66.ts @@ -0,0 +1,45 @@ +import { initDatabase, pool } from './server/src/db/mysql'; +import { repo } from './server/src/db/repository'; + +async function main() { + await initDatabase(); + const stamp = Date.now(); + const uid = (stamp % 800000000) + 100000001; + + const results: Array<[string, boolean, string]> = []; + const check = (name: string, ok: boolean, detail = '') => results.push([name, ok, detail]); + + try { + await pool.query( + `INSERT INTO users (id, user_no, nickname, avatar, phone, gender, role, balance, order_count, status) + VALUES (?, ?, 'smoke-plain-user', '', '13100001111', '保密', '普通玩家', 0, 5, '正常')`, + [uid, `SMPU${stamp}`] + ); + + const p: any = await repo.getCompanionById(uid); + check('普通用户档案标记 isCompanion=false', p && p.isCompanion === false, JSON.stringify({ isCompanion: p && p.isCompanion })); + check('评分不再虚构(0)', Number(p.rating) === 0, String(p && p.rating)); + check('单量不再虚构(0)', Number(p.orderCount) === 0, String(p && p.orderCount)); + check('价格不再虚构(0)', Number(p.price) === 0, String(p && p.price)); + check('不返回虚构服务包', Array.isArray(p.services) && p.services.length === 0, JSON.stringify(p && p.services)); + check('状态为休息中', p.status === '休息中', String(p && p.status)); + check('审核状态为未申请', p.auditStatus === '未申请', String(p && p.auditStatus)); + } finally { + await pool.query('DELETE FROM companions WHERE user_id = ?', [uid]); + await pool.query('DELETE FROM users WHERE id = ?', [uid]); + } + + let allPass = true; + for (const [n, ok, detail] of results) { + if (!ok) allPass = false; + console.log(`${ok ? 'PASS' : 'FAIL'} ${n} ${detail}`); + } + console.log(allPass ? '\nBATCH66 SMOKE: ALL PASS' : '\nBATCH66 SMOKE: FAILED'); + await pool.end(); + process.exit(allPass ? 0 : 1); +} + +main().catch((e) => { + console.error('SMOKE ERROR', e); + process.exit(2); +});