using MAX.Core.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Store.ImageCache.Dtos; using Store.ImageCache.Entities; using Store.ImageCache.Mapper; using System; using System.Collections.Generic; using System.Text; namespace Store.ImageCache.Services { public class ImageCacheService : ISingletonDependency { private const int Md5Length = 32; private const int PlatformUrlMaxLength = 1000; private readonly IFreeSql _freeSql; private ImageCacheMapper mapper; public ImageCacheService([FromKeyedServices("ImageCacheConnection")] IFreeSql freeSql) { mapper = new ImageCacheMapper(); if (freeSql == null) { throw new ArgumentNullException("freeSql"); } _freeSql = freeSql; } /// /// 按 MD5 查询图片缓存;platform 为空时返回该 MD5 在所有平台的缓存。 /// public ImageCacheDto GetByMd5(string sourceUrlMd5, byte? platform = null) { var md5 = NormalizeMd5(sourceUrlMd5); return GetByMd5sInternal(new List { md5 }, platform).FirstOrDefault(); } /// /// 批量按 MD5 查询图片缓存。MD5 会按首字符分组,只查询对应的 images_0 到 images_f 分表。 /// public List GetByMd5s(IEnumerable sourceUrlMd5s, byte? platform = null) { return GetByMd5sInternal(NormalizeMd5s(sourceUrlMd5s), platform); } /// /// 按 MD5 和平台直接修改平台图片 URL,未找到时返回 false。 /// public bool AddOrUpdate(string sourceUrlMd5, byte platform, string platformUrl) { var md5 = NormalizeMd5(sourceUrlMd5); ValidatePlatformUrl(platformUrl); var tableName = GetTableName(md5[0]); var existing = _freeSql.Select() .AsTable((type, oldName) => tableName) .Where(a => a.source_url_md5 == sourceUrlMd5 && a.platform == platform) .First(); var now = DateTime.Now; if (existing != null) { var affected = _freeSql.Update() .AsTable(tableName) .Set(a => a.platform_url, platformUrl) .Set(a => a.update_time, now) .Where(a => a.source_url_md5 == md5 && a.platform == platform) .ExecuteAffrows(); return affected > 0; } else { ImageCacheEntity imageCache = new ImageCacheEntity(); imageCache.id = 0; imageCache.source_url_md5 = md5; imageCache.platform = platform; imageCache.platform_url = platformUrl; imageCache.create_time = now; imageCache.update_time = now; imageCache.id = _freeSql.Insert(imageCache) .AsTable(tableName) .ExecuteIdentity(); return imageCache.id > 0; } } private List GetByMd5sInternal(List md5s, byte? platform) { var result = new List(); if (md5s.Count == 0) { return result; } foreach (var shard in md5s.GroupBy(a => a[0])) { var tableName = GetTableName(shard.Key); var md5sInTable = shard.ToList(); var query = _freeSql.Select() .AsTable((type, oldName) => tableName) .Where(a => md5sInTable.Contains(a.source_url_md5)); if (platform.HasValue) { var platformValue = platform.Value; query = query.Where(a => a.platform == platformValue); } result.AddRange(query.ToList().Select(mapper.ToDto)); } return result; } private List NormalizeMd5s(IEnumerable sourceUrlMd5s) { if (sourceUrlMd5s == null) { throw new ArgumentNullException("sourceUrlMd5s"); } var result = new List(); foreach (var sourceUrlMd5 in sourceUrlMd5s) { var md5 = NormalizeMd5(sourceUrlMd5); if (!result.Contains(md5)) { result.Add(md5); } } return result; } private string NormalizeMd5(string sourceUrlMd5) { if (string.IsNullOrWhiteSpace(sourceUrlMd5)) { throw new ArgumentException("MD5 不能为空", "sourceUrlMd5"); } var md5 = sourceUrlMd5.Trim().ToLowerInvariant(); if (md5.Length != Md5Length || md5.Any(a => !Uri.IsHexDigit(a))) { throw new ArgumentException("MD5 必须是 32 位十六进制字符串", "sourceUrlMd5"); } return md5; } private string GetTableName(char shard) { return "images_" + shard; } private void ValidatePlatformUrl(string platformUrl) { if (string.IsNullOrWhiteSpace(platformUrl)) { throw new ArgumentException("平台图片 URL 不能为空", "platformUrl"); } if (platformUrl.Length > PlatformUrlMaxLength) { throw new ArgumentException("平台图片 URL 长度不能超过 1000", "platformUrl"); } } } }