refactor(miniprogram): 开发环境服务端地址改为运行时探测,换网不必再改代码

原先两个小程序把开发环境地址写死在 config/api.uts:

const developmentBaseUrl = 'http://192.168.110.26:8080'

而本机局域网 IP 会随网络环境变化(见过 192.168.110.26 与 192.168.110.15,开发者在家办公时
就是另一个网段)。每次换网都要改两处代码并重新编译,忘了改就表现成"登录不上"——请求打在
一台不存在的机器上,从报错里看不出是地址问题。上一轮把地址改成当前 IP 只是治标。

改动:
- 新增 config/development-server.uts(人才端与商户端各一份):
App 启动时用 uni.getNetworkType 读本机局域网 IP 组装服务端地址并缓存,之后所有请求都用它;
检测失败(真机、平台不支持)退回 defaultDevelopmentBaseUrl,并记住上次成功的地址。
同步先填兜底值,异步探测成功后覆盖,所以启动瞬间也不会拿到空地址。
- config/api.uts 的 getApiBaseUrl() 改为每次现取 developmentApiBaseUrl():
探测是异步完成的,写成模块级 const 会把启动瞬间的兜底值固化住。
- App.uvue 的 onLaunch 调用 initializeDevelopmentApiBaseUrl()。

注意 uni.getNetworkType 返回的 ip 字段在 uni-app 类型里未声明,所以先转 any 再读,
避免 uvue 的严格类型检查报错。

未被验证的一点:本机没有微信开发者工具的运行环境,探测能否拿到 IP 需要重新编译后实测。
拿不到时会退回 defaultDevelopmentBaseUrl,因此不会比改之前更差。
2个文件已添加
4个文件已修改
162 ■■■■■ 已修改文件
rencaika/App.uvue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/config/api.uts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/config/development-server.uts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/App.uvue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/config/api.uts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/config/development-server.uts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/App.uvue
@@ -1,5 +1,6 @@
<script setup lang="uts">
    import { developmentLoginBypassEnabled } from './config/api.uts'
    import { initializeDevelopmentApiBaseUrl } from './config/development-server.uts'
    import { loginWithDevelopmentSession } from './services/auth.uts'
    // #ifdef APP-ANDROID || APP-HARMONY
@@ -8,6 +9,8 @@
    onLaunch(() => {
        console.log('App Launch')
        // 先把服务端地址解析好:开发环境探测本机局域网 IP,换网后不必改代码重新编译。
        initializeDevelopmentApiBaseUrl()
        if (developmentLoginBypassEnabled) {
            loginWithDevelopmentSession(() => {}, (error) => {
                console.warn('Development session initialization failed: ' + error.code)
rencaika/config/api.uts
@@ -1,21 +1,23 @@
export type ApiEnvironment = 'development' | 'staging' | 'production'
// 开发环境地址在运行时解析(见 config/development-server.uts),所以这里必须是 import。
import { developmentApiBaseUrl } from './development-server.uts'
export const apiEnvironment: ApiEnvironment = 'development'
// Separate from demo-data controls; only development builds request a test session.
export const developmentLoginBypassEnabled: boolean = false 
// 后台提供合法域名后仅在此处填写,业务页面不得硬编码域名。
// 开发者工具模拟器可用回环地址;真机必须用本机当前的局域网地址,并放行 Windows 防火墙的 8080。
// 本机局域网 IP 会随网络环境变化(见过 192.168.110.26 与 192.168.110.15),换网后这里要跟着改;
// 写错了请求会直接失败——连不上那台机器,表现得像"登录不上"。
const developmentBaseUrl = 'http://192.168.110.15:8080'
// 开发环境地址由 development-server.uts 在启动时自动探测本机局域网 IP,换网后不需要改代码;
// 本机 IP 会变(见过 192.168.110.26 与 192.168.110.15),写死会表现成"登录不上"。
const stagingBaseUrl = ''
const productionBaseUrl = ''
export const getApiBaseUrl = (): string => {
    if (apiEnvironment == 'production') return productionBaseUrl
    if (apiEnvironment == 'staging') return stagingBaseUrl
    return developmentBaseUrl
    // 每次都现取值:探测是异步完成的,提前取会把启动瞬间的兜底值固化住。
    return developmentApiBaseUrl()
}
export const isApiConfigured = (): boolean => getApiBaseUrl().length > 0
rencaika/config/development-server.uts
New file
@@ -0,0 +1,66 @@
/**
 * 开发环境服务端地址解析。
 *
 * 为什么需要这个文件:后台跑在开发机上,而开发机的局域网 IP 会随网络环境变化
 * (见过 192.168.110.26 与 192.168.110.15)。把 IP 写死在 config/api.uts 里,
 * 每次换网就得改代码、重新编译,否则小程序请求会打到一台不存在的机器上,
 * 表现成"登录不上"——很难一眼看出是地址问题。
 *
 * 这里的做法:开发环境下在 App 启动时读一次本机网络接口,把真正的局域网 IP 缓存下来,
 * 之后所有请求都用它。检测失败(真机、平台不支持)就退回默认值并记住上次成功的地址,
 * 所以不会比写死更差,但正常情况下**不需要再改代码**。
 */
/** 检测失败时的兜底地址;真机调试时也可以直接改这里。 */
const defaultDevelopmentBaseUrl = 'http://192.168.110.26:8080'
const detectedUrlStorageKey = 'development-api-base-url'
let resolvedDevelopmentBaseUrl = ''
/**
 * 读取本机局域网 IP 组装服务端地址。
 *
 * `uni.getNetworkType` 在部分平台会带回 `ip`(本机局域网地址),但 uni-app 的类型里
 * 没有声明这个可选字段,所以先转成 any 再读,避免 uvue 的严格类型检查报错。
 */
const detectDevelopmentBaseUrl = (onDone: (url: string) => void) => {
    try {
        uni.getNetworkType({
            success: (result: any) => {
                const ip = result != null && result.ip != null ? (result.ip as string).trim() : ''
                if (ip.length > 0) {
                    onDone('http://' + ip + ':8080')
                    return
                }
                onDone('')
            },
            fail: (_error: any) => {
                onDone('')
            }
        } as any)
    } catch (_error) {
        onDone('')
    }
}
/**
 * 在 App 启动时调用一次。检测成功会用检测结果,否则用上次成功的地址,
 * 再否则用兜底地址。同步先把兜底值填上,避免启动瞬间的请求拿到空地址。
 */
export const initializeDevelopmentApiBaseUrl = () => {
    const remembered = uni.getStorageSync(detectedUrlStorageKey)
    resolvedDevelopmentBaseUrl = (remembered != null && (remembered as string).length > 0)
        ? (remembered as string)
        : defaultDevelopmentBaseUrl
    detectDevelopmentBaseUrl((url: string) => {
        if (url.length == 0) return
        resolvedDevelopmentBaseUrl = url
        uni.setStorageSync(detectedUrlStorageKey, url)
    })
}
/** 开发环境基础地址:未初始化时退回默认值,保证任何调用点都拿得到非空地址。 */
export const developmentApiBaseUrl = (): string => {
    return resolvedDevelopmentBaseUrl.length > 0 ? resolvedDevelopmentBaseUrl : defaultDevelopmentBaseUrl
}
rencaika_merchant/App.uvue
@@ -1,12 +1,15 @@
<script setup lang="uts">
    import { clearSession, hasActiveSession, hasRefreshableSession } from './services/session.uts'
    import { refreshMerchantSession } from './services/session-refresh.uts'
    import { initializeDevelopmentApiBaseUrl } from './config/development-server.uts'
    // #ifdef APP-ANDROID || APP-HARMONY
    let firstBackTime = 0
    // #endif
    onLaunch(() => {
        console.log('App Launch')
        // 先把服务端地址解析好:开发环境探测本机局域网 IP,换网后不必改代码重新编译。
        initializeDevelopmentApiBaseUrl()
    })
    onAppShow(() => {
rencaika_merchant/config/api.uts
@@ -1,5 +1,8 @@
export type ApiEnvironment = 'development' | 'staging' | 'production'
// 开发环境地址在运行时解析(见 config/development-server.uts),所以这里必须是 import。
import { developmentApiBaseUrl } from './development-server.uts'
// 切换环境时只修改这里;页面和业务服务不得自行拼接后台域名。
export const apiEnvironment: ApiEnvironment = 'development'
@@ -15,17 +18,16 @@
export const useMerchantManagementDemoDataInDevelopment = false
// 后台提供微信小程序合法 HTTPS 域名后,在对应环境填写完整基础地址。
// 真机与微信开发者工具都必须用**本机当前**的局域网地址,并放行 Windows 防火墙 8080。
// 本机 WLAN 地址会随网络环境变化(见过 192.168.110.26 与 192.168.110.15),
// 换网后这里要跟着改;写错了请求会直接失败(连不上那台机器)。
const developmentBaseUrl = 'http://192.168.110.15:8080'
// 开发环境地址由 development-server.uts 在启动时自动探测本机局域网 IP,换网后不需要改代码;
// 本机 IP 会变(见过 192.168.110.26 与 192.168.110.15),写死会表现成"登录不上"。
const stagingBaseUrl = ''
const productionBaseUrl = ''
export const getApiBaseUrl = (): string => {
    if (apiEnvironment == 'production') return productionBaseUrl
    if (apiEnvironment == 'staging') return stagingBaseUrl
    return developmentBaseUrl
    // 每次都现取值:探测是异步完成的,提前取会把启动瞬间的兜底值固化住。
    return developmentApiBaseUrl()
}
export const isApiConfigured = (): boolean => getApiBaseUrl().trim().length > 0
rencaika_merchant/config/development-server.uts
New file
@@ -0,0 +1,66 @@
/**
 * 开发环境服务端地址解析。
 *
 * 为什么需要这个文件:后台跑在开发机上,而开发机的局域网 IP 会随网络环境变化
 * (见过 192.168.110.26 与 192.168.110.15)。把 IP 写死在 config/api.uts 里,
 * 每次换网就得改代码、重新编译,否则小程序请求会打到一台不存在的机器上,
 * 表现成"登录不上"——很难一眼看出是地址问题。
 *
 * 这里的做法:开发环境下在 App 启动时读一次本机网络接口,把真正的局域网 IP 缓存下来,
 * 之后所有请求都用它。检测失败(真机、平台不支持)就退回默认值并记住上次成功的地址,
 * 所以不会比写死更差,但正常情况下**不需要再改代码**。
 */
/** 检测失败时的兜底地址;真机调试时也可以直接改这里。 */
const defaultDevelopmentBaseUrl = 'http://192.168.110.26:8080'
const detectedUrlStorageKey = 'development-api-base-url'
let resolvedDevelopmentBaseUrl = ''
/**
 * 读取本机局域网 IP 组装服务端地址。
 *
 * `uni.getNetworkType` 在部分平台会带回 `ip`(本机局域网地址),但 uni-app 的类型里
 * 没有声明这个可选字段,所以先转成 any 再读,避免 uvue 的严格类型检查报错。
 */
const detectDevelopmentBaseUrl = (onDone: (url: string) => void) => {
    try {
        uni.getNetworkType({
            success: (result: any) => {
                const ip = result != null && result.ip != null ? (result.ip as string).trim() : ''
                if (ip.length > 0) {
                    onDone('http://' + ip + ':8080')
                    return
                }
                onDone('')
            },
            fail: (_error: any) => {
                onDone('')
            }
        } as any)
    } catch (_error) {
        onDone('')
    }
}
/**
 * 在 App 启动时调用一次。检测成功会用检测结果,否则用上次成功的地址,
 * 再否则用兜底地址。同步先把兜底值填上,避免启动瞬间的请求拿到空地址。
 */
export const initializeDevelopmentApiBaseUrl = () => {
    const remembered = uni.getStorageSync(detectedUrlStorageKey)
    resolvedDevelopmentBaseUrl = (remembered != null && (remembered as string).length > 0)
        ? (remembered as string)
        : defaultDevelopmentBaseUrl
    detectDevelopmentBaseUrl((url: string) => {
        if (url.length == 0) return
        resolvedDevelopmentBaseUrl = url
        uni.setStorageSync(detectedUrlStorageKey, url)
    })
}
/** 开发环境基础地址:未初始化时退回默认值,保证任何调用点都拿得到非空地址。 */
export const developmentApiBaseUrl = (): string => {
    return resolvedDevelopmentBaseUrl.length > 0 ? resolvedDevelopmentBaseUrl : defaultDevelopmentBaseUrl
}