using FreeRedis; using JetBrains.Annotations; using JiShe.CollectBus.FreeRedisProvider.Options; using JiShe.CollectBus.PagedResult; using JiShe.CollectBus.Serializer; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace JiShe.CollectBus.FreeRedisProvider { public class FreeRedisProvider : IFreeRedisProvider, ISingletonDependency { private readonly FreeRedisOptions _option; /// /// FreeRedis /// public FreeRedisProvider(IOptions options) { _option = options.Value; GetInstance(); } public RedisClient Instance { get; set; } = new(string.Empty); /// /// 获取 FreeRedis 客户端 /// /// public IRedisClient GetInstance() { var connectionString = $"{_option.Configuration},defaultdatabase={_option.DefaultDB}"; Instance = new RedisClient(connectionString); Instance.Serialize = obj => BusJsonSerializer.Serialize(obj); Instance.Deserialize = (json, type) => BusJsonSerializer.Deserialize(json, type); Instance.Notice += (s, e) => Trace.WriteLine(e.Log); return Instance; } //public async Task AddMeterZSetCacheData(string redisCacheKey, string redisCacheIndexKey, decimal score, T data) //{ // if (score < 0 || data == null || string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheIndexKey)) // { // throw new Exception($"{nameof(AddMeterZSetCacheData)} 参数异常,-101"); // } // // 生成唯一member标识 // var member = data.Serialize(); // // 计算score范围 // decimal dataScore = (long)score << 32; // //// 事务操作 // //using (var tran = FreeRedisProvider.Instance.Multi()) // //{ // // await tran.ZAddAsync(cacheKey, score,member); // // await tran.SAddAsync($"cat_index:{categoryId}", member); // // object[] ret = tran.Exec(); // //} // using (var pipe = Instance.StartPipe()) // { // pipe.ZAdd(redisCacheKey, dataScore, member); // pipe.SAdd(redisCacheIndexKey, member); // object[] ret = pipe.EndPipe(); // } // await Task.CompletedTask; //} //public async Task> GetMeterZSetPagedData( //string redisCacheKey, //string redisCacheIndexKey, //decimal score, //int pageSize = 10, //int pageIndex = 1) //{ // if (score < 0 || string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheIndexKey)) // { // throw new Exception($"{nameof(GetMeterZSetPagedData)} 参数异常,-101"); // } // // 计算score范围 // decimal minScore = (long)score << 32; // decimal maxScore = ((long)score + 1) << 32; // // 分页参数 // int start = (pageIndex - 1) * pageSize; // // 查询主数据 // var members = await Instance.ZRevRangeByScoreAsync( // redisCacheKey, // maxScore, // minScore, // offset: start, // count: pageSize // ); // if (members == null) // { // throw new Exception($"{nameof(GetMeterZSetPagedData)} 获取缓存的信息失败,第 {pageIndex + 1} 页数据未返回,-102"); // } // // 查询总数 // var total = await Instance.ZCountAsync(redisCacheKey, minScore, maxScore); // return new BusPagedResult // { // Items = members.Select(m => // BusJsonSerializer.Deserialize(m)!).ToList(), // TotalCount = total, // PageIndex = pageIndex, // PageSize = pageSize // }; //} ///// ///// 删除数据示例 ///// ///// ///// 分类 ///// ///// ///// //public async Task RemoveMeterZSetData( //string redisCacheKey, //string redisCacheIndexKey, //T data) //{ // // 查询需要删除的member // var members = await Instance.SMembersAsync(redisCacheIndexKey); // var target = members.FirstOrDefault(m => // BusJsonSerializer.Deserialize(m) == data);//泛型此处该如何处理? // if (target != null) // { // using (var trans = Instance.Multi()) // { // trans.ZRem(redisCacheKey, target); // trans.SRem(redisCacheIndexKey, target); // trans.Exec(); // } // } // await Task.CompletedTask; //} public async Task AddMeterZSetCacheData( string redisCacheKey, string redisCacheIndexKey, int categoryId, // 新增分类ID参数 T data, DateTimeOffset? timestamp = null) { // 参数校验增强 if (data == null || string.IsNullOrWhiteSpace(redisCacheKey) || string.IsNullOrWhiteSpace(redisCacheIndexKey)) { throw new ArgumentException("Invalid parameters"); } // 生成唯一member标识(带数据指纹) var member = $"{categoryId}:{Guid.NewGuid()}"; var serializedData = data.Serialize(); // 计算组合score(分类ID + 时间戳) var actualTimestamp = timestamp ?? DateTimeOffset.UtcNow; long scoreValue = ((long)categoryId << 32) | (uint)actualTimestamp.Ticks; //全局索引写入 long globalScore = actualTimestamp.ToUnixTimeMilliseconds(); // 使用事务保证原子性 using (var trans = Instance.Multi()) { // 主数据存储Hash trans.HSet(redisCacheKey, member, serializedData); // 排序索引使用ZSET trans.ZAdd($"{redisCacheKey}_scores", scoreValue, member); // 分类索引 trans.SAdd(redisCacheIndexKey, member); //全局索引 trans.ZAdd("global_data_all", globalScore, member); var results = trans.Exec(); if (results == null || results.Length <= 0) throw new Exception("Transaction failed"); } await Task.CompletedTask; } public async Task BatchAddMeterData( string redisCacheKey, string indexKey, IEnumerable items) where T : DeviceCacheBasicModel { const int BATCH_SIZE = 1000; // 每批1000条 var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); //foreach (var batch in items.Batch(BATCH_SIZE)) //{ // await semaphore.WaitAsync(); // _ = Task.Run(async () => // { // using (var pipe = FreeRedisProvider.Instance.StartPipe()) // { // foreach (var item in batch) // { // var member = $"{item.CategoryId}:{Guid.NewGuid()}"; // long score = ((long)item.CategoryId << 32) | (uint)item.Timestamp.Ticks; // // Hash主数据 // pipe.HSet(redisCacheKey, member, item.Data.Serialize()); // // 分类索引 // pipe.ZAdd($"{redisCacheKey}_scores", score, member); // // 全局索引 // pipe.ZAdd("global_data_all", item.Timestamp.ToUnixTimeMilliseconds(), member); // // 分类快速索引 // pipe.SAdd(indexKey, member); // } // pipe.EndPipe(); // } // semaphore.Release(); // }); //} await Task.CompletedTask; } public async Task UpdateMeterData( string redisCacheKey, string oldCategoryIndexKey, string newCategoryIndexKey, string memberId, // 唯一标识(格式:"分类ID:GUID") T newData, int? newCategoryId = null, DateTimeOffset? newTimestamp = null) { // 参数校验 if (string.IsNullOrWhiteSpace(memberId)) throw new ArgumentException("Invalid member ID"); var luaScript = @" local mainKey = KEYS[1] local scoreKey = KEYS[2] local oldIndex = KEYS[3] local newIndex = KEYS[4] local member = ARGV[1] local newData = ARGV[2] local newScore = ARGV[3] -- 校验旧数据是否存在 if redis.call('HEXISTS', mainKey, member) == 0 then return 0 end -- 更新主数据 redis.call('HSET', mainKey, member, newData) -- 处理分类变更 if newScore ~= '' then -- 删除旧索引 redis.call('SREM', oldIndex, member) -- 更新排序分数 redis.call('ZADD', scoreKey, newScore, member) -- 添加新索引 redis.call('SADD', newIndex, member) end return 1 "; // 计算新score(当分类或时间变化时) long? newScoreValue = null; if (newCategoryId.HasValue || newTimestamp.HasValue) { var parts = memberId.Split(':'); var oldCategoryId = int.Parse(parts[0]); var actualCategoryId = newCategoryId ?? oldCategoryId; var actualTimestamp = newTimestamp ?? DateTimeOffset.UtcNow; newScoreValue = ((long)actualCategoryId << 32) | (uint)actualTimestamp.Ticks; } var result = await Instance.EvalAsync(luaScript, new[] { redisCacheKey, $"{redisCacheKey}_scores", oldCategoryIndexKey, newCategoryIndexKey }, new[] { memberId, newData.Serialize(), newScoreValue?.ToString() ?? "" }); // 如果时间戳变化则更新全局索引 if (newTimestamp.HasValue) { long newGlobalScore = newTimestamp.Value.ToUnixTimeMilliseconds(); await Instance.ZAddAsync("global_data_all", newGlobalScore, memberId); } if ((int)result == 0) throw new KeyNotFoundException("指定数据不存在"); } public async Task> GetMeterZSetPagedData( string redisCacheKey, string redisCacheIndexKey, int categoryId, int pageSize = 10, int pageIndex = 1, bool descending = true) { // 计算score范围 long minScore = (long)categoryId << 32; long maxScore = ((long)categoryId + 1) << 32; // 分页参数计算 int start = (pageIndex - 1) * pageSize; // 获取排序后的member列表 var members = descending ? await Instance.ZRevRangeByScoreAsync( $"{redisCacheKey}_scores", maxScore, minScore, start, pageSize) : await Instance.ZRangeByScoreAsync( $"{redisCacheKey}_scores", minScore, maxScore, start, pageSize); // 批量获取实际数据 var dataTasks = members.Select(m => Instance.HGetAsync(redisCacheKey, m)).ToArray(); await Task.WhenAll(dataTasks); // 总数统计优化 var total = await Instance.ZCountAsync( $"{redisCacheKey}_scores", minScore, maxScore); return new BusPagedResult { Items = dataTasks.Select(t => t.Result).ToList(), TotalCount = total, PageIndex = pageIndex, PageSize = pageSize }; } public async Task RemoveMeterZSetData( string redisCacheKey, string redisCacheIndexKey, string uniqueId) // 改为基于唯一标识删除 { // 原子操作 var luaScript = @" local mainKey = KEYS[1] local scoreKey = KEYS[2] local indexKey = KEYS[3] local member = ARGV[1] redis.call('HDEL', mainKey, member) redis.call('ZREM', scoreKey, member) redis.call('SREM', indexKey, member) return 1 "; var keys = new[] { redisCacheKey, $"{redisCacheKey}_scores", redisCacheIndexKey }; var result = await Instance.EvalAsync(luaScript, keys, new[] { uniqueId }); if ((int)result != 1) throw new Exception("删除操作失败"); } public async Task> GetGlobalPagedData( string redisCacheKey, int pageSize = 10, long? lastScore = null, string lastMember = null, bool descending = true) { const string zsetKey = "global_data_all"; // 分页参数处理 var (startScore, excludeMember) = descending ? (lastScore ?? long.MaxValue, lastMember) : (lastScore ?? 0, lastMember); // 获取成员列表 string[] members; if (descending) { members = await Instance.ZRevRangeByScoreAsync( zsetKey, max: startScore, min: 0, offset: 0, count: pageSize + 1); } else { members = await Instance.ZRangeByScoreAsync( zsetKey, min: startScore, max: long.MaxValue, offset: 0, count: pageSize + 1); } // 处理分页结果 bool hasNext = members.Length > pageSize; var actualMembers = members.Take(pageSize).ToArray(); // 批量获取数据(优化版本) var dataTasks = actualMembers .Select(m => Instance.HGetAsync(redisCacheKey, m)) .ToArray(); await Task.WhenAll(dataTasks); // 获取下一页游标 (long? nextScore, string nextMember) = actualMembers.Any() ? await GetNextCursor(zsetKey, actualMembers.Last(), descending) : (null, null); return new GlobalPagedResult { Items = dataTasks.Select(t => t.Result).ToList(), HasNext = hasNext, NextScore = nextScore, NextMember = nextMember }; } private async Task<(long? score, string member)> GetNextCursor( string zsetKey, string lastMember, bool descending) { var score = await Instance.ZScoreAsync(zsetKey, lastMember); return (score.HasValue ? (long)score.Value : null, lastMember); } } }