export type ApiEnvironment = 'local' | 'staging' | 'production'; type MiniProgramVersion = 'develop' | 'trial' | 'release'; const LOCAL_BASE_URL = 'http://127.0.0.1:8800/api/v1'; const STORAGE_KEY = 'doodle_develop_api_environment_v1'; let selectedEnvironment: ApiEnvironment | undefined; let revision = 0; /** 暂时共用本地地址;部署后分别替换 staging/production,真机 local 使用电脑局域网 IP。 */ export const apiConfig = { developmentEnvironment: 'local' as ApiEnvironment, localBaseUrl: LOCAL_BASE_URL, stagingBaseUrl: LOCAL_BASE_URL, productionBaseUrl: LOCAL_BASE_URL, timeout: 10000, }; export const API_ENVIRONMENTS: ApiEnvironment[] = [ 'local', 'staging', 'production', ]; export function isApiEnvironment(value: unknown): value is ApiEnvironment { return API_ENVIRONMENTS.includes(value as ApiEnvironment); } /** 仅开发包读取本机选择;体验、正式及未知包始终连接 production。 */ export function getApiEnvironment( version: string = wx.getAccountInfoSync().miniProgram.envVersion, ): ApiEnvironment { if (version === 'develop' && selectedEnvironment === undefined) { try { const stored: unknown = wx.getStorageSync(STORAGE_KEY); if (isApiEnvironment(stored)) selectedEnvironment = stored; } catch { /* 无缓存时使用开发默认值。 */ } } return version === 'develop' ? (selectedEnvironment ?? apiConfig.developmentEnvironment) : 'production'; } export function resolveApiConfig(version: MiniProgramVersion | string) { return resolveEnvironment(getApiEnvironment(version), version); } function resolveEnvironment(environment: ApiEnvironment, version: string) { const baseUrl = apiConfig[`${environment}BaseUrl`] .trim() .replace(/\/+$/, ''); if (!baseUrl) throw new Error(`请先配置 ${environment} 接口地址`); // 用户指定的本机占位地址暂允许 HTTP;实际云端入口仍须使用 HTTPS。 if ( !/^https?:\/\/[^/?#\s]+\/api\/v1$/.test(baseUrl) || (environment !== 'local' && !baseUrl.startsWith('https://') && baseUrl !== LOCAL_BASE_URL) ) throw new Error('接口地址配置不正确'); return { environment, baseUrl, timeout: apiConfig.timeout, revision, version, }; } export function getApiConfig() { const { appId = '', envVersion } = wx.getAccountInfoSync().miniProgram; return { ...resolveApiConfig(envVersion), appId }; } /** 即使三个地址相同也按环境隔离;develop 不与体验/正式包共享业务缓存。 */ export function apiContextKey(config = getApiConfig()): string { const scope = config.version === 'develop' ? 'develop' : 'public'; return [config.appId, scope, config.environment, config.baseUrl] .map(encodeURIComponent) .join(':'); } /** 版本号阻止 A→B→A 后旧 A 请求覆盖当前页面。 */ export function isCurrentApiConfig(config: ReturnType) { const current = getApiConfig(); return ( config.revision === current.revision && apiContextKey(config) === apiContextKey(current) ); } /** 写入成功后才提交内存选择;配置失败保留原环境,不自动回退。 */ export function selectApiEnvironment(environment: ApiEnvironment): boolean { if (wx.getAccountInfoSync().miniProgram.envVersion !== 'develop') throw new Error('仅开发版可切换环境'); if (!isApiEnvironment(environment)) throw new Error('未知接口环境'); resolveEnvironment(environment, 'develop'); if (getApiEnvironment() === environment) return false; wx.setStorageSync(STORAGE_KEY, environment); selectedEnvironment = environment; revision++; return true; } export function getApiEnvironmentOptions() { return API_ENVIRONMENTS.map((environment) => { try { return { environment, baseUrl: resolveEnvironment(environment, 'develop').baseUrl, available: true, }; } catch { return { environment, baseUrl: '尚未配置有效地址', available: false, }; } }); }