fix: 打手端每次进入均向后端校验手机号认证

This commit is contained in:
XingQue
2026-06-22 16:42:36 +08:00
parent 0767e23f11
commit e54846df02
7 changed files with 137 additions and 104 deletions

25
app.js
View File

@@ -180,6 +180,22 @@ App({
const savedRole = getPrimaryRole(this);
lockPrimaryRole(savedRole, this);
// ②b 进入各端前先问后端是否需要手机号认证
if (wx.getStorageSync('token')) {
try {
const needAuth = await check();
if (needAuth) {
if (savedRole === 'dashou') {
wx.setStorageSync('phone_auth_return_role', 'dashou');
} else if (savedRole === 'shangjia') {
wx.setStorageSync('phone_auth_return_role', 'shangjia');
}
wx.reLaunch({ url: '/pages/phone-auth/phone-auth' });
return;
}
} catch (e) {}
}
const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
if (savedRole !== 'normal') {
setTimeout(() => {
@@ -221,15 +237,6 @@ App({
.catch(err => {
console.warn('远程配置获取失败,继续使用本地缓存', err);
});
// ⑦ 手机号认证检查
try {
const needAuth = await check();
if (needAuth) {
wx.reLaunch({ url: '/pages/phone-auth/phone-auth' });
return;
}
} catch (e) {}
},
// 连接管理方法

View File

@@ -3,6 +3,7 @@ const app = getApp();
import request from '../../utils/request.js';
import PopupService from '../../services/popupService.js';
import { reconnectForRole } from '../../utils/role-tab-bar.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
Page({
data: {
@@ -58,7 +59,10 @@ Page({
}
},
onShow() {
async onShow() {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
if (!phoneOk) return;
this.registerNotificationComponent();
this.loadGlobalStatus();
if (wx.getStorageSync('uid')) {

View File

@@ -1,7 +1,7 @@
// pages/dashou-chongzhi/index.js
import request from '../../utils/request';
// 引入弹窗服务(路径根据实际调整)
import PopupService from '../../services/popupService.js';
import { ensurePhoneAuth } from '../../utils/phone-auth.js';
Page({
data: {
@@ -83,7 +83,10 @@ onHide() {
}
},
onShow() {
async onShow() {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
if (!phoneOk) return;
// 每次显示页面都刷新数据
this.registerNotificationComponent();
this.loadFromGlobalData();

View File

@@ -2,6 +2,7 @@
import { createPage, request, isRoleStatusActive, isCenterPageActive, syncRoleStatuses, syncProfileFields, syncGroupFields, ensureRoleOnCenterPage, clearCacheAndEnterNormal, lockPrimaryRole, migrateLegacyCenterRole, enterLockedRole, parseSceneOptions } from '../../utils/base-page.js'
import { resolveAvatarUrl } from '../../utils/avatar.js'
import { openPrivateChat, buildInviterPeerId } from '../../utils/im-user.js'
import { ensurePhoneAuth } from '../../utils/phone-auth.js'
const PLATFORM_INVITE_CODE = '0000008RffVgKHMj7kQC'
@@ -171,7 +172,10 @@ Page(createPage({
this.checkColdStartPopup('dashouduan');
},
onShow() {
async onShow() {
const phoneOk = await ensurePhoneAuth({ redirect: true, role: 'dashou' });
if (!phoneOk) return;
migrateLegacyCenterRole(getApp());
lockPrimaryRole('dashou', getApp());
wx.setStorageSync('isJinpai', 0);

View File

@@ -1,4 +1,4 @@
import { submit } from '../../utils/phone-auth';
import { submit, getPhoneAuthReturnPage } from '../../utils/phone-auth';
Page({
data: {
@@ -27,8 +27,7 @@ Page({
wx.showLoading({ title: '认证中...', mask: true });
await submit(phoneCode, avatarFile);
wx.hideLoading();
// 成功重启小程序此时check返回false用户无感进入
wx.reLaunch({ url: '/pages/index/index' });
wx.reLaunch({ url: getPhoneAuthReturnPage() });
} catch (e) {
wx.hideLoading();
wx.showToast({ title: e.message, icon: 'none' });

View File

@@ -1,57 +1,32 @@
/**
* 手机号强制认证 - 完全自包含逻辑模块
*
* 特点:
* 1. 不引用项目中的 request.js / upload.js避免其顶层 getApp() 带来的加载顺序错误
* 2. 所有请求直接使用 wx.request / wx.uploadFile基础 URL 写死(与 app.globalData.apiBaseUrl 保持一致)
* 3. check() 内部捕获所有异常,永远返回 false 或 true不会抛出错误
* 4. submit() 抛出错误由认证页面自己处理,不影响主流程
*
* 用法:
* app.js 中:
* try {
* const { check } = require('./utils/phone-auth');
* if (await check()) { wx.reLaunch(...); return; }
* } catch(e) {}
*
* 认证页面中:
* const { submit } = require('../../utils/phone-auth');
* await submit(phoneCode, avatarPath);
* check / ensurePhoneAuth 每次均请求后端 /peizhi/yhbdsjh 判断 need_auth
*/
// ==================== 基础配置 ====================
const API_BASE = 'https://www.abas.asia/hqhd'; // 与你 app.js 里的 apiBaseUrl 一致
const AUTH_CHECK_URL = '/peizhi/yhbdsjh'; // 检查是否需要认证
const AUTH_SUBMIT_URL = '/yonghu/xiugai'; // 提交认证(复用修改信息接口)
const API_BASE = 'https://www.abas.asia/hqhd';
const AUTH_CHECK_URL = '/peizhi/yhbdsjh';
const AUTH_SUBMIT_URL = '/yonghu/xiugai';
const AUTH_PAGE = '/pages/phone-auth/phone-auth';
const RETURN_ROLE_KEY = 'phone_auth_return_role';
// ==================== 内部封装wx.request ====================
function innerRequest(url, data = {}) {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync('token'); // 运行时获取 token不提前取
const header = {
'Content-Type': 'application/json'
};
if (token) {
header['Authorization'] = 'Bearer ' + token;
}
const token = wx.getStorageSync('token');
const header = { 'Content-Type': 'application/json' };
if (token) header['Authorization'] = 'Bearer ' + token;
wx.request({
url: API_BASE + url,
method: 'POST',
data,
header,
success: (res) => {
// 正常响应直接 resolve 整个响应对象,让上层自己解析
resolve(res);
},
fail: (err) => {
reject(err);
}
success: (res) => resolve(res),
fail: (err) => reject(err),
});
});
}
// ==================== 内部封装wx.uploadFile ====================
function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync('token');
@@ -60,7 +35,6 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
return;
}
// 先验证文件是否存在
wx.getFileInfo({
filePath,
success: () => {
@@ -69,15 +43,11 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
filePath,
name: fileName,
formData,
header: {
'Authorization': 'Bearer ' + token
},
header: { Authorization: 'Bearer ' + token },
success: (res) => {
// ✅ 修复wx.uploadFile 成功时 statusCode 为 200
if (res.statusCode === 200) {
try {
const data = JSON.parse(res.data);
resolve(data); // 直接返回解析后的对象,方便下游使用
resolve(JSON.parse(res.data));
} catch (e) {
reject(new Error('解析响应数据失败'));
}
@@ -88,24 +58,43 @@ function innerUpload(url, filePath, formData = {}, fileName = 'avatar') {
reject(new Error(`上传失败,状态码:${res.statusCode}`));
}
},
fail: (err) => {
reject(new Error('网络请求失败'));
}
fail: () => reject(new Error('网络请求失败')),
});
},
fail: () => {
reject(new Error('文件不存在或无法访问'));
}
fail: () => reject(new Error('文件不存在或无法访问')),
});
});
}
// ==================== 公开方法 ====================
function isOnPhoneAuthPage() {
const pages = getCurrentPages();
const cur = pages[pages.length - 1];
return !!(cur && cur.route === 'pages/phone-auth/phone-auth');
}
function parseCheckResponse(res) {
if (!res || res.statusCode === 401) {
return { ok: false, needAuth: false };
}
if (res.statusCode !== 200) {
return { ok: false, needAuth: false };
}
const body = res.data;
if (!body || typeof body !== 'object') {
return { ok: false, needAuth: false };
}
const code = Number(body.code);
if (code !== 0 && code !== 200) {
return { ok: false, needAuth: false };
}
const needAuth = body.need_auth === true
|| (body.data && body.data.need_auth === true);
return { ok: true, needAuth: !!needAuth };
}
/**
* 检查是否需要手机号认证
* - 无 token 或 任何异常 → 返回 false
* - 后端明确 need_auth: true → 返回 true
* 询问后端是否需要手机号认证
* @returns {Promise<boolean>} true=需要认证
*/
async function check() {
const token = wx.getStorageSync('token');
@@ -113,53 +102,68 @@ async function check() {
try {
const res = await innerRequest(AUTH_CHECK_URL, {});
// 假设后端返回格式:{ data: { need_auth: true/false } } 或直接在顶层
const body = res.data;
return body && body.need_auth === true;
const parsed = parseCheckResponse(res);
if (!parsed.ok) return false;
return parsed.needAuth;
} catch (e) {
// 任何错误(网络、后端异常)都吞掉,不影响主流程
return false;
}
}
function redirectToPhoneAuth(returnRole) {
if (returnRole) {
wx.setStorageSync(RETURN_ROLE_KEY, returnRole);
}
if (!isOnPhoneAuthPage()) {
wx.reLaunch({ url: AUTH_PAGE });
}
}
/**
* 提交认证(手机号 code + 头像文件)
* @param {string} phoneCode 微信 getPhoneNumber 返回的 code
* @param {string} avatarPath 头像临时路径
* @returns {Promise<object>} 后端返回的完整数据
* 打手端等场景:向后端确认,未认证则跳转认证页
* @returns {Promise<boolean>} true=已通过或无需认证false=已跳转认证页
*/
async function ensurePhoneAuth(options = {}) {
const { redirect = true, role = 'dashou' } = options;
const token = wx.getStorageSync('token');
if (!token) return true;
const needAuth = await check();
if (!needAuth) return true;
if (redirect) redirectToPhoneAuth(role);
return false;
}
function getPhoneAuthReturnPage() {
const role = wx.getStorageSync(RETURN_ROLE_KEY) || '';
wx.removeStorageSync(RETURN_ROLE_KEY);
if (role === 'dashou') return '/pages/accept-order/accept-order';
if (role === 'shangjia') return '/pages/merchant-orders/merchant-orders';
return '/pages/mine/mine';
}
async function submit(phoneCode, avatarPath) {
if (!phoneCode || !avatarPath) {
throw new Error('缺少参数');
}
// 将手机号 code 放入 formData
const formData = {
shoujihao_code: phoneCode
};
const data = await innerUpload(AUTH_SUBMIT_URL, avatarPath, {
shoujihao_code: phoneCode,
});
// 调用上传
const data = await innerUpload(AUTH_SUBMIT_URL, avatarPath, formData);
// ✅ 修复:正确判断后端返回的 code 字段,后端成功时 code === 0
if (data && data.code === 0) {
// data.data 包含 { touxiang: '相对路径', phone: '...' }
const respData = data.data || data;
// 存储手机号
if (respData.phone) {
wx.setStorageSync('phone', respData.phone);
}
// ✅ 修复:存储头像相对路径,与 xiugai 页面处理方式一致
if (respData.touxiang) {
wx.setStorageSync('touxiang', respData.touxiang);
}
if (respData.phone) wx.setStorageSync('phone', respData.phone);
if (respData.touxiang) wx.setStorageSync('touxiang', respData.touxiang);
return respData;
} else {
throw new Error((data && data.msg) || '认证失败');
}
throw new Error((data && data.msg) || '认证失败');
}
module.exports = { check, submit };
module.exports = {
check,
submit,
ensurePhoneAuth,
redirectToPhoneAuth,
getPhoneAuthReturnPage,
};

View File

@@ -1,5 +1,7 @@
/** 三端固定normal | dashou | shangjia认证后不可回退 */
import { ensurePhoneAuth } from './phone-auth.js';
const VALID = ['normal', 'dashou', 'shangjia'];
const LEGACY_CENTER_ROLES = ['guanshi', 'zuzhang', 'kaoheguan'];
@@ -87,8 +89,7 @@ export const PRIMARY_DEFAULT_PAGES = {
};
/** 锁定主端并跳转到该端默认首页,同时建立 IM 长连接 */
export function enterLockedRole(role, app) {
if (!VALID.includes(role) || role === 'normal') return false;
function finishEnterLockedRole(role, app) {
app = app || getApp();
lockPrimaryRole(role, app);
wx.reLaunch({ url: PRIMARY_DEFAULT_PAGES[role] });
@@ -103,3 +104,14 @@ export function enterLockedRole(role, app) {
}, 600);
return true;
}
export function enterLockedRole(role, app) {
if (!VALID.includes(role) || role === 'normal') return false;
if (role === 'dashou') {
ensurePhoneAuth({ redirect: true, role: 'dashou' }).then((ok) => {
if (ok) finishEnterLockedRole(role, app);
});
return true;
}
return finishEnterLockedRole(role, app);
}