Commit 47f57b61 by yuzhenWang

合并代码解决冲突

parents 22e6be98 fd04b50f
...@@ -558,7 +558,7 @@ ...@@ -558,7 +558,7 @@
queryAreaCenterInfo() { queryAreaCenterInfo() {
api.queryAreaCenterInfo({ api.queryAreaCenterInfo({
userId: uni.getStorageSync('cffp_userId'), // userId: uni.getStorageSync('cffp_userId'),
companyType: `${companyInfo.companyType}` companyType: `${companyInfo.companyType}`
}).then((res) => { }).then((res) => {
if (res['success']) { if (res['success']) {
......
...@@ -531,27 +531,28 @@ ...@@ -531,27 +531,28 @@
] ]
uni.removeTabBarBadge({ index: 3 }); uni.removeTabBarBadge({ index: 3 });
api.loginVerification({ // api.loginVerification({
"loginType": 1, // "loginType": 1,
}).then((res) => { // }).then((res) => {
if (res['success']) { // if (res['success']) {
this.userId = String(res['data']['userId']); // this.userId = String(res['data']['userId']);
uni.setStorageSync('isLogin', '1'); // uni.setStorageSync('isLogin', '1');
uni.setStorageSync('cffp_userId', this.userId); // uni.setStorageSync('cffp_userId', this.userId);
uni.setStorageSync('loginType', 'visitor'); // uni.setStorageSync('loginType', 'visitor');
uni.setStorageSync('uni-token', res.data['token']); // uni.setStorageSync('uni-token', res.data['token']);
// } else {
// uni.showToast({
// title: res['message'],
// duration: 2000,
// icon: 'none'
// })
// }
// })
this.courseList(); this.courseList();
this.getOneProduct() this.getOneProduct()
this.initShare(); this.initShare();
this.queryAreaCenterInfo() this.queryAreaCenterInfo()
} else {
uni.showToast({
title: res['message'],
duration: 2000,
icon: 'none'
})
}
})
} }
}, },
querySystemMessage() { querySystemMessage() {
...@@ -737,7 +738,7 @@ ...@@ -737,7 +738,7 @@
// 获取轮播图 // 获取轮播图
queryAreaCenterInfo() { queryAreaCenterInfo() {
api.queryAreaCenterInfo({ api.queryAreaCenterInfo({
userId: uni.getStorageSync('cffp_userId'), // userId: uni.getStorageSync('cffp_userId'),
companyType: `${companyInfo.companyType}` companyType: `${companyInfo.companyType}`
}).then((res) => { }).then((res) => {
if (res['success']) { if (res['success']) {
......
...@@ -66,6 +66,27 @@ function ensureWechatSDK(): Promise<void> { ...@@ -66,6 +66,27 @@ function ensureWechatSDK(): Promise<void> {
}); });
} }
// ===== 排查分享问题的调试开关 =====
// 改为 true 后 wx.config 会弹窗显示签名结果与每个 jsApi 权限,
// 签名不对会明确提示 invalid signature(URL 不一致)或 invalid domain(安全域名未配置)
const WX_SHARE_DEBUG = false
// ===== 签名 URL(分享能不能生效的关键) =====
// 本项目 H5 是 history 路由,页面跳转后 window.location.href 会变。
// iOS 微信校验签名用的是「首次进入页面那一刻的 URL」,Android 微信用「当前 URL」。
// 所以 iOS 必须用 main.js 存下的 firstEntryUrl(首次进入 URL),否则必定 invalid signature。
function getSignUrl(fallbackUrl: string): string {
const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent)
if (isIOS && typeof window !== 'undefined' && window.sessionStorage) {
const firstUrl = window.sessionStorage.getItem('firstEntryUrl')
if (firstUrl) {
console.log('[fiveshare] iOS 使用首次进入 URL 签名:', firstUrl);
return firstUrl
}
}
return fallbackUrl
}
// ===== 初始化微信 SDK ===== // ===== 初始化微信 SDK =====
export function initJssdkShare(callback: any, url: string) { export function initJssdkShare(callback: any, url: string) {
// 只在微信浏览器且非小程序环境中初始化 // 只在微信浏览器且非小程序环境中初始化
...@@ -89,14 +110,18 @@ export function initJssdkShare(callback: any, url: string) { ...@@ -89,14 +110,18 @@ export function initJssdkShare(callback: any, url: string) {
return; return;
} }
const rawUrl = url || window.location.href.split('#')[0];
const signUrl = getSignUrl(rawUrl);
console.log('[fiveshare] 当前 URL:', window.location.href.split('#')[0], '| 签名 URL:', signUrl);
const WxConfigRequestVO = { const WxConfigRequestVO = {
url: url || window.location.href.split('#')[0], url: signUrl,
systemType: uni.getStorageSync('addSystemType') || '1' systemType: uni.getStorageSync('addSystemType') || '1'
}; };
api.Wxshare(WxConfigRequestVO).then(res => { api.Wxshare(WxConfigRequestVO).then(res => {
wx.config({ wx.config({
debug: false, debug: WX_SHARE_DEBUG,
appId: res.data.appId, appId: res.data.appId,
timestamp: res.data.timestamp, timestamp: res.data.timestamp,
nonceStr: res.data.nonceStr, nonceStr: res.data.nonceStr,
......
...@@ -113,6 +113,145 @@ const whiteApiList = [`${apiURL}/authorize/obtainToken`, ...@@ -113,6 +113,145 @@ const whiteApiList = [`${apiURL}/authorize/obtainToken`,
`${cffpURL}/partner/queryById`, `${cffpURL}/partner/queryById`,
`${apiURL}/sso/ticket/consume`, // Casdoor 票据消费:匿名,唯一凭据是 ticket `${apiURL}/sso/ticket/consume`, // Casdoor 票据消费:匿名,唯一凭据是 ticket
]; ];
// ==================== token 获取 & 整页刷新 控制 ====================
// obtainToken + 整页刷新是兼容 iOS 微信分享的既有方案(先让 token 就位,再渲染页面)。
// 但首页接口(如 queryAreaCenterInfo)不在白名单又需要 token,会一运行就无限刷新,这里补 4 道保护:
// 1. 单飞:同一时刻只发一个 obtainToken,避免并发请求各自刷新
// 2. 校验:没拿到有效 token 一律不写、不刷新(防写入 undefined 后反复刷新)
// 3. 上限:时间窗内最多刷新 MAX_RELOAD 次,超出只补 token、不再刷新
// 4. 不覆盖:页面自己(loginVerification)已拿到 token 时不再写入、不再刷新
const TOKEN_KEY = 'uni-token'
const RELOAD_COUNT_KEY = '__cffp_token_reload_count'
const RELOAD_TS_KEY = '__cffp_token_reload_ts'
const MAX_RELOAD = 3
const MIN_RELOAD_INTERVAL = 800
const RELOAD_RESET_WINDOW = 30000
let obtaining = false
let t001Locked = false
const hasWindow = typeof window !== 'undefined'
const canUseSession = hasWindow && !!window.sessionStorage
// 读取有效 token(兼容历史脏数据 'undefined' / 'null')
const readToken = () => {
const token = uni.getStorageSync(TOKEN_KEY)
if (!token || token === 'undefined' || token === 'null') return ''
return token
}
const isWhiteApi = (url = '') => whiteApiList.some(item => url === item || url.indexOf(item) === 0)
const readReloadState = () => {
let count = 0
let ts = 0
if (canUseSession) {
count = Number(window.sessionStorage.getItem(RELOAD_COUNT_KEY) || 0)
ts = Number(window.sessionStorage.getItem(RELOAD_TS_KEY) || 0)
}
// 超过窗口期没再刷新,说明循环已断开,计数归零
if (!ts || Date.now() - ts > RELOAD_RESET_WINDOW) count = 0
return { count, ts }
}
const canReload = () => {
if (!hasWindow) return false
const { count, ts } = readReloadState()
if (count >= MAX_RELOAD) return false
if (ts && Date.now() - ts < MIN_RELOAD_INTERVAL) return false
return true
}
// 去掉 URL 上一次的 t_reload,避免反复刷新时参数无限堆积
const stripReloadParam = (url) => {
if (url.indexOf('t_reload=') === -1) return url
const index = url.indexOf('?')
if (index === -1) return url
const path = url.slice(0, index)
const kept = url.slice(index + 1).split('&').filter(kv => kv && kv.indexOf('t_reload=') !== 0)
return kept.length ? `${path}?${kept.join('&')}` : path
}
// 整页刷新:参数必须放在 hash 之前,只有 URL 主体变化浏览器才会重新加载文档
// (hash 内加参数不会触发整页刷新,这也是原写法在带参页面上刷新不生效的原因)
const reloadPage = () => {
if (!hasWindow) return
const { count } = readReloadState()
if (canUseSession) {
window.sessionStorage.setItem(RELOAD_COUNT_KEY, String(count + 1))
window.sessionStorage.setItem(RELOAD_TS_KEY, String(Date.now()))
}
const href = window.location.href
const hashIndex = href.indexOf('#')
let base = stripReloadParam(hashIndex > -1 ? href.slice(0, hashIndex) : href)
const hash = hashIndex > -1 ? href.slice(hashIndex) : ''
base += (base.indexOf('?') > -1 ? '&' : '?') + 't_reload=' + Date.now()
// 用 replace 而非直接赋值 href,避免浏览器历史里堆一长串刷新记录
window.location.replace(base + hash)
}
const obtainTokenOnce = () => {
if (obtaining) return
obtaining = true
const params: any = {
ticket: 'uni-app',
loginId: null
}
const h5userId = uni.getStorageSync('cffp_userId')
if (h5userId) params.loginId = h5userId
uni.request({
url: `${apiURL}/authorize/obtainToken`,
method: 'POST',
data: params,
success: (res: any) => {
const body = res && res.data
const token = body && body.data && body.data.token ? body.data.token : (body && body.token)
if (res.statusCode !== 200 || !token) {
console.warn('[interceptor] obtainToken 未拿到有效 token,跳过写入与刷新', res)
return
}
// 这里必须无条件刷新一次:本次是为"已经裸奔发出的无 token 请求"补的 token,
// 不重载的话那个请求会带着空 X-Authorization 一直失败,看起来就是"有 token 但请求没带"
const existed = readToken()
if (!existed) uni.setStorageSync(TOKEN_KEY, token)
if (canReload()) {
reloadPage()
} else {
console.warn('[interceptor] token 已补齐,但刷新次数已达上限,不再整页刷新')
}
},
fail: (err: any) => {
console.warn('[interceptor] obtainToken 请求失败,跳过刷新', err)
},
complete: () => {
obtaining = false
}
})
}
// T001 = token 失效 / 未登录
const handleTokenInvalid = () => {
if (t001Locked) return
const loginType = uni.getStorageSync('loginType')
// 访客态 token 由 obtainToken 自动续期,这里清 token 会和请求拦截器互相触发,形成刷新死循环
if (loginType === 'visitor') {
console.warn('[interceptor] 访客态接口返回 T001,跳过清理(避免触发整页刷新循环)')
return
}
t001Locked = true
uni.removeStorageSync('isLogin')
uni.removeStorageSync(TOKEN_KEY)
uni.setStorageSync('loginType', 'visitor')
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
const current = pages.length ? (pages[pages.length - 1] as any).route : ''
if (current !== 'pages/index/index') {
uni.switchTab({ url: '/pages/index/index' })
}
// 短时间内多个请求同时返回 T001,只处理一次
setTimeout(() => { t001Locked = false }, 500)
}
export const interceptor = () => { export const interceptor = () => {
uni.addInterceptor('request', { uni.addInterceptor('request', {
// 请求拦截器 // 请求拦截器
...@@ -147,35 +286,20 @@ export const interceptor = () => { ...@@ -147,35 +286,20 @@ export const interceptor = () => {
return return
} }
// 当本地没有token,并且接口地址没在白名单内,需要重新获取token // 当本地没有token,并且接口地址没在白名单内,需要重新获取token
if (!uni.getStorageSync('uni-token') && !whiteApiList.includes(args.url)) { // 注意:并发请求只会触发一次(单飞),且刷新有次数上限,不会无限刷新
if (!readToken() && !isWhiteApi(args.url)) {
const params = { // 只有确定会整页重载时才取消本请求,避免"带着空 token 裸奔"打到后端被 T001
ticket: 'uni-app', const willReload = hasWindow && canReload()
loginId: null obtainTokenOnce()
if (willReload) {
// 取消本次请求,等整页重载后由页面用已就位的 token 重新发起
return false
} }
let h5userId = uni.getStorageSync('cffp_userId');
if (h5userId) {
params.loginId = h5userId;
}
uni.request({
url: `${apiURL}/authorize/obtainToken`,
method: 'POST',
data: params,
success: (res) => {
if (res.statusCode === 200) {
uni.setStorageSync('uni-token', res.data['data']['token']);
let isHas = window.location.href.indexOf('?')==-1?'?':'&';
window.location.href = window.location.href + isHas + 't_reload=' + new Date().getTime();
}
}
})
} }
//设置请求头及token //设置请求头及token
args.header = { args.header = {
'content-type': args.method === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded', 'content-type': args.method === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded',
'X-Authorization': uni.getStorageSync('uni-token') ? uni.getStorageSync('uni-token') : '', 'X-Authorization': readToken(),
// 'Access-Control-Allow-Headers': 'appId', // 'Access-Control-Allow-Headers': 'appId',
// 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', // 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
// 'Access-Control-Max-Age': 86400, // 'Access-Control-Max-Age': 86400,
...@@ -191,18 +315,15 @@ export const interceptor = () => { ...@@ -191,18 +315,15 @@ export const interceptor = () => {
const unauthorized = sso.isSsoMode() const unauthorized = sso.isSsoMode()
? args.statusCode === 401 ? args.statusCode === 401
: bodyT001 : bodyT001
if(unauthorized){ if (unauthorized) {
if (sso.isSsoMode()) { if (sso.isSsoMode()) {
// 不回 H5 首页/登录页:销毁 web-view,原生强制重走 wx.login→票据 // 不回 H5 首页/登录页:销毁 web-view,原生强制重走 wx.login→票据
sso.reloginViaMiniprogram() sso.reloginViaMiniprogram()
return return
} }
uni.removeStorageSync('isLogin'); // 非 sso 模式统一走 handleTokenInvalid:访客态跳过清理、短时间内并发 T001 只处理一次,
uni.removeStorageSync('uni-token'); // 避免与请求拦截器的 obtainToken + 整页刷新互相触发形成刷新死循环
uni.setStorageSync('loginType','visitor') handleTokenInvalid()
uni.switchTab({
url:'/pages/index/index'
})
} }
}, },
fail() { fail() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment