增加ImageCache微信 图片缓存
This commit is contained in:
1 parent
d6cb5b966b
commit
d0f9f00b76
15 files changed
+518
-30
No files matched your search
@@ -16,37 +16,31 @@ namespace MAX.Framework.Commons
|
|||||||
{
|
{
|
||||||
if (context.Result is ObjectResult objResult)
|
if (context.Result is ObjectResult objResult)
|
||||||
{
|
{
|
||||||
if (!IsAlreadyWrapped(objResult.Value))
|
if (objResult.Value is ApiResultBase)
|
||||||
|
{
|
||||||
|
objResult.StatusCode = 200;
|
||||||
|
objResult.DeclaredType = objResult.Value.GetType();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非成功响应交由状态码中间件处理,避免先包装成业务成功。
|
||||||
|
var statusCode = objResult.StatusCode ?? 200;
|
||||||
|
if (statusCode >= 200 && statusCode < 300)
|
||||||
{
|
{
|
||||||
var apiResult = ApiResult<object>.Ok(objResult.Value);
|
var apiResult = ApiResult<object>.Ok(objResult.Value);
|
||||||
|
|
||||||
objResult.Value = apiResult;
|
objResult.Value = apiResult;
|
||||||
objResult.DeclaredType = null;
|
objResult.DeclaredType = null;
|
||||||
|
objResult.StatusCode = 200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (context.Result is EmptyResult || context.Result is NoContentResult)
|
else if (context.Result is EmptyResult || context.Result is NoContentResult)
|
||||||
{
|
{
|
||||||
context.Result = new ObjectResult(ApiResult.Ok());
|
context.Result = new ObjectResult(ApiResult.Ok()) { StatusCode = 200 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnActionExecuting(ActionExecutingContext context) { }
|
public void OnActionExecuting(ActionExecutingContext context) { }
|
||||||
private static bool IsAlreadyWrapped(object? value)
|
|
||||||
{
|
|
||||||
if (value is null) return false;
|
|
||||||
|
|
||||||
var type = value.GetType();
|
|
||||||
|
|
||||||
// 递归判断:只要类型链上有任何一层是 ApiResult<> 的开放泛型定义,或直接是 ApiResult,就算已包装
|
|
||||||
while (type is not null)
|
|
||||||
{
|
|
||||||
if (type == typeof(ApiResult)) return true;
|
|
||||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ApiResult<>)) return true;
|
|
||||||
type = type.BaseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ namespace MAX.Framework.Commons
|
|||||||
}
|
}
|
||||||
|
|
||||||
var originalBody = context.Response.Body;
|
var originalBody = context.Response.Body;
|
||||||
var buffer = _streamManager.GetStream();
|
using var buffer = _streamManager.GetStream();
|
||||||
|
|
||||||
context.Response.Body = buffer;
|
context.Response.Body = buffer;
|
||||||
|
|
||||||
@@ -126,7 +126,6 @@ namespace MAX.Framework.Commons
|
|||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
});
|
});
|
||||||
|
|
||||||
buffer.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsPassthroughByRequest(HttpContext context)
|
private static bool IsPassthroughByRequest(HttpContext context)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using MAX.Framework.Commons;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace MAX.Framework.Dtos
|
namespace MAX.Framework.Dtos
|
||||||
{
|
{
|
||||||
@@ -29,10 +29,10 @@ namespace MAX.Framework.Dtos
|
|||||||
public abstract class ApiResultBase
|
public abstract class ApiResultBase
|
||||||
{
|
{
|
||||||
/// <summary>业务状态码</summary>
|
/// <summary>业务状态码</summary>
|
||||||
public int Code { get; set; } = ResultCode.Success;
|
public int Code { get; init; } = ResultCode.Success;
|
||||||
|
|
||||||
/// <summary>提示信息</summary>
|
/// <summary>提示信息</summary>
|
||||||
public string? Msg { get; set; }
|
public string? Msg { get; init; }
|
||||||
|
|
||||||
/// <summary>请求是否成功</summary>
|
/// <summary>请求是否成功</summary>
|
||||||
public bool Success => Code == ResultCode.Success;
|
public bool Success => Code == ResultCode.Success;
|
||||||
@@ -44,13 +44,25 @@ namespace MAX.Framework.Dtos
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApiResult<T> : ApiResultBase
|
public class ApiResult<T> : ApiResultBase
|
||||||
{
|
{
|
||||||
public T? Data { get; set; }
|
/// <summary>响应数据;失败时不应依赖该值,值类型默认值并非 null。</summary>
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||||
|
public T? Data { get; init; }
|
||||||
|
|
||||||
|
/// <summary>将无数据响应转换为当前数据类型,保留状态码和提示信息。</summary>
|
||||||
|
public static implicit operator ApiResult<T>(ApiResult result)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(result);
|
||||||
|
return new ApiResult<T> { Code = result.Code, Msg = result.Msg, Data = default };
|
||||||
|
}
|
||||||
|
|
||||||
public static ApiResult<T> Ok(T data, string msg = "操作成功")
|
public static ApiResult<T> Ok(T data, string msg = "操作成功")
|
||||||
=> new ApiResult<T> { Code = ResultCode.Success, Data = data, Msg = msg };
|
=> new ApiResult<T> { Code = ResultCode.Success, Data = data, Msg = msg };
|
||||||
|
|
||||||
public static ApiResult<T> Fail(string msg = "操作失败", int code = ResultCode.Fail, T? data = default)
|
public static ApiResult<T> Fail(string msg = "操作失败", int code = ResultCode.Fail, T? data = default)
|
||||||
=> new ApiResult<T> { Code = code, Msg = msg, Data = data };
|
{
|
||||||
|
ApiResult.ValidateFailureCode(code);
|
||||||
|
return new ApiResult<T> { Code = code, Msg = msg, Data = data };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,11 +70,32 @@ namespace MAX.Framework.Dtos
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApiResult : ApiResultBase
|
public class ApiResult : ApiResultBase
|
||||||
{
|
{
|
||||||
public static ApiResult Ok(string msg = "操作成功")
|
/// <summary>无数据响应仍输出 data: null,保持统一的响应字段。</summary>
|
||||||
=> new ApiResult { Code = ResultCode.Success, Msg = msg };
|
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||||
|
public object? Data => null;
|
||||||
|
|
||||||
|
/// <summary>创建带数据的成功响应,自动推断数据类型。</summary>
|
||||||
|
public static ApiResult<T> Ok<T>(T data, string msg = "操作成功")
|
||||||
|
=> ApiResult<T>.Ok(data, msg);
|
||||||
|
|
||||||
|
/// <summary>创建带数据的失败响应,自动推断数据类型。</summary>
|
||||||
|
public static ApiResult<T> Fail<T>(string msg, T data, int code = ResultCode.Fail)
|
||||||
|
=> ApiResult<T>.Fail(msg, code, data);
|
||||||
|
|
||||||
|
public static ApiResult Ok()
|
||||||
|
=> new ApiResult { Code = ResultCode.Success, Msg = "操作成功" };
|
||||||
|
|
||||||
public static ApiResult Fail(string msg = "操作失败", int code = ResultCode.Fail)
|
public static ApiResult Fail(string msg = "操作失败", int code = ResultCode.Fail)
|
||||||
=> new ApiResult { Code = code, Msg = msg };
|
{
|
||||||
|
ValidateFailureCode(code);
|
||||||
|
return new ApiResult { Code = code, Msg = msg };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ValidateFailureCode(int code)
|
||||||
|
{
|
||||||
|
if (code == ResultCode.Success)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(code), code, "失败响应不能使用成功状态码。");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using Asp.Versioning;
|
||||||
|
using MAX.Framework.Dtos;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MySqlX.XDevAPI.Common;
|
||||||
|
using Store.Framework.Base;
|
||||||
|
using Store.Framework.Models;
|
||||||
|
using Store.ImageCache.Dtos;
|
||||||
|
using Store.ImageCache.Services;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
||||||
|
namespace KJD_Web.Controllers
|
||||||
|
{
|
||||||
|
[ApiVersionNeutral]
|
||||||
|
[Route("api/ImageCache/[action]")]
|
||||||
|
public class ImageCacheController : AppControllerBase
|
||||||
|
{
|
||||||
|
private readonly ImageCacheService _imageCacheService;
|
||||||
|
private readonly ILogger<ImageCacheController> _logger;
|
||||||
|
|
||||||
|
public ImageCacheController(ImageCacheService imageCacheService, ILogger<ImageCacheController> logger)
|
||||||
|
{
|
||||||
|
if (imageCacheService == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("imageCacheService");
|
||||||
|
}
|
||||||
|
|
||||||
|
_imageCacheService = imageCacheService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量按 MD5 查询图片缓存。
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public ApiResult<List<ImageCacheDto>> GetImageCache(ImageCacheBatchQueryRequest request)
|
||||||
|
{
|
||||||
|
List<ImageCacheDto> data = new List<ImageCacheDto>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (request != null && request.Md5s != null)
|
||||||
|
{
|
||||||
|
data = _imageCacheService.GetByMd5s(request.Md5s, request.Platform);
|
||||||
|
}
|
||||||
|
return ApiResult.Ok(data);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return ApiResult.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按 MD5 和平台更新平台图片 URL。
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public ApiResult<List<string>> AddOrUpdate(ImageCacheUpdateUrlRequest[] requests)
|
||||||
|
{
|
||||||
|
if (requests == null || requests.Length == 0)
|
||||||
|
{
|
||||||
|
return ApiResult.Fail("参数不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var failedItems = new List<string>();
|
||||||
|
foreach (var item in requests)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return ApiResult.Fail("图片参数不能包含空项", ResultCode.ValidationError);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_imageCacheService.AddOrUpdate(item.Md5, item.Platform, item.PlatformUrl))
|
||||||
|
{
|
||||||
|
failedItems.Add(item.Md5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "AddOrUpdate 失败, Md5={Md5}", item.Md5);
|
||||||
|
failedItems.Add(item.Md5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return failedItems.Count == 0
|
||||||
|
? ApiResult.Ok()
|
||||||
|
: ApiResult.Fail("部分图片失败", failedItems);
|
||||||
|
}
|
||||||
|
public class ImageCacheBatchQueryRequest
|
||||||
|
{
|
||||||
|
public List<string> Md5s { get; set; }
|
||||||
|
public byte? Platform { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageCacheUpdateUrlRequest
|
||||||
|
{
|
||||||
|
public string Md5 { get; set; }
|
||||||
|
public byte Platform { get; set; }
|
||||||
|
public string PlatformUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Store.ImageCache.Dtos
|
||||||
|
{
|
||||||
|
public class ImageCacheDto
|
||||||
|
{
|
||||||
|
public byte platform { get; set; }
|
||||||
|
public string md5 { get; set; }
|
||||||
|
public string platform_url { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FreeSql.DataAnnotations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Store.ImageCache.Entities
|
||||||
|
{
|
||||||
|
[Table(Name = "images_0")]
|
||||||
|
public class ImageCacheEntity
|
||||||
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
[Column(Name = "id", IsIdentity = true, IsPrimary = true)]
|
||||||
|
public long id { get; set; }
|
||||||
|
|
||||||
|
[Column(Name = "platform")]
|
||||||
|
public byte platform { get; set; }
|
||||||
|
|
||||||
|
//[JsonProperty("md5")]
|
||||||
|
[Column(Name = "source_url_md5", StringLength = 32)]
|
||||||
|
public string source_url_md5 { get; set; }
|
||||||
|
|
||||||
|
[Column(Name = "platform_url", StringLength = 1000)]
|
||||||
|
public string platform_url { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
[Column(Name = "create_time")]
|
||||||
|
public DateTime create_time { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
[Column(Name = "update_time")]
|
||||||
|
public DateTime update_time { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
using Store.Framework.Models;
|
||||||
|
using Store.ImageCache.Dtos;
|
||||||
|
using Store.ImageCache.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Store.ImageCache.Mapper
|
||||||
|
{
|
||||||
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
|
public partial class ImageCacheMapper
|
||||||
|
{
|
||||||
|
[MapProperty(nameof(ImageCacheEntity.source_url_md5), nameof(ImageCacheDto.md5))]
|
||||||
|
public partial ImageCacheDto ToDto(ImageCacheEntity entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
using MAX.Core.Interfaces;
|
||||||
|
using MAX.Framework.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
|
||||||
|
namespace Store.Modules.CloudProduct
|
||||||
|
{
|
||||||
|
public class Module : IModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 本模块自己的配置文件
|
||||||
|
/// </summary>
|
||||||
|
public const string ConfigurationFileName = "appsettings.ImageCache.json";
|
||||||
|
|
||||||
|
public int Priority => IntPriority.Normal;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app
|
||||||
|
/// </summary>
|
||||||
|
public void AddModule(IHostApplicationBuilder host)
|
||||||
|
{
|
||||||
|
IServiceCollection services = host.Services;
|
||||||
|
IConfigurationManager configuration = host.Configuration;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(ConfigurationFileName))
|
||||||
|
{
|
||||||
|
string path = Path.IsPathRooted(ConfigurationFileName)? ConfigurationFileName: Path.Combine(AppContext.BaseDirectory, ConfigurationFileName);
|
||||||
|
configuration.AddJsonFile(path, optional: false, reloadOnChange: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管道装配阶段(builder.Build() 之后、app.Run() 之前):这里可以挂中间件、解析容器里的服务
|
||||||
|
/// </summary>
|
||||||
|
public void UseModule(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按 MD5 查询图片缓存;platform 为空时返回该 MD5 在所有平台的缓存。
|
||||||
|
/// </summary>
|
||||||
|
public ImageCacheDto GetByMd5(string sourceUrlMd5, byte? platform = null)
|
||||||
|
{
|
||||||
|
var md5 = NormalizeMd5(sourceUrlMd5);
|
||||||
|
return GetByMd5sInternal(new List<string> { md5 }, platform).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量按 MD5 查询图片缓存。MD5 会按首字符分组,只查询对应的 images_0 到 images_f 分表。
|
||||||
|
/// </summary>
|
||||||
|
public List<ImageCacheDto> GetByMd5s(IEnumerable<string> sourceUrlMd5s, byte? platform = null)
|
||||||
|
{
|
||||||
|
return GetByMd5sInternal(NormalizeMd5s(sourceUrlMd5s), platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按 MD5 和平台直接修改平台图片 URL,未找到时返回 false。
|
||||||
|
/// </summary>
|
||||||
|
public bool AddOrUpdate(string sourceUrlMd5, byte platform, string platformUrl)
|
||||||
|
{
|
||||||
|
var md5 = NormalizeMd5(sourceUrlMd5);
|
||||||
|
ValidatePlatformUrl(platformUrl);
|
||||||
|
var tableName = GetTableName(md5[0]);
|
||||||
|
var existing = _freeSql.Select<ImageCacheEntity>()
|
||||||
|
.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<ImageCacheEntity>()
|
||||||
|
.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<ImageCacheDto> GetByMd5sInternal(List<string> md5s, byte? platform)
|
||||||
|
{
|
||||||
|
var result = new List<ImageCacheDto>();
|
||||||
|
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<ImageCacheEntity>()
|
||||||
|
.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<string> NormalizeMd5s(IEnumerable<string> sourceUrlMd5s)
|
||||||
|
{
|
||||||
|
if (sourceUrlMd5s == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("sourceUrlMd5s");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<string>();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using MAX.Core.DependencyInjection;
|
||||||
|
using MAX.Core.Interfaces;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Store.Modules.CloudProduct
|
||||||
|
{
|
||||||
|
|
||||||
|
public class Startup : IStartup
|
||||||
|
{
|
||||||
|
public int Priority => IntPriority.Normal;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(IServiceRegistrar registrar)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Func<IServiceProvider, IFreeSql> KJDFsqlFactory = r =>
|
||||||
|
{
|
||||||
|
var config = r.GetRequiredService<IConfiguration>();
|
||||||
|
var connection = config.GetValue<string>("ImageCache:ConnectionString");
|
||||||
|
IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
||||||
|
.UseConnectionString(FreeSql.DataType.MySql, connection)
|
||||||
|
.UseAdoConnectionPool(true)
|
||||||
|
.Build();
|
||||||
|
return fsql;
|
||||||
|
};
|
||||||
|
registrar.RegisterSingleton<IFreeSql>("ImageCacheConnection", (s, k) =>
|
||||||
|
{
|
||||||
|
return KJDFsqlFactory(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Store.Framework\Store.Framework.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.ImageCache.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"ImageCache": {
|
||||||
|
"ConnectionString": "Server=192.168.0.200;Port=3306;Database=ImageCache;Uid=root;Pwd=123456;Allow User Variables=true"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Logs\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -17,6 +21,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Modules\CloudProduct\Store.CloudProduct.csproj" />
|
<ProjectReference Include="..\Modules\CloudProduct\Store.CloudProduct.csproj" />
|
||||||
|
<ProjectReference Include="..\Modules\Store.ImageCache\Store.ImageCache.csproj" />
|
||||||
<ProjectReference Include="..\Modules\Store.Users\Store.Users.csproj" />
|
<ProjectReference Include="..\Modules\Store.Users\Store.Users.csproj" />
|
||||||
<ProjectReference Include="..\Store.Framework\Store.Framework.csproj" />
|
<ProjectReference Include="..\Store.Framework\Store.Framework.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Server=11192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;",
|
"DefaultConnection": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;",
|
||||||
"KJDConnection": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;",
|
"KJDConnection": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;",
|
||||||
"ConnKJD": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;"
|
"ConnKJD": "Server=192.168.0.200;Port=3306;Database=mysql;Uid=root;Pwd=123456;"
|
||||||
//"KJDConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;",
|
//"KJDConnection": "Max Pool Size=512;Database='kjd';Data Source='nj-cynosdbmysql-grp-hl51pkfr.sql.tencentcdb.com';Port=27660;User Id='kjd';Password='Temukjd0625';Charset=utf8mb4;",
|
||||||
|
|||||||
+2
-1
@@ -5,6 +5,7 @@
|
|||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Modules/">
|
<Folder Name="/Modules/">
|
||||||
<Project Path="Modules/CloudProduct/Store.CloudProduct.csproj" />
|
<Project Path="Modules/CloudProduct/Store.CloudProduct.csproj" />
|
||||||
|
<Project Path="Modules/Store.ImageCache/Store.ImageCache.csproj" Id="53cc9cf1-62f9-4980-bad9-958bb3314370" />
|
||||||
<Project Path="Modules/Store.Shops.Contracts/Store.Shops.Contracts.csproj" Id="7d313823-f86a-450a-be29-8f915fc4b5b3" />
|
<Project Path="Modules/Store.Shops.Contracts/Store.Shops.Contracts.csproj" Id="7d313823-f86a-450a-be29-8f915fc4b5b3" />
|
||||||
<Project Path="Modules/Store.Shops/Store.Shops.csproj" Id="b1c06d62-9cfb-4e87-b495-7077dd0c088d" />
|
<Project Path="Modules/Store.Shops/Store.Shops.csproj" Id="b1c06d62-9cfb-4e87-b495-7077dd0c088d" />
|
||||||
<Project Path="Modules/Store.Users.Contracts/Store.Users.Contracts.csproj" Id="d4a8177b-503c-4205-96ce-da0e01c855db" />
|
<Project Path="Modules/Store.Users.Contracts/Store.Users.Contracts.csproj" Id="d4a8177b-503c-4205-96ce-da0e01c855db" />
|
||||||
@@ -15,6 +16,6 @@
|
|||||||
</Folder>
|
</Folder>
|
||||||
<Project Path="Store.API/Store.Api.csproj" />
|
<Project Path="Store.API/Store.Api.csproj" />
|
||||||
<Project Path="Store.Framework/Store.Framework.csproj" />
|
<Project Path="Store.Framework/Store.Framework.csproj" />
|
||||||
<Project Path="StoreHub.KJDWebService/StoreHub.KJDWebService.csproj" />
|
<Project Path="Store.KJDWebService/StoreHub.KJDWebService.csproj" />
|
||||||
<Project Path="StoreHub.Services/Store.Services.csproj" />
|
<Project Path="StoreHub.Services/Store.Services.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
Reference in new issue
Block a user