diff --git a/gvsds/apps/gerp/apps/subscription/manager.py b/gvsds/apps/gerp/apps/subscription/manager.py index 383d6076..25332995 100644 --- a/gvsds/apps/gerp/apps/subscription/manager.py +++ b/gvsds/apps/gerp/apps/subscription/manager.py @@ -589,12 +589,20 @@ class SubscriptionManager: # /venom/remote-entry.js),若不主动清理,主服务在 TTL 内会一直回旧内容; # 清理后下一次请求立即回源,配合 SHA256 只换来真正变化的文件。 purged = SubscriptionManager.PurgeRemoteAssets(group.SubscriptionGroupUUID) + # 立即回源预热:让新内容及其 SHA256 版本号就绪(ProxyEntryURL 会带 ?v=), + # 避免首个访问者才触发拉取、也保证版本号从第一刻起就是新的。 + entry_path = urlparse(entry).path.lstrip('/') or 'index.js' + _payload, warm_err = SubscriptionManager.FetchRemoteAsset( + _ToStr(group.SubscriptionGroupUUID), entry_path) + if warm_err: + logger.warning('声明后预热远端资源失败: %s (%s)', entry_path, warm_err) return { 'GroupUUID': _ToStr(group.SubscriptionGroupUUID), 'GroupName': group.GroupName, 'RemoteEntry': group.RemoteEntry, 'RemoteModule': group.RemoteModule, 'PurgedAssets': purged, + 'WarmError': warm_err or '', }, None # ==================== 远端资源代理与缓存(主服务代子服务器出资源) ==================== @@ -617,12 +625,24 @@ class SubscriptionManager: 相对/同源入口(非绝对 http(s))原样返回,无需代理。 供应商声明的绝对入口 → `/API/Subscription/RemoteAsset//`, 浏览器只访问主服务,不再直连提供者子服务器。 + + **URL 带内容版本**(`?v=`):内容一变 URL 就变,浏览器与中间 + 缓存无法再用旧副本,提供者重建后无需强制刷新/等 TTL 即生效。缓存尚无内容 + (首次)时不带版本,下一次调用即带上。 """ entry = str(getattr(group, 'RemoteEntry', '') or '').strip() if SubscriptionManager.EntryOrigin(entry) is None: return entry asset_path = urlparse(entry).path.lstrip('/') or 'index.js' - return f'{REMOTE_ASSET_URL_PREFIX}/{_ToStr(group.SubscriptionGroupUUID)}/{asset_path}' + group_uuid = _ToStr(group.SubscriptionGroupUUID) + url = f'{REMOTE_ASSET_URL_PREFIX}/{group_uuid}/{asset_path}' + version = SubscriptionRemoteAsset.objects.filter( + SubscriptionGroupUUID=_ToBytes(group.SubscriptionGroupUUID), + AssetPath=asset_path, + ).values_list('SHA256', flat=True).first() + if version: + url = f'{url}?v={version[:16]}' + return url @staticmethod def ProviderOrigin(GroupUUID):