feat(security): 三端闭环加固 + 微信提现打款/订阅消息落地
后端: 身份与归属鉴权收口; 资金安全(结算闸门/支付防篡改/报名费真实扣款/赠金/结算唯一入账/提现打款); 数据泄露与隐私; 登录与密钥安全(默认密钥闸门/登录限流); 可观测性; 启动自检. 微信: 商家转账到零钱提现打款闭环(含回调/查单/失败退回佣金); 订阅消息真实下发闭环与后台模板字段对齐. 后台: 财务/微信支付配置读取失败锁定; 看板/待办/走势去假数据; 打款与测试推送文案修正. 小程序: 请求层错误透传与登录过期提示; 多页失败反馈与乐观回滚; 帮助中心FAQ页面化. 测试: 新增 40+ 回归护栏与真实 MySQL 冒烟(75 套件/599 用例全绿).
这个提交包含在:
@@ -17,7 +17,7 @@ export function formatActivityData(raw) {
|
||||
userId: p.userId,
|
||||
nickname: p.nickname || `搭子_${p.userId || p.id}`,
|
||||
avatar: p.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100',
|
||||
verifyCode: p.verifyCode || '8392',
|
||||
verifyCode: p.verifyCode || '',
|
||||
checkedIn: Boolean(p.checkinStatus || p.checkedIn),
|
||||
payAmount: p.payAmountCents != null ? p.payAmountCents / 100 : (p.payAmount || 0),
|
||||
}));
|
||||
@@ -68,7 +68,7 @@ export async function getActivities() {
|
||||
*/
|
||||
export async function getActivityDetail(id) {
|
||||
try {
|
||||
const detail = await http.get(`/activities/${id}`);
|
||||
const detail = await http.get(`/activities/${id}?userId=${normalizeUserId()}`);
|
||||
if (detail) {
|
||||
return formatActivityData(detail);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export async function getActivityDetail(id) {
|
||||
*/
|
||||
export async function createActivity(payload) {
|
||||
const body = {
|
||||
creatorId: payload.creatorId || 1001,
|
||||
creatorId: normalizeUserId(payload.creatorId),
|
||||
title: payload.title,
|
||||
gameCategory: payload.game || payload.gameCategory || '美食陪吃',
|
||||
location: payload.location,
|
||||
@@ -110,7 +110,7 @@ export async function createActivity(payload) {
|
||||
*/
|
||||
export async function joinActivity(activityId, params = {}) {
|
||||
const body = {
|
||||
userId: params.userId || 2008,
|
||||
userId: normalizeUserId(params.userId),
|
||||
payAmountCents: Math.round((Number(params.payAmount) || 50) * 100),
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@ export async function adjustActivityPrice(activityId, newPriceYuan, operatorId)
|
||||
*/
|
||||
export async function cancelActivity(activityId, hostId = 1001, reason = '房主主动解散') {
|
||||
return http.post(`/activities/${activityId}/cancel`, {
|
||||
hostId,
|
||||
hostId: normalizeUserId(hostId),
|
||||
reason,
|
||||
});
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export async function cancelActivity(activityId, hostId = 1001, reason = '房主
|
||||
*/
|
||||
export async function leaveActivity(activityId, userId = 2001) {
|
||||
return http.post(`/activities/${activityId}/leave`, {
|
||||
userId,
|
||||
userId: normalizeUserId(userId),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ export function formatOrderData(raw) {
|
||||
*/
|
||||
export async function getOrders() {
|
||||
try {
|
||||
const list = await http.get('/orders');
|
||||
const list = await http.get(`/orders?userId=${normalizeUserId()}`);
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
return list.map(formatOrderData);
|
||||
}
|
||||
@@ -334,8 +334,9 @@ export async function cancelOrder(orderId) {
|
||||
return formatOrderData(res);
|
||||
} catch (err) {
|
||||
console.warn('[API] 取消订单网络异常:', err.message);
|
||||
// 写操作失败必须抛出,供调用方提示并阻止"假成功"
|
||||
throw err;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +344,7 @@ export async function cancelOrder(orderId) {
|
||||
*/
|
||||
export async function payOrder(orderId, payload = {}) {
|
||||
try {
|
||||
const res = await http.post(`/orders/${orderId}/pay`, payload);
|
||||
const res = await http.post(`/orders/${orderId}/pay`, { ...payload, userId: normalizeUserId(payload.userId) });
|
||||
return formatOrderData(res);
|
||||
} catch (err) {
|
||||
console.warn('[API] 支付订单网络异常:', err.message);
|
||||
@@ -386,8 +387,9 @@ export async function completeOrder(orderId) {
|
||||
return formatOrderData(res);
|
||||
} catch (err) {
|
||||
console.warn('[API] 确认完成网络异常:', err.message);
|
||||
// 写操作失败必须抛出,供调用方提示并阻止"假成功"
|
||||
throw err;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,7 +423,7 @@ export async function submitReport(payload = {}) {
|
||||
*/
|
||||
export async function queryCheckinCode(code) {
|
||||
try {
|
||||
const res = await http.get(`/verify/query/${code}`);
|
||||
const res = await http.get(`/verify/query/${code}?userId=${normalizeUserId()}`);
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn('[API] 反查核销码失败:', err.message);
|
||||
@@ -502,7 +504,7 @@ export async function getPosts(category, userId) {
|
||||
*/
|
||||
export async function createPost(payload) {
|
||||
try {
|
||||
const res = await http.post('/posts', payload);
|
||||
const res = await http.post('/posts', { ...(payload || {}), userId: normalizeUserId(payload && payload.userId) });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn('[API] 发布动态异常:', err.message);
|
||||
@@ -524,7 +526,8 @@ export async function likePost(id, action, userId) {
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn('[API] 点赞动态异常:', err.message);
|
||||
return null;
|
||||
// 点赞是写操作,失败必须向上抛出,避免调用方误以为已落盘
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,7 +690,7 @@ export async function getOrdersPool() {
|
||||
*/
|
||||
export async function claimOrder(orderId, expertData = {}) {
|
||||
try {
|
||||
const res = await http.post(`/orders/${orderId}/claim`, expertData);
|
||||
const res = await http.post(`/orders/${orderId}/claim`, { ...expertData, userId: normalizeUserId(expertData.userId) });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn('[API] 大神抢单异常:', err.message);
|
||||
@@ -780,7 +783,7 @@ export async function getMarketingCoupons(category) {
|
||||
*/
|
||||
export async function submitOrderReview(orderId, reviewData = {}) {
|
||||
try {
|
||||
const res = await http.post(`/orders/${orderId}/review`, reviewData);
|
||||
const res = await http.post(`/orders/${orderId}/review`, { ...reviewData, userId: normalizeUserId(reviewData.userId) });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn('[API] 提交订单评价异常:', err.message);
|
||||
@@ -836,7 +839,7 @@ export async function addPostComment(postId, commentData = {}) {
|
||||
*/
|
||||
export async function applyPostParty(postId, applyData = {}) {
|
||||
try {
|
||||
const res = await http.post(`/posts/${postId}/apply`, applyData);
|
||||
const res = await http.post(`/posts/${postId}/apply`, { ...applyData, applicantId: normalizeUserId(applyData.applicantId) });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.warn('[API] 申请搭子上车异常:', err.message);
|
||||
@@ -996,7 +999,7 @@ export async function replyOrderReview(reviewId, replyContent) {
|
||||
*/
|
||||
export async function deletePost(postId, userId) {
|
||||
try {
|
||||
const uid = userId || uni.getStorageSync('userId') || 1001;
|
||||
const uid = normalizeUserId(userId);
|
||||
const res = await http.delete(`/posts/${postId}?userId=${uid}`);
|
||||
return res;
|
||||
} catch (err) {
|
||||
@@ -1129,7 +1132,7 @@ export async function bindInviteCode(userId, inviteCode) {
|
||||
*/
|
||||
export async function getPostApplies(postId) {
|
||||
try {
|
||||
const res = await http.get(`/posts/${postId}/applies`);
|
||||
const res = await http.get(`/posts/${postId}/applies?userId=${normalizeUserId()}`);
|
||||
return res || [];
|
||||
} catch (err) {
|
||||
console.warn('[API] 获取搭子上车申请列表异常:', err.message);
|
||||
@@ -1196,6 +1199,19 @@ export async function getUserSettings(userId) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信订阅消息模板ID (用于 wx.requestSubscribeMessage 请求用户授权;模板ID非敏感)
|
||||
*/
|
||||
export async function getSubscribeTemplates() {
|
||||
try {
|
||||
const res = await http.get('/message/subscribe-templates');
|
||||
return res || { enablePush: false };
|
||||
} catch (err) {
|
||||
console.warn('[API] 获取订阅消息模板异常:', err.message);
|
||||
return { enablePush: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户隐私与通知订阅设置 (同步落盘服务端与本地存储)
|
||||
*/
|
||||
@@ -1210,12 +1226,9 @@ export async function updateUserSettings(userId, settings = {}) {
|
||||
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'
|
||||
};
|
||||
} catch (err) {
|
||||
// 写入失败必须向上抛出,避免调用方误以为已保存
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户