• unity内存优化之AB包篇(微信小游戏)


    1.搭建资源服务器使用(HFS软件(https://www.pianshen.com/article/54621708008/))

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System;
    5. public class Singleton<T> where T : class, new()
    6. {
    7. private static readonly Lazy lazy = new Lazy(() => new T());
    8. public static T Instance { get { return lazy.Value; } }
    9. protected Singleton() { }
    10. }
    11. public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
    12. {
    13. private static T _instance;
    14. public static T Instance
    15. {
    16. get
    17. {
    18. return _instance;
    19. }
    20. }
    21. protected virtual void Awake()
    22. {
    23. _instance = this as T;
    24. }
    25. }

    2.核心代码

    1. using Cysharp.Threading.Tasks;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Xml.Linq;
    5. using UnityEngine;
    6. using UnityEngine.Networking;
    7. using UnityEngine.U2D;
    8. /*
    9. 内存优化之AB包篇(微信小游戏)
    10. 问题:如何优化AB包占用的内存,游戏的AB包资源卸载策略是什么
    11. 答:卸载时机
    12. 1.该AB包被引用次数为0时候,此时缓存一定的时间,当缓存时间为0时,就可以调用bundle.Unload(true);
    13. 缓存时间内被调用重置缓存时间,引用次数增加。
    14. 这部分主要用来处理偶尔打开的界面
    15. 2.首先维护一个已经加载的AB包资源大小总值,然后设置一个内存基准值,当总值大于内存基准值的时候,
    16. 此时去卸载那些引用次数为0的ab资源(优先卸载加载早的ab包资源)。
    17. 这部分用来处理短时间内,玩家打开过多大型界面场景,如果不这么处理,手机内存会占用高且发热会严重。
    18. 引用次数的维护时机(引用次数始终不小于0)
    19. 1.例如一张图片 更换属于不同ab包的资源图片时,需要先将旧的ab包 引用次数减一,界面销毁时,最后动态加载的ab图片资源也需要减一,其他资源同理
    20. 2.同时加载一个AB资源时,在AB资源未加载完毕前,需要维护一个加载中的AB包资源实际被加载次数,
    21. 由于部分界面 正在动态加载的ab包资源未加载完毕时,此界面就可能已经被销毁,如果被销毁就需要将加载中的ab包的实际被加载次数减一。
    22. 3.当ab包资源加载完毕时,如果发现加载中的此ab包维护的实际被加载次数大于0,此时ab包的引用次数加一,同时实际被加载次数减一。
    23. 4.当界面销毁时,此界面的ab包和相关依赖的引用次数需要减一,动态加载的ab包资源也需要将引用次数减一
    24. 5.!!!需要注意的是 当A依赖于B时, A的最后一个实例被销毁时 A的引用变为0 但是B的引用此刻不变,除非A被卸载 才能将B的引用减一
    25. // A依赖于B
    26. // A被加载 会先加载B
    27. //那么A引用为1 B引用为1
    28. //A被加载第二次 A引用为2 B引用为2
    29. //A被加载第3次 A引用为3 B引用为3
    30. // A被删除1次 引用为2 B引用为2
    31. //A被删除第二次 A引用为1 B引用为1
    32. //A被删除第3次 A引用为0 B引用为1
    33. //A被卸载时 B引用为0
    34. // A依赖于B
    35. // A被加载 会先加载B
    36. //那么A引用为1 B引用为1
    37. //A被加载第二次 A引用为2 B引用为2
    38. // A被删除1次 引用为1 B引用为1
    39. //A被删除第二次 A引用为0 B引用为1
    40. //A被卸载时 B引用为0
    41. // A依赖于B
    42. // A被加载 会先加载B
    43. //那么A引用为1 B引用为1
    44. // A被删除1次 A引用为0 B引用为1
    45. //A被卸载时 B引用为0
    46. */
    47. [SelectionBase]
    48. public class LoadingAssetBundle
    49. {
    50. private string abName;
    51. public string GetABName()
    52. {
    53. return abName;
    54. }
    55. private int realLoadedTimesInLoading = 0;//在加载中 被加载的真实次数(也就是剔除那些只加载不使用的部分,例如界面动态加载图片还没加载完毕 这个界面就被销毁了)
    56. public int GetRealLoadedTimesInLoading()
    57. {
    58. return realLoadedTimesInLoading;
    59. }
    60. public void AddRealLoadedTimesInLoading()
    61. {
    62. realLoadedTimesInLoading++;
    63. }
    64. public void ReduceRealLoadedTimesInLoading()
    65. {
    66. realLoadedTimesInLoading--;
    67. }
    68. public LoadingAssetBundle(string _abName)
    69. {
    70. abName = _abName;
    71. AddRealLoadedTimesInLoading();
    72. }
    73. }
    74. [SelectionBase]
    75. public class LoadedAssetBundle
    76. {
    77. private string abName;
    78. private AssetBundle bundle;
    79. private float cacheTimeBySenconds = 10;//缓存秒数不同ab可配置
    80. public float curLastCacheTime = 10;//当前剩余缓存时间
    81. public int referenceTimes = 0;//引用次数
    82. public long memoryValue = 0;//ab包大小
    83. public int loadIndexOrder = 0;//引用顺序 越小代表越早被引用
    84. private bool isUnload = false;//是否被卸载
    85. public LoadedAssetBundle(string _abName, AssetBundle _bundle, long _memoryValue, int _loadIndexOrder)
    86. {
    87. isUnload = false;
    88. abName = _abName;
    89. bundle = _bundle;
    90. memoryValue = _memoryValue;//long size = long.Parse(unityWebRequest.GetResponseHeader("Content-Length"));
    91. ABManager.Instance.AddMemoryValue(_memoryValue);
    92. loadIndexOrder = _loadIndexOrder;
    93. }
    94. public AssetBundle GetAssetBundle()
    95. {
    96. return bundle;
    97. }
    98. public void AddRefer()//添加引用1
    99. {
    100. referenceTimes = referenceTimes + 1;
    101. curLastCacheTime = cacheTimeBySenconds;//重置剩余缓存1时间时间
    102. }
    103. public int ReduceRefer()//减少引用
    104. {
    105. if (referenceTimes > 0) {
    106. referenceTimes--;
    107. };
    108. return referenceTimes;
    109. }
    110. public void RefreshCacheLastTime(float time)
    111. {
    112. if (referenceTimes == 0)
    113. {
    114. curLastCacheTime -= time;
    115. CheckCacheTimeUnload();
    116. }
    117. }
    118. private void CheckCacheTimeUnload()
    119. {
    120. if (isUnload) return;
    121. if (curLastCacheTime <= 0&& referenceTimes == 0) {
    122. bundle.Unload(true); //卸载时机1
    123. isUnload = true;
    124. ABManager.Instance.ReduceMemoryValue(memoryValue);
    125. ABManager.Instance.RemoveABRequest(abName);
    126. ABManager.Instance.ReduceDependciedRefer(abName);
    127. Debug.Log($"curLastCacheTime Unload{abName},Count={ABManager.Instance.cachedLoadedDic.Count}");
    128. }
    129. }
    130. public void CheckOverMemoryUnload(int curMinReferIndexOrder)
    131. {
    132. if (isUnload) return;
    133. if (referenceTimes == 0 && ABManager.Instance.CheckOverMemoryMemoryReferenceValue())//&& curMinReferIndexOrder == loadIndexOrder
    134. {
    135. bundle.Unload(true);//卸载时机2
    136. isUnload = true;
    137. ABManager.Instance.ReduceMemoryValue(memoryValue);
    138. ABManager.Instance.RemoveABRequest(abName);
    139. ABManager.Instance.ReduceDependciedRefer(abName);
    140. Debug.Log($"Unload{abName}");
    141. }
    142. }
    143. public string GetABName()
    144. {
    145. return abName;
    146. }
    147. public bool IsUnLoad()
    148. {
    149. return isUnload;
    150. }
    151. }
    152. public class ABManager : MonoSingleton<ABManager>
    153. {
    154. public Dictionary<string, LoadedAssetBundle> cachedLoadedDic = new Dictionary<string, LoadedAssetBundle>();
    155. private Dictionary<string, LoadingAssetBundle> cachedLoadingDic = new Dictionary<string, LoadingAssetBundle>();
    156. private long memoryReferenceValue= 995406;//内存基准值
    157. private long curMemoryValue = 0;//内存当前值
    158. private int curReferIndexOrder = 0;//当前索引
    159. private int curMinReferIndexOrder = 0;//当前被加载最早的索引
    160. public void AddMemoryValue(long _memoryValue)
    161. {
    162. curMemoryValue = curMemoryValue + _memoryValue;
    163. //print("curMemoryValue" + curMemoryValue);
    164. }
    165. public void ReduceMemoryValue(long _memoryValue)
    166. {
    167. //Debug.Log("memoryValue" + _memoryValue);
    168. curMemoryValue = curMemoryValue - _memoryValue;
    169. curMinReferIndexOrder++;
    170. if (curMinReferIndexOrder > curReferIndexOrder)
    171. {
    172. curMinReferIndexOrder = curReferIndexOrder;
    173. }
    174. }
    175. public bool CheckOverMemoryMemoryReferenceValue()
    176. {
    177. return curMemoryValue > memoryReferenceValue;
    178. }
    179. private float checkSpan = 0.3f;
    180. public float time;
    181. List<string> removeList = new List<string>();
    182. public int CachedLoadedCount;
    183. private void CheckUnLoadCachedLoaded()
    184. {
    185. time += Time.fixedDeltaTime;
    186. if (time > checkSpan)
    187. {
    188. time = 0;
    189. removeList.Clear();
    190. foreach (var item in cachedLoadedDic)
    191. {
    192. if (!cachedLoadingDic.ContainsKey(item.Key))
    193. {
    194. item.Value.RefreshCacheLastTime(checkSpan);
    195. item.Value.CheckOverMemoryUnload(curMinReferIndexOrder);
    196. if (item.Value.IsUnLoad()) removeList.Add(item.Key);
    197. }
    198. }
    199. for (int i = 0; i < removeList.Count; i++)
    200. {
    201. print($"removeList={removeList[i]}");
    202. cachedLoadedDic.Remove(removeList[i]);
    203. }
    204. }
    205. CachedLoadedCount = cachedLoadedDic.Count;
    206. }
    207. // Update is called once per frame
    208. void FixedUpdate()
    209. {
    210. CheckUnLoadCachedLoaded();
    211. }
    212. private AssetBundle mainAB = null; //主包
    213. private AssetBundleManifest mainManifest = null; //主包中配置文件---用以获取依赖包
    214. private string basePath = "http://192.168.31.208/AssetBundles/";
    215. private string mainABName = "AssetBundles";
    216. public Dictionary<string, string> AssetNameToABName = new Dictionary<string, string>();
    217. public async UniTask LoadAsset(string assetName)
    218. {
    219. string abName = assetName.ToLower() + ".ab";
    220. AssetBundle ab = await LoadABPackage(abName);
    221. //await UniTask.SwitchToMainThread();
    222. return ab.LoadAsset(assetName);
    223. }
    224. ///
    225. /// 加载图集里面的图片
    226. /// 案例
    227. /// Image a = nul;;
    228. /// if (a != null)
    229. /// ABManager.Instance.UnloadAsset(a);
    230. /// a = ABManager.Instance.LoadAtlasSprite(a);
    231. ///
    232. ///
    233. ///
    234. ///
    235. public async UniTask LoadAtlasSprite(string assetName, string textureName)
    236. {
    237. string abName = assetName.ToLower() + ".ab";
    238. AssetBundle ab = await LoadABPackage(abName);
    239. SpriteAtlas spriteAtlas = ab.LoadAsset(assetName);
    240. return spriteAtlas.GetSprite(textureName);
    241. }
    242. //单个包卸载
    243. public void ReduceRefer(string assetName)
    244. {
    245. string abName = assetName.ToLower() + ".ab";
    246. if (cachedLoadingDic.ContainsKey(abName))
    247. {
    248. cachedLoadingDic[abName].ReduceRealLoadedTimesInLoading();
    249. }
    250. else
    251. {
    252. //--引用
    253. if (cachedLoadedDic.ContainsKey(abName))
    254. {
    255. int referValue = cachedLoadedDic[abName].ReduceRefer();
    256. // A依赖于B
    257. // A被加载 会先加载B
    258. //那么A引用为1 B引用为1
    259. //A被加载第二次 A引用为2 B引用为2
    260. //A被加载第3次 A引用为3 B引用为3
    261. // A被删除1次 引用为2 B引用为2
    262. //A被删除第二次 A引用为1 B引用为1
    263. //A被删除第3次 A引用为0 B引用为1
    264. //A被卸载时 B引用为0
    265. // A依赖于B
    266. // A被加载 会先加载B
    267. //那么A引用为1 B引用为1
    268. //A被加载第二次 A引用为2 B引用为2
    269. // A被删除1次 引用为1 B引用为1
    270. //A被删除第二次 A引用为0 B引用为1
    271. //A被卸载时 B引用为0
    272. // A依赖于B
    273. // A被加载 会先加载B
    274. //那么A引用为1 B引用为1
    275. // A被删除1次 A引用为0 B引用为1
    276. //A被卸载时 B引用为0
    277. if (referValue > 0)
    278. {
    279. ReduceDependciedRefer(abName);
    280. }
    281. }
    282. }
    283. }
    284. public void ReduceDependciedRefer(string abName)
    285. {
    286. string[] dependencies = mainManifest.GetAllDependencies(abName);
    287. for (int i = 0; i < dependencies.Length; i++)
    288. {
    289. if (cachedLoadedDic.ContainsKey(dependencies[i]))
    290. {
    291. cachedLoadedDic[dependencies[i]].ReduceRefer();
    292. }
    293. }
    294. }
    295. //加载AB包
    296. private async UniTask LoadABPackage(string abName)
    297. {
    298. //加载ab包,需一并加载其依赖包。
    299. if (mainAB == null)
    300. {
    301. //获取ab包内容
    302. mainAB = await DownloadABPackage(mainABName);
    303. //获取主包下的AssetBundleManifest资源文件(存有依赖信息)
    304. mainManifest = mainAB.LoadAsset("AssetBundleManifest");
    305. }
    306. //根据manifest获取所有依赖包的名称 固定API 保证不丢失依赖
    307. string[] dependencies = mainManifest.GetAllDependencies(abName);
    308. if (dependencies.Length > 0)
    309. {
    310. var tasks = new List(); // 创建一个任务列表来存储异步操作
    311. //循环加载所有依赖包
    312. for (int i = 0; i < dependencies.Length; i++)
    313. {
    314. //如果不在缓存则加入
    315. if (!cachedLoadedDic.ContainsKey(dependencies[i]))
    316. tasks.Add(LoadABPackage(dependencies[i]));
    317. else
    318. {
    319. cachedLoadedDic[dependencies[i]].AddRefer(); //++引用
    320. }
    321. }
    322. // 使用UniTask.WhenAll等待所有任务完成
    323. await UniTask.WhenAll(tasks);
    324. }
    325. //加载目标包 -- 同理注意缓存问题
    326. if (cachedLoadedDic.ContainsKey(abName))
    327. {
    328. cachedLoadedDic[abName].AddRefer(); //++引用
    329. Debug.Log($"ContainsKey{abName}");
    330. return (cachedLoadedDic[abName].GetAssetBundle());
    331. }
    332. else
    333. {
    334. await DownloadABPackage(abName);
    335. Debug.Log($"DownloadABPackage{abName}");
    336. return (cachedLoadedDic[abName].GetAssetBundle());
    337. }
    338. }
    339. //存儲下載操作
    340. Dictionary<string, UnityWebRequestAsyncOperation> ABRequestOpera = new Dictionary<string, UnityWebRequestAsyncOperation>();
    341. public void RemoveABRequest(string abname)
    342. {
    343. string url = basePath + abname;
    344. ABRequestOpera[url].webRequest.Dispose();//试试多个异步创建
    345. ABRequestOpera.Remove(url);
    346. }
    347. async UniTask DownloadABPackage(string abname)
    348. {
    349. if (cachedLoadedDic.ContainsKey(abname))
    350. {
    351. cachedLoadedDic[abname].AddRefer();
    352. return cachedLoadedDic[abname].GetAssetBundle();
    353. }
    354. string url = basePath + abname;
    355. Debug.Log(url);
    356. if (!cachedLoadingDic.ContainsKey(abname))
    357. {
    358. cachedLoadingDic.Add(abname, new LoadingAssetBundle(abname));
    359. }
    360. else
    361. {
    362. cachedLoadingDic[abname].AddRealLoadedTimesInLoading();
    363. }
    364. if (!ABRequestOpera.ContainsKey(url))
    365. {
    366. UnityWebRequest req = UnityWebRequestAssetBundle.GetAssetBundle(url);
    367. UnityWebRequestAsyncOperation operation = req.SendWebRequest();
    368. ABRequestOpera.Add(url, operation);
    369. }
    370. await ABRequestOpera[url];
    371. if (!cachedLoadedDic.ContainsKey(abname))
    372. {
    373. curReferIndexOrder++;
    374. AssetBundle ab = DownloadHandlerAssetBundle.GetContent(ABRequestOpera[url].webRequest);
    375. long size = long.Parse(ABRequestOpera[url].webRequest.GetResponseHeader("Content-Length"));
    376. cachedLoadedDic.Add(abname, new LoadedAssetBundle(abname,ab, size, curReferIndexOrder));
    377. }
    378. if (cachedLoadingDic.ContainsKey(abname)&&cachedLoadingDic[abname].GetRealLoadedTimesInLoading() > 0)
    379. {
    380. cachedLoadedDic[abname].AddRefer();
    381. cachedLoadingDic[abname].ReduceRealLoadedTimesInLoading();
    382. if (cachedLoadingDic[abname].GetRealLoadedTimesInLoading() == 0)
    383. {
    384. cachedLoadingDic.Remove(abname);
    385. }
    386. }
    387. return cachedLoadedDic[abname].GetAssetBundle();
    388. }
    389. //所有包卸载
    390. public void UnLoadAll()
    391. {
    392. AssetBundle.UnloadAllAssetBundles(false);
    393. //注意清空缓存
    394. cachedLoadedDic.Clear();
    395. cachedLoadingDic.Clear();
    396. mainAB = null;
    397. mainManifest = null;
    398. }
    399. }

    3..打包AB包代码

    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.IO;
    4. using System;
    5. using System.Collections.Generic;
    6. ///
    7. /// AB包创建
    8. ///
    9. public class CreateAssetBundles : MonoBehaviour
    10. {
    11. public static string BuildAssetBundlePath = Application.dataPath + "/AssetsPach/AssetBundles";
    12. [MenuItem("Build/BuildAssetBundles")]
    13. public static void BuildAssetBundle()
    14. {
    15. SetAssetBundle();
    16. string dir = BuildAssetBundlePath; //相对路径
    17. if (!Directory.Exists(dir)) //判断路径是否存在
    18. {
    19. Directory.CreateDirectory(dir);
    20. }
    21. BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, EditorUserBuildSettings.activeBuildTarget); //这里是第一点注意事项,BuildTarget类型选择WebGL
    22. AssetDatabase.Refresh();
    23. Debug.Log("打包完成");
    24. }
    25. //需要打包的资源目录
    26. public static string SetAssetBundlePath = Application.dataPath + "/AssetsPach/WortAsset";
    27. public static void SetAssetBundle()
    28. {
    29. string dir = SetAssetBundlePath; //相对路径
    30. AssetDatabase.RemoveUnusedAssetBundleNames();//移除无用的AssetBundleName
    31. //Debug.LogError(Application.dataPath);//上级路径 F:/TUANJIEProject/My project/Assets
    32. list_Files = new List();
    33. ContinueCheck(dir);
    34. for (int a = 0; a < list_Files.Count; a++)//
    35. {
    36. SetBundleName(list_Files[a].assetPath);
    37. }
    38. Debug.Log("生成ab包完成");
    39. //SetBundleName("Assets/Ship/AC_Enterprise_T01/prefab/AC_Enterprise_T01_M01_ShipMesh.prefab");
    40. }
    41. //******资源参数
    42. static List list_Files;//文件列表
    43. static string assetBundleName = "ab";
    44. static string assetBundleVariant = "";
    45. //int indentation;//缩进等级
    46. struct stru_FileInfo
    47. {
    48. public string fileName;
    49. public string filePath;//绝对路径
    50. public string assetPath;//U3D内部路径
    51. public Type assetType;
    52. }
    53. static void ContinueCheck(string path)
    54. {
    55. DirectoryInfo directory = new DirectoryInfo(path);
    56. FileSystemInfo[] fileSystemInfos = directory.GetFileSystemInfos();//获取文件夹下的文件信息
    57. foreach (var item in fileSystemInfos)
    58. {
    59. int idx = item.ToString().LastIndexOf(@"\");
    60. string name = item.ToString().Substring(idx + 1);
    61. if (!name.Contains(".meta"))//剔除meta文件
    62. {
    63. CheckFileOrDirectory(item, path + "/" + name);
    64. }
    65. }
    66. }
    67. static void CheckFileOrDirectory(FileSystemInfo fileSystemInfo, string path)
    68. {
    69. FileInfo fileInfo = fileSystemInfo as FileInfo;
    70. if (fileInfo != null)
    71. {
    72. stru_FileInfo t_file = new stru_FileInfo();
    73. t_file.fileName = fileInfo.Name;
    74. t_file.filePath = fileInfo.FullName;
    75. t_file.assetPath = "Assets" + fileInfo.FullName.Replace(Application.dataPath.Replace("/", "\\"), "");//用于下一步获得文件类型
    76. t_file.assetType = AssetDatabase.GetMainAssetTypeAtPath(t_file.assetPath);
    77. list_Files.Add(t_file);
    78. }
    79. else
    80. {
    81. ContinueCheck(path);
    82. }
    83. }
    84. static void SetBundleName(string path)
    85. {
    86. print(path);
    87. var importer = AssetImporter.GetAtPath(path);
    88. string[] strs = path.Split(".");
    89. string[] dictors = strs[0].Split('/');
    90. if (importer)
    91. {
    92. if (assetBundleVariant != "")
    93. {
    94. importer.assetBundleVariant = assetBundleVariant;
    95. }
    96. if (assetBundleName != "")
    97. {
    98. importer.assetBundleName = path.ToLower() + "." + assetBundleName;
    99. }
    100. }
    101. else
    102. {
    103. Debug.Log("importer是空的" + path);//jpg png tga
    104. }
    105. }
    106. }

    4.资源如下 几张美女壁纸,每个预设都是一个壁纸和关闭按钮界面挂载了代码

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class Panel : MonoBehaviour
    6. {
    7. public string asssetName;
    8. // Start is called before the first frame update
    9. void Start()
    10. {
    11. transform.GetComponentInChildren
    12. //StartCoroutine(TestLoadSize();
    13. UIManager.Instance.DeletePanel(this);
    14. });
    15. }
    16. // Update is called once per frame
    17. void Update()
    18. {
    19. }
    20. }

    4.启动场景和代码

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class UIManager : MonoSingleton<UIManager>
    6. {
    7. public Transform parent;
    8. // Start is called before the first frame update
    9. void Start()
    10. {
    11. for (int i = 0; i < transform.childCount; i++)
    12. {
    13. int index = i;
    14. transform.GetChild(i).GetComponent
    15. //StartCoroutine(TestLoadSize();
    16. print(111);
    17. DownPanel($"Assets/Assetspach/wortasset/prefabs/Panel{index + 1}.prefab");
    18. //DownPanel($"Assets/Assetspach/wortasset/prefabs/Panel{index + 1}.prefab");
    19. });
    20. }
    21. }
    22. async void DownPanel(string asssetName)
    23. {
    24. GameObject go = await ABManager.Instance.LoadAsset(asssetName);
    25. GameObject.Instantiate(go, parent).GetComponent().asssetName =asssetName;
    26. }
    27. // Update is called once per frame
    28. public void DeletePanel(Panel panel)
    29. {
    30. ABManager.Instance.ReduceRefer(panel.asssetName);
    31. DestroyImmediate(panel.gameObject);
    32. }
    33. }

  • 相关阅读:
    疏水18碳磷脂磷脂-聚乙二醇-羧基,DSPE-PEG-Acid,CAS:1403744-37-5
    Flask 学习-47.Flask-RESTX 自定义响应内容marshal_with
    3、微服务设计为什么要选择DDD
    IDEA 中使用 SparkSQL 远程连接 Hive
    数据建模设计
    k8s--基础--25.4--Helm--部署
    aarch64 麒麟V10创建nvidia_gpu_expoter服务
    推荐Java开发常用的工具类库google guava
    哪个牌子的led灯质量好?2022LED护眼台灯最好的品牌有哪些
    新鲜速递:Spring Cloud Alibaba环境在Spring Boot 3时代的快速搭建
  • 原文地址:https://blog.csdn.net/weixin_41995872/article/details/136760959