aphla 测试 1
Some checks failed
Plugin Library CI / publish (00.BuildOriginality) (push) Successful in 4s
Plugin Library CI / publish (00.StaryEvo) (push) Successful in 4s
Plugin Library CI / publish (00.StaryEvoTools) (push) Failing after 10s
Plugin Library CI / publish (01.HybridCLR) (push) Successful in 4s
Plugin Library CI / publish (02.InformationSave) (push) Successful in 3s
Plugin Library CI / publish (03.YooAsset) (push) Successful in 32s
Plugin Library CI / publish (04.AudioCore) (push) Successful in 2s
Plugin Library CI / publish (05.TableTextConversion) (push) Successful in 3s
Plugin Library CI / publish (06.UIFarme) (push) Successful in 16s
Plugin Library CI / publish (07.RKTools) (push) Successful in 3s
Plugin Library CI / publish (08.UniTask) (push) Successful in 3s
Plugin Library CI / publish (09.CodeChecker) (push) Successful in 16s
Plugin Library CI / publish (11.PointCloudTools) (push) Successful in 2s
Plugin Library CI / publish (10.StoryEditor) (push) Successful in 3s
Plugin Library CI / publish (10.XNode) (push) Successful in 3s

This commit is contained in:
2026-04-16 22:04:55 +08:00
parent ef5113afc6
commit 8d9569557d
11 changed files with 565 additions and 60 deletions

View File

@@ -0,0 +1,318 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Cysharp.Threading.Tasks;
#if WECHAT_MINIGAME
using WeChatWASM;
#else
using System.IO; // 仅在非微信平台使用
using ICSharpCode.SharpZipLib.Zip;
#endif
namespace Stary.Evo.Unzip
{
public static class CrossPlatformUnzip
{
public static async UniTask UnzipAsync(string zipPath, string extractPath,
Action<float> progressCallback = null, bool deleteAfterExtract = true)
{
#if WECHAT_MINIGAME && UNITY_WEBGL
// 微信小程序:使用原生 WX API零 System.IO
await UnzipWeChatNativeAsync(zipPath, extractPath, progressCallback, deleteAfterExtract);
#elif UNITY_WEBGL && !WECHAT_MINIGAME
// WebGL使用 SharpZipLib虚拟文件系统
await UnzipWebGLAsync(zipPath, extractPath, progressCallback, deleteAfterExtract);
#else
// PC/移动端:标准 System.IO
await UnzipStandardAsync(zipPath, extractPath, progressCallback, deleteAfterExtract);
#endif
}
#region 1. System.IO
#if WECHAT_MINIGAME
private static async UniTask UnzipWeChatNativeAsync(string zipPath, string extractPath,
Action<float> progressCallback, bool deleteAfterExtract)
{
var tcs = new UniTaskCompletionSource();
var fs = WX.GetFileSystemManager();
// 检查 ZIP 存在(使用微信 API
bool fileExists = false;
fs.Access(new AccessOption()
{
path = zipPath,
success = (res) => fileExists = true,
fail = (err) => { }
});
await UniTask.Delay(50); // 等待异步结果
if (!fileExists)
throw new Exception($"ZIP 不存在: {zipPath}");
// 清理目标目录(递归删除)
try
{
fs.RmdirSync(extractPath, true);
}
catch
{
// 目录不存在或已删除
}
// 创建目标目录
fs.MkdirSync(extractPath, true);
// 模拟进度WX.Unzip 无细粒度进度)
float reportedProgress = 0f;
var progressTask = SimulateProgressAsync(progressCallback, () => reportedProgress >= 1f);
// 微信原生解压性能最佳C++ 实现)
WX.Unzip(new UnzipOption()
{
zipFilePath = zipPath,
targetPath = extractPath,
success = (res) =>
{
reportedProgress = 1f;
progressCallback?.Invoke(1f);
tcs.TrySetResult();
},
fail = (err) =>
{
tcs.TrySetException(new Exception($"微信解压失败: {err.errMsg}"));
}
});
await tcs.Task;
await progressTask;
// 删除 ZIP 释放空间(微信 200MB 限制严格)
if (deleteAfterExtract)
{
DeleteWeChatFileSafe(fs, zipPath);
}
}
private static async UniTask SimulateProgressAsync(Action<float> callback, Func<bool> isCompleted)
{
if (callback == null) return;
float progress = 0f;
while (!isCompleted())
{
progress = Mathf.Min(progress + UnityEngine.Random.Range(0.02f, 0.08f), 0.95f);
callback.Invoke(progress);
await UniTask.Delay(200);
}
}
private static void DeleteWeChatFileSafe(FileSystemManager fs, string path)
{
try
{
fs.UnlinkSync(path);
Debug.Log($"[微信] 已删除 ZIP");
}
catch (Exception ex)
{
Debug.LogWarning($"[微信] 删除失败: {ex.Message}");
}
}
/// <summary>
/// 获取微信缓存路径(字符串拼接,不使用 Path.Combine
/// </summary>
public static string GetWeChatPath(string relativePath)
{
// 手动拼接路径,避免使用 System.IO.Path
string basePath = WX.env.USER_DATA_PATH;
if (relativePath.StartsWith("/"))
return basePath + relativePath;
return basePath + "/" + relativePath;
}
#endif
#endregion
#region 2. WebGL SharpZipLib +
#if UNITY_WEBGL && !WECHAT_MINIGAME
private static async UniTask UnzipWebGLAsync(string zipPath, string extractPath,
Action<float> progressCallback, bool deleteAfterExtract)
{
// WebGL 使用 Unity 的虚拟文件系统(基于 IndexedDB
// 注意:虽然是 FileStream但在 WebGL 中实际是虚拟 IO
if (!File.Exists(zipPath))
throw new FileNotFoundException($"ZIP 不存在: {zipPath}");
string fullExtractPath = Path.GetFullPath(extractPath);
// 清理目录
if (Directory.Exists(extractPath))
Directory.Delete(extractPath, true);
Directory.CreateDirectory(extractPath);
using (FileStream fs = File.OpenRead(zipPath))
using (ZipFile zipFile = new ZipFile(fs))
{
int total = 0;
var validEntries = new List<ZipEntry>();
foreach (ZipEntry entry in zipFile)
{
if (entry.IsFile && !string.IsNullOrEmpty(entry.Name))
{
validEntries.Add(entry);
total++;
}
}
// WebGL 分帧解压,避免卡死浏览器
int processed = 0;
const int FILES_PER_FRAME = 3;
foreach (var entry in validEntries)
{
// 安全检查
string fullPath = Path.GetFullPath(Path.Combine(extractPath, entry.Name));
if (!fullPath.StartsWith(fullExtractPath))
{
Debug.LogWarning($"跳过非法路径: {entry.Name}");
continue;
}
// 创建目录
string dir = Path.GetDirectoryName(fullPath);
if (!string.IsNullOrEmpty(dir))
Directory.CreateDirectory(dir);
// 解压单文件
using (Stream input = zipFile.GetInputStream(entry))
using (FileStream output = File.Create(fullPath))
{
byte[] buffer = new byte[4096]; // WebGL 使用更小缓冲区
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, read);
}
}
processed++;
// 每 N 个文件让出一帧,保持浏览器响应
if (processed % FILES_PER_FRAME == 0)
{
progressCallback?.Invoke((float)processed / total);
await UniTask.Yield();
}
}
}
if (deleteAfterExtract)
{
File.Delete(zipPath);
}
}
#endif
#endregion
#region 3. PC/ System.IO
private static async UniTask UnzipStandardAsync(string zipPath, string extractPath,
Action<float> progressCallback, bool deleteAfterExtract)
{
await UniTask.SwitchToThreadPool();
try
{
if (!File.Exists(zipPath))
throw new FileNotFoundException($"ZIP 不存在: {zipPath}");
string fullExtractPath = Path.GetFullPath(extractPath);
if (Directory.Exists(extractPath))
Directory.Delete(extractPath, true);
Directory.CreateDirectory(extractPath);
using (FileStream fs = File.OpenRead(zipPath))
using (ZipFile zipFile = new ZipFile(fs))
{
int total = 0;
foreach (ZipEntry entry in zipFile)
if (entry.IsFile) total++;
int processed = 0;
foreach (ZipEntry entry in zipFile)
{
if (!entry.IsFile) continue;
string fullPath = Path.GetFullPath(Path.Combine(extractPath, entry.Name));
if (!fullPath.StartsWith(fullExtractPath)) continue;
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
using (Stream input = zipFile.GetInputStream(entry))
using (FileStream output = File.Create(fullPath))
{
await input.CopyToAsync(output);
}
processed++;
await UniTask.SwitchToMainThread();
progressCallback?.Invoke((float)processed / total);
await UniTask.SwitchToThreadPool();
}
}
}
finally
{
await UniTask.SwitchToMainThread();
if (deleteAfterExtract && File.Exists(zipPath))
File.Delete(zipPath);
}
}
#endregion
#region 4.
/// <summary>
/// 获取缓存路径(平台特定实现)
/// </summary>
public static string GetCachePath(string relativePath)
{
#if WECHAT_MINIGAME
return GetWeChatPath(relativePath);
#else
return Path.Combine(Application.persistentDataPath, relativePath);
#endif
}
/// <summary>
/// 检查文件存在(平台特定)
/// </summary>
public static bool FileExists(string path)
{
#if WECHAT_MINIGAME
bool exists = false;
WX.GetFileSystemManager().Access(new AccessOption()
{
path = path,
success = (res) => exists = true,
fail = (err) => { }
});
return exists;
#else
return File.Exists(path);
#endif
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a27d5c7ab782498b90e6d3a8879410d0
timeCreated: 1776267030

View File

@@ -19,8 +19,8 @@ namespace Stary.Evo
{
var initParameters = new OfflinePlayModeParameters();
var buildinFileSystemParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(null,
$"{Application.temporaryCachePath}/DownloadedContent/{packageName}");
Debug.Log($"UnityEvo:Host InitializeParameterPath: 【{Application.temporaryCachePath}/DownloadedContent/{packageName}】");
$"{Application.persistentDataPath}/DownloadedContent/{packageName}");
Debug.Log($"UnityEvo:Host InitializeParameterPath: 【{Application.persistentDataPath}/DownloadedContent/{packageName}】");
buildinFileSystemParams.AddParameter(FileSystemParametersDefine.APPEND_FILE_EXTENSION, true);
buildinFileSystemParams.AddParameter(FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST, true);
initParameters.BuildinFileSystemParameters = buildinFileSystemParams;

View File

@@ -4,36 +4,86 @@ using System.IO;
using System.IO.Compression;
using System.Threading;
using Cysharp.Threading.Tasks;
using Stary.Evo.Unzip;
using UnityEngine;
using UnityEngine.Networking;
using WeChatWASM;
namespace Stary.Evo
{
public class ZipTool
{
private static SemaphoreSlim unzipLock = new(1, 1);
// 新增:带进度回调的下载解压方法
public static async UniTask DownloadAndUnzipAsync(string fildId, string extractPath,
Action<float> downloadProgress = null, Action<float> unzipProgress = null)
{
string tempPath = Path.Combine(Application.persistentDataPath, "temp.zip");
try
{
string url = $"{AppConfig.IpConfig}/FileLoad/Download/{fildId}";
await WebRequestSystem.GetFile(url, tempPath,downloadProgress);
#if !WEIXINMINIGAME
string tempPath = Path.Combine(Application.persistentDataPath, "temp.zip");
await WebRequestSystem.GetFile(url, tempPath, downloadProgress);
#else
bool isCompleted = false;
string tempPath = WX.env.USER_DATA_PATH + "/temp.zip"; // 指定保存路径
var downloadTask = WX.DownloadFile(new DownloadFileOption()
{
url = url,
filePath = WX.env.USER_DATA_PATH + "/temp.zip", // 指定保存路径
success = (res) => {
isCompleted = true;
},
fail = (err) => {
Debug.LogError("下载失败:" + err.errMsg);
isCompleted = true;
}
});
// 注册进度回调(关键代码)
downloadTask.OnProgressUpdate((res) =>
{
// res.progress: 下载进度 0-100
// res.totalBytesExpectedToWrite: 预期总字节数
// res.totalBytesWritten: 已下载字节数
downloadProgress?.Invoke((float) res.progress);
Debug.Log($"下载进度:{res.progress:F1}% | " +
$"已下载:{res.totalBytesWritten / 1024}KB / " +
$"总计:{res.totalBytesExpectedToWrite / 1024}KB");
});
// 等待完成或被取消
await new WaitUntil(() => isCompleted);
// 等待下载完成,同时可以更新 UI
// while (!isCompleted)
// {
// // 在这里更新 Unity UI 进度条
// UpdateProgressUI(currentProgress);
// yield return null;
// }
// WX.GetFileSystemManager().Unzip(new UnzipOption()
// {
// zipFilePath = tempPath,
// targetPath = extractPath,
// success = (suc) =>
// {
// Debug.Log("解压成功");
// File.Delete(tempPath);
// },
// fail = (err) =>
// {
// Debug.LogError($"解压失败:{err.errMsg}");
// }
// });
#endif
// 解压下载的文件
await UnzipAsync(tempPath, extractPath, unzipProgress);
File.Delete(tempPath);
await CrossPlatformUnzip.UnzipAsync(tempPath, extractPath, unzipProgress);
}
finally
catch (Exception ex)
{
// 清理临时文件
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
Debug.LogError($"解压失败: {ex.Message}");
}
}
@@ -60,6 +110,7 @@ namespace Stary.Evo
Debug.LogWarning($"跳过不安全的文件路径: {entry.FullName}");
continue;
}
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
if (!string.IsNullOrEmpty(entry.Name))
@@ -67,7 +118,7 @@ namespace Stary.Evo
using (Stream inputStream = entry.Open())
using (FileStream outputStream = File.Create(filePath))
{
await inputStream.CopyToAsync(outputStream,81920);
await inputStream.CopyToAsync(outputStream, 81920);
}
}
@@ -76,6 +127,7 @@ namespace Stary.Evo
progressCallback?.Invoke(progress);
}
}
Debug.Log($"解压完成: {extractPath}");
}
catch (Exception ex)
@@ -85,6 +137,7 @@ namespace Stary.Evo
}
}
/// <summary>
/// 自定义网络请求器需要登录
/// </summary>