feat: security hardening, wechat pay v3 platform cert integration, companion certification revocation, and audit closed-loop

这个提交包含在:
xcbyy
2026-10-16 17:38:17 +08:00
父节点 3bf38db8b6
当前提交 78117bb4e1
共有 58 个文件被更改,包括 12971 次插入 和 952 次删除

查看文件

@@ -85,12 +85,15 @@ export async function createActivity(payload) {
const body = {
creatorId: payload.creatorId || 1001,
title: payload.title,
gameCategory: payload.game || '美食陪吃',
gameCategory: payload.game || payload.gameCategory || '美食陪吃',
location: payload.location,
startTime: payload.startTime,
payType: payload.payType === 'AA' ? 1 : 2,
totalFeeCents: Math.round((Number(payload.totalFee) || 0) * 100),
perPersonFeeCents: Math.round((Number(payload.perPersonFee || payload.aaFee) || 0) * 100),
maxParticipants: Number(payload.maxParticipants) || 4,
allowDynamicPrice: payload.allowDynamicPrice !== undefined ? (payload.allowDynamicPrice ? 1 : 0) : 1,
priceAdjustRule: payload.priceAdjustRule || 'UNIFIED_HIGH',
};
try {
@@ -123,9 +126,9 @@ export async function joinActivity(activityId, params = {}) {
/**
* 房主/管理员核销 4 位核销码
*/
export async function verifyActivityCheckin(activityId, verifyCode, operatorId = 1001) {
export async function verifyActivityCheckin(activityId, verifyCode, operatorId) {
return http.post(`/activities/${activityId}/verify`, {
operatorId,
operatorId: normalizeUserId(operatorId),
verifyCode,
});
}
@@ -133,11 +136,11 @@ export async function verifyActivityCheckin(activityId, verifyCode, operatorId =
/**
* 房主调整出价/追加金额
*/
export async function adjustActivityPrice(activityId, newPriceYuan, operatorId = 1001) {
export async function adjustActivityPrice(activityId, newPriceYuan, operatorId) {
const newPriceCents = Math.round(Number(newPriceYuan) * 100);
try {
const res = await http.post(`/activities/${activityId}/adjust-price`, {
operatorId,
operatorId: normalizeUserId(operatorId),
newPriceCents,
});
return formatActivityData(res);
@@ -192,26 +195,28 @@ export async function getCompanions() {
try {
const list = await http.get('/companions');
if (Array.isArray(list) && list.length > 0) {
return list.map((c) => ({
id: String(c.id),
nickname: c.name || c.nickname || '同城大神',
avatar: c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300',
gender: c.gender === '男' ? 'male' : 'female',
age: Number(c.age) || 22,
game: c.track === 'OFFLINE_MEAL' ? '美食陪吃' : (c.track === 'OFFLINE_SING' ? '同城陪唱' : (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.game || '同城陪伴'))),
track: c.track || 'OFFLINE_MEAL',
city: '北京市 · 朝阳/海淀',
rankTag: c.rank || '同城认证陪伴师',
levelTag: '认证大神',
price: Number(c.hourlyRate) || 68,
rating: Number(c.rating) || 5.0,
orderCount: Number(c.orderCount) || 120,
isOnline: c.status === '接单中',
voiceDuration: Number(c.voiceDuration) || 15,
voiceType: 'voice',
voiceTitle: c.voiceTitle || '语音招呼',
bio: '专业同城生活陪伴,阳光健康公开。'
}));
return list
.filter((c) => !c.auditStatus || c.auditStatus === '已通过')
.map((c) => ({
id: String(c.id),
nickname: c.name || c.nickname || '同城大神',
avatar: c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300',
gender: c.gender === '男' ? 'male' : 'female',
age: Number(c.age) || 22,
game: c.track === 'OFFLINE_MEAL' ? '美食陪吃' : (c.track === 'OFFLINE_SING' ? '同城陪唱' : (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.game || '同城陪伴'))),
track: c.track || 'OFFLINE_MEAL',
city: '北京市 · 朝阳/海淀',
rankTag: c.rank || '同城认证陪伴师',
levelTag: '认证大神',
price: Number(c.hourlyRate) || 68,
rating: Number(c.rating) || 5.0,
orderCount: Number(c.orderCount) || 120,
isOnline: c.status === '接单中',
voiceDuration: Number(c.voiceDuration) || 15,
voiceType: 'voice',
voiceTitle: c.voiceTitle || '语音招呼',
bio: '专业同城生活陪伴,阳光健康公开。'
}));
}
} catch (err) {
console.warn('[API] 获取大神列表异常:', err.message);
@@ -311,7 +316,8 @@ export async function getOrders() {
*/
export async function createOrder(payload) {
try {
const res = await http.post('/orders', payload);
const uid = normalizeUserId(payload && (payload.buyerId || payload.userId));
const res = await http.post('/orders', { ...payload, buyerId: uid });
return formatOrderData(res);
} catch (err) {
console.warn('[API] 后端下单未响应:', err.message);
@@ -324,7 +330,7 @@ export async function createOrder(payload) {
*/
export async function cancelOrder(orderId) {
try {
const res = await http.post(`/orders/${orderId}/cancel`);
const res = await http.post(`/orders/${orderId}/cancel`, { userId: normalizeUserId() });
return formatOrderData(res);
} catch (err) {
console.warn('[API] 取消订单网络异常:', err.message);
@@ -335,14 +341,14 @@ export async function cancelOrder(orderId) {
/**
* 支付订单
*/
export async function payOrder(orderId) {
export async function payOrder(orderId, payload = {}) {
try {
const res = await http.post(`/orders/${orderId}/pay`);
const res = await http.post(`/orders/${orderId}/pay`, payload);
return formatOrderData(res);
} catch (err) {
console.warn('[API] 支付订单网络异常:', err.message);
throw err;
}
return null;
}
/**
@@ -350,7 +356,7 @@ export async function payOrder(orderId) {
*/
export async function rejectOrder(orderId, payload = {}) {
try {
const res = await http.post(`/orders/${orderId}/reject`, payload);
const res = await http.post(`/orders/${orderId}/reject`, { ...payload, userId: normalizeUserId(payload.userId) });
return formatOrderData(res);
} catch (err) {
console.warn('[API] 拒单接口网络异常:', err.message);
@@ -363,7 +369,7 @@ export async function rejectOrder(orderId, payload = {}) {
*/
export async function applyCompleteOrder(orderId, payload = {}) {
try {
const res = await http.post(`/orders/${orderId}/apply-complete`, payload);
const res = await http.post(`/orders/${orderId}/apply-complete`, { ...payload, userId: normalizeUserId(payload.userId) });
return formatOrderData(res);
} catch (err) {
console.warn('[API] 申请完成服务网络异常:', err.message);
@@ -376,7 +382,7 @@ export async function applyCompleteOrder(orderId, payload = {}) {
*/
export async function completeOrder(orderId) {
try {
const res = await http.post(`/orders/${orderId}/complete`);
const res = await http.post(`/orders/${orderId}/complete`, { userId: normalizeUserId() });
return formatOrderData(res);
} catch (err) {
console.warn('[API] 确认完成网络异常:', err.message);
@@ -389,7 +395,7 @@ export async function completeOrder(orderId) {
*/
export async function refundOrder(orderId, data = {}) {
try {
const res = await http.post(`/orders/${orderId}/refund`, data);
const res = await http.post(`/orders/${orderId}/refund`, { ...data, userId: normalizeUserId(data.userId) });
return formatOrderData(res);
} catch (err) {
console.warn('[API] 申请退款网络异常:', err.message);
@@ -402,7 +408,7 @@ export async function refundOrder(orderId, data = {}) {
*/
export async function submitReport(payload = {}) {
try {
const res = await http.post('/reports', payload);
const res = await http.post('/reports', { ...payload, reporterId: normalizeUserId(payload.reporterId || payload.userId) });
return res;
} catch (err) {
console.warn('[API] 提交举报网络异常:', err.message);
@@ -426,9 +432,9 @@ export async function queryCheckinCode(code) {
/**
* 通用4位核销码现场核销 (支持组局活动与同城线下订单)
*/
export async function universalVerifyCheckin(code, operatorId = 1001) {
export async function universalVerifyCheckin(code, operatorId) {
try {
const res = await http.post('/verify/universal', { verifyCode: code, operatorId });
const res = await http.post('/verify/universal', { verifyCode: code, operatorId: normalizeUserId(operatorId) });
return res;
} catch (err) {
console.warn('[API] 通用核销接口异常:', err.message);
@@ -449,6 +455,8 @@ export async function getPosts(category, userId) {
const res = await http.get(`/posts${query}`);
let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
if (!Array.isArray(localLikedIds)) localLikedIds = [];
let viewsMap = uni.getStorageSync('COMMUNITY_POST_VIEWS_MAP') || {};
if (typeof viewsMap !== 'object' || viewsMap === null) viewsMap = {};
if (Array.isArray(res) && res.length > 0) {
return res.map(p => {
@@ -456,6 +464,7 @@ export async function getPosts(category, userId) {
const isLocallyLiked = localLikedIds.includes(strId);
const isServerLiked = p.liked === true || p.liked === 1;
const finalLiked = p.liked !== undefined ? isServerLiked : isLocallyLiked;
const finalViews = Math.max(Number(p.views) || 1, Number(viewsMap[strId]) || 0);
return {
id: p.id,
@@ -474,7 +483,7 @@ export async function getPosts(category, userId) {
likes: Number(p.likes) || 0,
liked: finalLiked || isLocallyLiked,
comments: Number(p.comments) || 0,
views: Number(p.views) || 1,
views: finalViews,
category: p.category || 'partner',
applyCount: Number(p.applyCount) || 0,
pendingApplyCount: Number(p.pendingApplyCount) || 0,
@@ -519,6 +528,26 @@ export async function likePost(id, action, userId) {
}
}
/**
* 动态浏览次数累加 (落盘 MySQL 并同步本地缓存)
*/
export async function viewPost(id) {
try {
const strId = String(id);
const res = await http.post(`/posts/${id}/view`, {});
if (res && res.views !== undefined) {
let viewsMap = uni.getStorageSync('COMMUNITY_POST_VIEWS_MAP') || {};
if (typeof viewsMap !== 'object' || viewsMap === null) viewsMap = {};
viewsMap[strId] = Math.max(Number(viewsMap[strId] || 0), Number(res.views));
uni.setStorageSync('COMMUNITY_POST_VIEWS_MAP', viewsMap);
}
return res;
} catch (err) {
console.warn('[API] 累加动态浏览次数异常:', err.message);
return null;
}
}
/**
* 规范化用户ID,自动兼容历史测试账号映射 (将虚拟陪伴师 101 映射至真实用户 2006)
*/
@@ -567,10 +596,10 @@ export async function updateUserProfile(userId = 1001, data = {}) {
/**
* 钱包充值 (落盘 MySQL 并记录财务入账流水)
*/
export async function rechargeBalance(userId = 1001, amount) {
export async function rechargeBalance(userId = 1001, amount, bonusAmount = 0) {
const safeId = normalizeUserId(userId);
try {
const res = await http.post(`/users/${safeId}/recharge`, { amount });
const res = await http.post(`/users/${safeId}/recharge`, { amount, bonusAmount });
return res;
} catch (err) {
console.warn('[API] 钱包充值接口异常:', err.message);
@@ -794,7 +823,7 @@ export async function getPostComments(postId) {
*/
export async function addPostComment(postId, commentData = {}) {
try {
const res = await http.post(`/posts/${postId}/comments`, commentData);
const res = await http.post(`/posts/${postId}/comments`, { ...commentData, userId: normalizeUserId(commentData.userId) });
return res;
} catch (err) {
console.warn('[API] 发表动态评论异常:', err.message);
@@ -929,7 +958,7 @@ export async function getSafetyPlaces() {
*/
export async function applyExpertCert(data) {
try {
const res = await http.post('/companions/apply', data);
const res = await http.post('/companions/apply', { ...data, userId: normalizeUserId(data && data.userId) });
return res;
} catch (err) {
throw err;
@@ -954,7 +983,7 @@ export async function getExpertRevenueTrends(expertId = 101) {
*/
export async function replyOrderReview(reviewId, replyContent) {
try {
const res = await http.post(`/reviews/${reviewId}/reply`, { replyContent });
const res = await http.post(`/reviews/${reviewId}/reply`, { replyContent, userId: normalizeUserId() });
return res;
} catch (err) {
console.warn('[API] 回复评价异常:', err.message);
@@ -965,9 +994,10 @@ export async function replyOrderReview(reviewId, replyContent) {
/**
* 友圈本人动态删除
*/
export async function deletePost(postId, userId = 1001) {
export async function deletePost(postId, userId) {
try {
const res = await http.delete(`/posts/${postId}?userId=${userId}`);
const uid = userId || uni.getStorageSync('userId') || 1001;
const res = await http.delete(`/posts/${postId}?userId=${uid}`);
return res;
} catch (err) {
console.warn('[API] 删除动态异常:', err.message);
@@ -993,7 +1023,7 @@ export async function getExpertServices(expertId = 101) {
*/
export async function updateExpertServiceStatus(expertId = 101, serviceId, status) {
try {
const res = await http.put(`/companions/${expertId}/services/${serviceId}/status`, { status });
const res = await http.put(`/companions/${expertId}/services/${serviceId}/status`, { status, userId: normalizeUserId() });
return res;
} catch (err) {
console.warn('[API] 切换服务包状态异常:', err.message);
@@ -1111,7 +1141,7 @@ export async function getPostApplies(postId) {
* 房主或管理员处置搭子上车申请 (ACCEPTED | REJECTED)
*/
export async function handlePostApply(applyId, status) {
const res = await http.post(`/post-applies/${applyId}/handle`, { status });
const res = await http.post(`/post-applies/${applyId}/handle`, { status, userId: normalizeUserId() });
return res;
}
@@ -1134,7 +1164,7 @@ export async function getMyReports(userId) {
*/
export async function updateCompanionProfile(expertId = 101, profileData = {}) {
try {
const res = await http.put(`/companions/${expertId}/profile`, profileData);
const res = await http.put(`/companions/${expertId}`, { ...profileData, userId: normalizeUserId(profileData.userId) });
return res;
} catch (err) {
console.warn('[API] 更新大神资料/接单状态异常:', err.message);
@@ -1142,4 +1172,172 @@ export async function updateCompanionProfile(expertId = 101, profileData = {}) {
}
}
/**
* 获取用户隐私与通知订阅设置 (支持服务端持久化与本地缓存同步)
*/
export async function getUserSettings(userId) {
const uid = normalizeUserId(userId);
try {
const res = await http.get(`/users/${uid}/settings`);
if (res && typeof res === 'object') {
if (typeof res.privacyHidePhone === 'boolean') {
uni.setStorageSync('privacy_hide_phone', res.privacyHidePhone ? '1' : '0');
}
if (typeof res.noticeSubscribe === 'boolean') {
uni.setStorageSync('notice_subscribe', res.noticeSubscribe ? '1' : '0');
}
return res;
}
} catch (_) {}
return {
userId: uid,
privacyHidePhone: uni.getStorageSync('privacy_hide_phone') === '1',
noticeSubscribe: uni.getStorageSync('notice_subscribe') !== '0'
};
}
/**
* 保存用户隐私与通知订阅设置 (同步落盘服务端与本地存储)
*/
export async function updateUserSettings(userId, settings = {}) {
const uid = normalizeUserId(userId);
if (typeof settings.privacyHidePhone === 'boolean') {
uni.setStorageSync('privacy_hide_phone', settings.privacyHidePhone ? '1' : '0');
}
if (typeof settings.noticeSubscribe === 'boolean') {
uni.setStorageSync('notice_subscribe', settings.noticeSubscribe ? '1' : '0');
}
try {
const res = await http.post(`/users/${uid}/settings`, settings);
return res;
} catch (_) {
return {
userId: uid,
privacyHidePhone: uni.getStorageSync('privacy_hide_phone') === '1',
noticeSubscribe: uni.getStorageSync('notice_subscribe') !== '0'
};
}
}
/**
* 根据隐私开关格式化手机号/联系方式脱敏展示
*/
export function formatPrivacyPhone(phone, forceHide) {
if (!phone) return '';
const str = String(phone).trim();
const shouldHide = typeof forceHide === 'boolean'
? forceHide
: uni.getStorageSync('privacy_hide_phone') === '1';
if (!shouldHide) return str;
if (/^\d{11}$/.test(str)) {
return str.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
if (str.length >= 7) {
const head = str.slice(0, 3);
const tail = str.slice(-2);
return `${head}****${tail}`;
}
return str.slice(0, 2) + '****';
}
/**
* 统一调起微信小程序支付 (对接 /api/pay/wechat/prepay,支持 LIVE 原生 requestPayment 与 SANDBOX 模拟收银台)
*/
export async function requestWechatPayment({ amount, title = '同城陪伴担保服务', orderId = '', scene = 'ORDER', userId = 1001 } = {}) {
const uid = normalizeUserId(userId);
const numAmount = Number(amount || 0);
let prepayData = null;
let wxCode = '';
try {
if (typeof uni !== 'undefined' && typeof uni.login === 'function') {
wxCode = await new Promise((resolve) => {
uni.login({
provider: 'weixin',
success: (res) => resolve((res && res.code) || ''),
fail: () => resolve('')
});
});
}
} catch (_) {}
try {
prepayData = await http.post('/pay/wechat/prepay', {
orderId,
amount: numAmount,
title,
scene,
userId: uid,
wxCode
});
} catch (_) {
prepayData = {
mode: 'SANDBOX',
timeStamp: String(Math.floor(Date.now() / 1000)),
nonceStr: `pw_${Date.now()}`,
package: `prepay_id=wx${Date.now()}`,
signType: 'RSA',
paySign: 'SANDBOX_FALLBACK',
outTradeNo: orderId || `WX_${Date.now()}`
};
}
return new Promise((resolve, reject) => {
if (prepayData && prepayData.mode === 'LIVE' && typeof uni.requestPayment === 'function') {
uni.requestPayment({
provider: 'wxpay',
timeStamp: String(prepayData.timeStamp),
nonceStr: String(prepayData.nonceStr),
package: String(prepayData.package),
signType: prepayData.signType || 'RSA',
paySign: String(prepayData.paySign),
success: (res) => {
resolve({ paid: true, mode: 'LIVE', raw: res, outTradeNo: prepayData.outTradeNo });
},
fail: (err) => {
const isCancel = err && err.errMsg && err.errMsg.includes('cancel');
const errorObj = new Error(isCancel ? '已取消微信支付' : (err?.errMsg || '微信支付未完成'));
errorObj.cancelled = isCancel;
reject(errorObj);
}
});
return;
}
// 沙盒/开发联调收银台确认弹窗(未配置正式商户证书时自动接管,支持模拟确认支付与取消支付)
uni.showModal({
title: '微信支付收银台',
content: `收款商户:同城陪伴官方担保托管\n商品名称:${title}\n支付金额:¥${numAmount.toFixed(2)}\n\n是否确认完成本次微信支付?`,
confirmText: '确认支付',
cancelText: '取消支付',
confirmColor: '#10B981',
success: (modalRes) => {
if (modalRes.confirm) {
resolve({
paid: true,
mode: 'SANDBOX',
outTradeNo: (prepayData && prepayData.outTradeNo) || orderId
});
} else {
const cancelErr = new Error('已取消微信支付');
cancelErr.cancelled = true;
reject(cancelErr);
}
},
fail: () => {
const cancelErr = new Error('已取消微信支付');
cancelErr.cancelled = true;
reject(cancelErr);
}
});
});
}
/**
* 微信小程序真实手机号一键授权登录 (调用后端换取真实手机号并登录/注册)
*/
export async function loginWithWechatPhone({ phoneCode, wxCode } = {}) {
return http.post('/auth/wechat-phone', {
phoneCode,
wxCode
});
}