hookehuyr

refactor(offline-booking): 重构离线预约缓存轮询逻辑

- 移除未使用的 Vue 组合式 API 导入
- 拆分轮询控制逻辑为独立函数,提高可维护性
- 优化网络监听器注册流程,避免重复执行
- 使用引用计数管理轮询生命周期
- 添加详细注释说明核心逻辑
...@@ -74,6 +74,51 @@ ...@@ -74,6 +74,51 @@
74 pnpm build:weapp 74 pnpm build:weapp
75 ``` 75 ```
76 76
77 +## 离线预约缓存轮询说明
78 +
79 +### 目标
80 +
81 +在「已授权 + 网络可用」时,定时刷新离线预约记录缓存(本地 storage:`OFFLINE_BOOKING_DATA`),用于弱网/离线兜底页面展示。
82 +
83 +### 相关文件
84 +
85 +* 离线缓存刷新:`src/composables/useOfflineBookingCache.js`
86 + * 核心方法:`refresh_offline_booking_cache({ force })`
87 +* 轮询与网络监听:`src/composables/useOfflineBookingCachePolling.js`
88 + * 核心方法:`enable_offline_booking_cache_polling(options)` / `disable_offline_booking_cache_polling()`
89 +* 启动入口:`src/app.js`
90 + * 在授权成功后调用 `enable_offline_booking_cache_polling()`
91 +
92 +### 启动条件
93 +
94 +轮询启动需要同时满足:
95 +
96 +1. `ref_count > 0`:表示当前确实有人需要轮询(应用级启用会占用 1 个引用)
97 +2. `network_usable === true`:网络类型可用(由 `src/utils/network.js` 判定)
98 +
99 +### 运行流程
100 +
101 +1. `enable_offline_booking_cache_polling(options)`
102 + * 缓存最后一次 `options`(用于网络恢复时重启轮询)
103 + * 增加 `ref_count`
104 + * 注册 `Taro.onNetworkStatusChange`(仅注册一次)
105 + * 如果当前网络可用,则启动定时器,默认每 `60000ms` 刷新一次
106 +2. 定时器 tick:
107 + * 调用 `refresh_offline_booking_cache({ force: !!options?.force })`
108 + * 内部会再次校验授权与网络,避免无效请求
109 +3. 网络变更监听:
110 + * 变为弱网/无网:立即停止轮询定时器
111 + * 恢复为可用网络:若 `ref_count > 0`,用最后一次 `options` 重新启动轮询
112 +4. `disable_offline_booking_cache_polling()`
113 + * 释放一次 `ref_count`
114 + *`ref_count === 0` 时:停止轮询并注销网络监听器
115 +
116 +### options 说明
117 +
118 +* `interval_ms`:轮询间隔(毫秒),默认 `60000`
119 +* `immediate`:是否立即刷新一次,默认 `true`
120 +* `force`:透传给 `refresh_offline_booking_cache`,用于强制刷新(默认 `false`
121 +
77 ## 优化建议 (TODO) 122 ## 优化建议 (TODO)
78 123
79 * [x] 小程序授权流程有问题 - 已处理 124 * [x] 小程序授权流程有问题 - 已处理
......
1 -import { ref, onMounted, onUnmounted } from 'vue'
2 import Taro from '@tarojs/taro' 1 import Taro from '@tarojs/taro'
3 import { refresh_offline_booking_cache } from '@/composables/useOfflineBookingCache' 2 import { refresh_offline_booking_cache } from '@/composables/useOfflineBookingCache'
4 import { get_network_type, is_usable_network } from '@/utils/network' 3 import { get_network_type, is_usable_network } from '@/utils/network'
...@@ -31,9 +30,9 @@ const polling_state = { ...@@ -31,9 +30,9 @@ const polling_state = {
31 } 30 }
32 31
33 /** 32 /**
34 - * @description: 归一化选项 33 + * @description: 缓存最后一次 options(用于网络恢复时重启轮询)
35 * @param {Object} options 选项 34 * @param {Object} options 选项
36 - * @return {Object} 归一化后的选项 35 + * @return {Object} 最后一次选项
37 */ 36 */
38 37
39 const normalize_options = (options) => { 38 const normalize_options = (options) => {
...@@ -48,11 +47,13 @@ const normalize_options = (options) => { ...@@ -48,11 +47,13 @@ const normalize_options = (options) => {
48 */ 47 */
49 48
50 const run_refresh_once = async (options) => { 49 const run_refresh_once = async (options) => {
51 - if (polling_state.in_flight) return 50 + if (polling_state.in_flight) return // 核心防重复——如果正在刷新,直接返回
51 + // 标记为“正在刷新”
52 polling_state.in_flight = true 52 polling_state.in_flight = true
53 try { 53 try {
54 await refresh_offline_booking_cache({ force: !!options?.force }) 54 await refresh_offline_booking_cache({ force: !!options?.force })
55 } finally { 55 } finally {
56 + // 刷新完成后,标记为“刷新完成”
56 polling_state.in_flight = false 57 polling_state.in_flight = false
57 } 58 }
58 } 59 }
...@@ -79,48 +80,53 @@ const should_run_polling = () => { ...@@ -79,48 +80,53 @@ const should_run_polling = () => {
79 } 80 }
80 81
81 /** 82 /**
82 - * @description: 确保轮询已启动
83 - * @param {Object} options 选项
84 - */
85 -
86 -const ensure_polling_started = (options) => {
87 - start_offline_booking_cache_polling(options)
88 -}
89 -
90 -/**
91 * @description: 确保网络监听器已注册 83 * @description: 确保网络监听器已注册
84 + * @return {Promise} 网络监听器Promise
92 */ 85 */
93 86
94 const ensure_network_listener = async () => { 87 const ensure_network_listener = async () => {
88 + /**
89 + * 代码优先通过两个条件判断避免重复执行监听器逻辑
90 + * 1. 有已注册的监听器直接返回
91 + * 2. 有未完成的注册 Promise 则直接返回
92 + */
93 +
95 if (polling_state.has_network_listener) { 94 if (polling_state.has_network_listener) {
96 await update_network_usable() 95 await update_network_usable()
97 return 96 return
98 } 97 }
98 +
99 if (polling_state.network_listener_promise) { 99 if (polling_state.network_listener_promise) {
100 await polling_state.network_listener_promise 100 await polling_state.network_listener_promise
101 return 101 return
102 } 102 }
103 103
104 + // 立即执行异步的监听器注册流程(标记状态→更新网络可用性→定义回调→注册监听)
104 polling_state.network_listener_promise = (async () => { 105 polling_state.network_listener_promise = (async () => {
106 + // 标记已注册网络监听器
105 polling_state.has_network_listener = true 107 polling_state.has_network_listener = true
108 + // 初始化时更新网络可用性
106 await update_network_usable() 109 await update_network_usable()
107 110
111 + // 网络状态变化监听器, 网络状态变化时的处理逻辑,此时只是定义,不会立即执行
108 polling_state.network_listener = (res) => { 112 polling_state.network_listener = (res) => {
109 const is_connected = res?.isConnected !== false 113 const is_connected = res?.isConnected !== false
110 const type = res?.networkType || 'unknown' 114 const type = res?.networkType || 'unknown'
111 polling_state.network_usable = is_connected && is_usable_network(type) 115 polling_state.network_usable = is_connected && is_usable_network(type)
112 116
117 + // 网络不可用时,停止轮询
113 if (!polling_state.network_usable) { 118 if (!polling_state.network_usable) {
114 stop_offline_booking_cache_polling() 119 stop_offline_booking_cache_polling()
115 return 120 return
116 } 121 }
117 122
118 if (should_run_polling()) { 123 if (should_run_polling()) {
119 - ensure_polling_started(polling_state.last_options || {}) 124 + start_offline_booking_cache_polling(polling_state.last_options || {})
120 } 125 }
121 } 126 }
122 127
123 try { 128 try {
129 + // 注册网络状态变化监听器
124 Taro.onNetworkStatusChange(polling_state.network_listener) 130 Taro.onNetworkStatusChange(polling_state.network_listener)
125 } catch (e) { 131 } catch (e) {
126 polling_state.has_network_listener = false 132 polling_state.has_network_listener = false
...@@ -131,8 +137,10 @@ const ensure_network_listener = async () => { ...@@ -131,8 +137,10 @@ const ensure_network_listener = async () => {
131 })() 137 })()
132 138
133 try { 139 try {
140 + // 等待网络监听器初始化完成, Taro.onNetworkStatusChange
134 await polling_state.network_listener_promise 141 await polling_state.network_listener_promise
135 } finally { 142 } finally {
143 + // 等待注册流程完成后,强制清空 Promise 缓存(finally 块),保证下次执行逻辑时状态干净。
136 polling_state.network_listener_promise = null 144 polling_state.network_listener_promise = null
137 } 145 }
138 } 146 }
...@@ -161,20 +169,23 @@ const teardown_network_listener = () => { ...@@ -161,20 +169,23 @@ const teardown_network_listener = () => {
161 * @param {Object} options 选项 169 * @param {Object} options 选项
162 * @param {Number} options.interval_ms 轮询间隔,单位毫秒 170 * @param {Number} options.interval_ms 轮询间隔,单位毫秒
163 * @param {Boolean} options.immediate 是否立即刷新一次 171 * @param {Boolean} options.immediate 是否立即刷新一次
172 + * @param {Boolean} options.force 是否强制刷新(透传给 refresh_offline_booking_cache)
164 */ 173 */
165 174
166 -export const start_offline_booking_cache_polling = (options) => { 175 +const start_offline_booking_cache_polling = (options) => {
167 normalize_options(options) 176 normalize_options(options)
168 - if (!should_run_polling()) return 177 + if (!should_run_polling()) return // 不满足轮询条件直接返回
169 const interval_ms = Number(options?.interval_ms || 60000) 178 const interval_ms = Number(options?.interval_ms || 60000)
170 - if (polling_state.running) return 179 + if (polling_state.running) return // 核心防重复——如果已经在轮询,直接返回
171 180
172 - polling_state.running = true 181 + polling_state.running = true // 标记为“正在轮询”
173 182
183 + // 立即刷新一次,确保轮询开始时数据是最新的
174 if (options?.immediate !== false) { 184 if (options?.immediate !== false) {
175 run_refresh_once(options) 185 run_refresh_once(options)
176 } 186 }
177 187
188 + // 启动轮询定时器,按照指定间隔执行刷新操作
178 polling_state.timer_id = setInterval(() => { 189 polling_state.timer_id = setInterval(() => {
179 run_refresh_once(options) 190 run_refresh_once(options)
180 }, interval_ms) 191 }, interval_ms)
...@@ -184,7 +195,7 @@ export const start_offline_booking_cache_polling = (options) => { ...@@ -184,7 +195,7 @@ export const start_offline_booking_cache_polling = (options) => {
184 * @description: 停止离线预约缓存轮询 195 * @description: 停止离线预约缓存轮询
185 */ 196 */
186 197
187 -export const stop_offline_booking_cache_polling = () => { 198 +const stop_offline_booking_cache_polling = () => {
188 if (polling_state.timer_id) { 199 if (polling_state.timer_id) {
189 clearInterval(polling_state.timer_id) 200 clearInterval(polling_state.timer_id)
190 polling_state.timer_id = null 201 polling_state.timer_id = null
...@@ -192,70 +203,64 @@ export const stop_offline_booking_cache_polling = () => { ...@@ -192,70 +203,64 @@ export const stop_offline_booking_cache_polling = () => {
192 polling_state.running = false 203 polling_state.running = false
193 } 204 }
194 205
195 -export const enable_offline_booking_cache_polling = (options) => { 206 +/**
207 + * 引用计数的核心作用
208 + * 这两个函数实现了轮询功能的 “引用计数式资源管理”,本质是追踪有多少 “使用者 / 场景” 依赖这个轮询功能,从而决定是否启动 / 维持 / 停止轮询、注册 / 注销网络监听器,
209 + * 核心目的是:
210 + * - 避免轮询被重复启动、错误停止
211 + * - 防止无使用者时仍占用资源(定时器、网络监听器)
212 + * - 保证多场景共用轮询时的逻辑一致性
213 + */
214 +
215 +/**
216 + * @description: 增加轮询引用计数
217 + * 核心动作:将全局的 ref_count 加 1,代表 “又多了一个场景需要使用轮询功能”。
218 + * @param {Object} options 选项
219 + */
220 +
221 +const acquire_polling_ref = (options) => {
196 normalize_options(options) 222 normalize_options(options)
197 - if (polling_state.app_enabled) {
198 - ensure_network_listener().then(() => ensure_polling_started(polling_state.last_options || {}))
199 - return
200 - }
201 - polling_state.app_enabled = true
202 polling_state.ref_count += 1 223 polling_state.ref_count += 1
203 - ensure_network_listener().then(() => ensure_polling_started(polling_state.last_options || {})) 224 + // 增加引用计数后,确保网络监听器已注册并初始化完成,然后启动轮询
225 + ensure_network_listener().then(() => start_offline_booking_cache_polling(polling_state.last_options || {}))
204 } 226 }
205 227
206 -export const disable_offline_booking_cache_polling = () => { 228 +/**
207 - if (!polling_state.app_enabled) return 229 + * @description: 减少轮询引用计数
208 - polling_state.app_enabled = false 230 + * 核心动作:将 ref_count 减 1(且保证不会为负数),代表 “有一个场景不再需要轮询功能”。
231 + */
232 +
233 +const release_polling_ref = () => {
209 polling_state.ref_count = Math.max(0, polling_state.ref_count - 1) 234 polling_state.ref_count = Math.max(0, polling_state.ref_count - 1)
210 if (polling_state.ref_count === 0) { 235 if (polling_state.ref_count === 0) {
236 + // 引用计数降为0时,停止轮询并注销网络监听器
211 stop_offline_booking_cache_polling() 237 stop_offline_booking_cache_polling()
212 teardown_network_listener() 238 teardown_network_listener()
213 } 239 }
214 } 240 }
215 241
216 /** 242 /**
217 - * @description: 用于管理离线预约缓存轮询的组合式函数 243 + * @description: 启用离线预约缓存轮询
218 * @param {Object} options 选项 244 * @param {Object} options 选项
219 - * @param {Boolean} options.enabled 是否启用轮询
220 - * @param {Boolean} options.auto 是否自动启动轮询
221 - * @param {Number} options.interval_ms 轮询间隔,单位毫秒
222 - * @param {Boolean} options.immediate 是否立即刷新一次
223 */ 245 */
224 246
225 -export const use_offline_booking_cache_polling = (options) => { 247 +export const enable_offline_booking_cache_polling = (options) => {
226 - const is_running = ref(false) 248 + normalize_options(options)
227 - const enabled = options?.enabled !== false 249 + // 核心防重复——如果已经启用,直接返回
228 - 250 + if (polling_state.app_enabled) {
229 - const start = () => { 251 + ensure_network_listener().then(() => start_offline_booking_cache_polling(polling_state.last_options || {}))
230 - if (!enabled) return 252 + return
231 - polling_state.ref_count += 1
232 - ensure_network_listener().then(() => {
233 - ensure_polling_started(options)
234 - })
235 - is_running.value = true
236 - }
237 -
238 - const stop = () => {
239 - if (!is_running.value) return
240 - polling_state.ref_count = Math.max(0, polling_state.ref_count - 1)
241 - if (polling_state.ref_count === 0) {
242 - stop_offline_booking_cache_polling()
243 - teardown_network_listener()
244 - }
245 - is_running.value = false
246 } 253 }
254 + polling_state.app_enabled = true
255 + acquire_polling_ref(polling_state.last_options || {})
256 +}
247 257
248 - onMounted(() => { 258 +/**
249 - if (options?.auto !== false) start() 259 + * @description: 禁用离线预约缓存轮询
250 - }) 260 + */
251 -
252 - onUnmounted(() => {
253 - stop()
254 - })
255 261
256 - return { 262 +export const disable_offline_booking_cache_polling = () => {
257 - is_running, 263 + if (!polling_state.app_enabled) return
258 - start, 264 + polling_state.app_enabled = false
259 - stop, 265 + release_polling_ref()
260 - }
261 } 266 }
......