import { ENV, STORAGE_KEYS } from './env'; import { version as packageVersion } from '@/package.json'; const CACHE_KEY = STORAGE_KEYS.APP_CONFIG || 'app_config_cache'; const DEFAULT_TTL_SECONDS = 3600; let inFlight = null; let lastConfig = null; function buildUrl(base, path) { if (!base) return path; const normalizedBase = String(base).replace(/\/$/, ''); const normalizedPath = String(path || '').replace(/^\//, ''); return `${normalizedBase}/${normalizedPath}`; } function parseVersion(value) { if (!value) return [0]; return String(value) .replace(/^v/i, '') .split('.') .map((item) => { const num = Number.parseInt(item, 10); return Number.isNaN(num) ? 0 : num; }); } function compareVersion(a, b) { const left = parseVersion(a); const right = parseVersion(b); const maxLen = Math.max(left.length, right.length); for (let i = 0; i < maxLen; i += 1) { const l = left[i] || 0; const r = right[i] || 0; if (l > r) return 1; if (l < r) return -1; } return 0; } function getAppVersion() { let current = packageVersion || '0.0.0'; // #ifdef MP-WEIXIN try { const account = uni.getAccountInfoSync && uni.getAccountInfoSync(); const mpVersion = account && account.miniProgram && account.miniProgram.version; if (mpVersion) current = mpVersion; } catch (e) { // ignore } // #endif return current || '0.0.0'; } function getCache() { try { return uni.getStorageSync(CACHE_KEY); } catch (e) { return null; } } function setCache(config) { const ttlSeconds = Number(config && config.ttlSeconds) || DEFAULT_TTL_SECONDS; const now = Date.now(); const cache = { data: config.data, cachedAt: now, expireAt: now + ttlSeconds * 1000 }; uni.setStorageSync(CACHE_KEY, cache); return cache; } function isCacheValid(cache) { if (!cache || !cache.expireAt) return false; return Date.now() < cache.expireAt; } function applyConfig(config) { if (!config) return; if (config.api && config.api.baseUrl) { ENV.API_BASE_URL = config.api.baseUrl; } if (config.webview && config.webview.baseUrl) { ENV.WEBVIEW_BASE_URL = config.webview.baseUrl; } lastConfig = config; } function buildFailMessage(message) { return message || '配置获取失败,请退出小程序重新打开。'; } function showExitModal(message) { uni.showModal({ title: '提示', content: message, showCancel: false, confirmText: '退出', success: () => { // #ifdef MP-WEIXIN if (uni.exitMiniProgram) { uni.exitMiniProgram(); } // #endif // #ifdef APP-PLUS if (typeof plus !== 'undefined' && plus.runtime && plus.runtime.quit) { plus.runtime.quit(); } // #endif } }); } function validateConfig(config) { if (!config) { return { ok: false, message: buildFailMessage('配置无效,请退出小程序重新打开。') }; } if (config.disabled) { return { ok: false, message: config.disableReason || '服务暂不可用,请退出小程序重新打开。' }; } const currentVersion = getAppVersion(); const app = config.app || {}; const api = config.api || {}; const webview = config.webview || {}; if (app.minVersion && compareVersion(currentVersion, app.minVersion) < 0) { return { ok: false, message: '当前版本过低,请退出小程序重新打开。' }; } if (api.version && webview.version && api.version !== webview.version) { return { ok: false, message: '当前版本不一致,请退出小程序并重新打开以获取最新配置。' }; } if (api.minClientVersion && compareVersion(currentVersion, api.minClientVersion) < 0) { return { ok: false, message: '当前版本过低,请退出小程序重新打开。' }; } if (webview.minClientVersion && compareVersion(currentVersion, webview.minClientVersion) < 0) { return { ok: false, message: '当前版本过低,请退出小程序重新打开。' }; } return { ok: true, message: '' }; } function requestConfig() { const url = buildUrl(ENV.API_BASE_URL, '/api/open/app/config'); return new Promise((resolve, reject) => { uni.request({ url, method: 'GET', timeout: 8000, success: (res) => { if (res.statusCode >= 200 && res.statusCode < 300 && res.data) { resolve(res.data); return; } reject({ message: '配置获取失败', response: res }); }, fail: (err) => reject(err) }); }); } export async function initAppConfig(options = {}) { if (inFlight) return inFlight; const { force = false } = options; inFlight = (async () => { const cache = getCache(); if (!force && isCacheValid(cache)) { const validation = validateConfig(cache.data); if (!validation.ok) { showExitModal(validation.message); throw new Error(validation.message); } applyConfig(cache.data); return cache.data; } try { const config = await requestConfig(); const validation = validateConfig(config); if (!validation.ok) { showExitModal(validation.message); throw new Error(validation.message); } setCache(config); applyConfig(config); return config; } catch (error) { if (cache && isCacheValid(cache)) { const validation = validateConfig(cache.data); if (!validation.ok) { showExitModal(validation.message); throw new Error(validation.message); } applyConfig(cache.data); return cache.data; } showExitModal(buildFailMessage()); throw error; } })(); try { return await inFlight; } finally { inFlight = null; } } export async function ensureAppConfig() { const cache = getCache(); console.log('【业务代码定位】', cache, new Error().stack); if (cache && isCacheValid(cache)) { const validation = validateConfig(cache.data); if (!validation.ok) { showExitModal(validation.message); throw new Error(validation.message); } applyConfig(cache.data); return cache.data; } return initAppConfig({ force: true }); } export function getAppConfig() { return lastConfig || (getCache() ? getCache().data : null); } export function getWebviewBaseUrl() { const config = getAppConfig(); let webviewBaseUrl = (config && config.webview && config.webview.baseUrl) || ENV.WEBVIEW_BASE_URL || ''; console.log(webviewBaseUrl); return webviewBaseUrl; }