NewLife/X

新增支持累加/递减并获取过期时间的功能

在 `Cache` 抽象类中新增四个方法:
- `IncrementWithTtl` 和 `DecrementWithTtl`,支持整数和浮点数操作。
- 方法返回累加/递减结果及剩余过期时间(TTL)。
- TTL 返回值:`-1` 表示永不过期,`-2` 表示键不存在。

在 `ICache` 接口中新增对应方法声明。
支持限流计数器等场景,远程实现可通过 Pipeline 优化。
猿人易 authored at 2025-12-06 11:08:47
53d2e0a
Tree
1 Parent(s) 83c1b10
Summary: 2 changed files with 107 additions and 0 deletions.
Modified +67 -0
Modified +40 -0
Modified +67 -0
diff --git a/NewLife.Core/Caching/Cache.cs b/NewLife.Core/Caching/Cache.cs
index 5cc7e17..a117f0e 100644
--- a/NewLife.Core/Caching/Cache.cs
+++ b/NewLife.Core/Caching/Cache.cs
@@ -302,6 +302,73 @@ public abstract class Cache : DisposeBase, ICache
         }
     }
 
+    /// <summary>累加并获取过期时间,原子操作</summary>
+    /// <remarks>
+    /// 适用于需要同时获取累加结果和剩余过期时间的场景,如限流计数器。
+    /// 远端实现(如 Redis)可重写此方法使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为累加后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    public virtual (Int64 Value, Int32 Ttl) IncrementWithTtl(String key, Int64 value = 1)
+    {
+        var result = Increment(key, value);
+        var expire = GetExpire(key);
+
+        // 转换过期时间:TimeSpan.Zero 表示永不过期(-1),负值表示不存在(-2)
+        var ttl = expire < TimeSpan.Zero ? -2 : (expire == TimeSpan.Zero ? -1 : (Int32)expire.TotalSeconds);
+
+        return (result, ttl);
+    }
+
+    /// <summary>累加并获取过期时间,原子操作(浮点)</summary>
+    /// <remarks>
+    /// 适用于需要同时获取累加结果和剩余过期时间的场景。
+    /// 远端实现(如 Redis)可重写此方法使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为累加后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    public virtual (Double Value, Int32 Ttl) IncrementWithTtl(String key, Double value)
+    {
+        var result = Increment(key, value);
+        var expire = GetExpire(key);
+
+        // 转换过期时间:TimeSpan.Zero 表示永不过期(-1),负值表示不存在(-2)
+        var ttl = expire < TimeSpan.Zero ? -2 : (expire == TimeSpan.Zero ? -1 : (Int32)expire.TotalSeconds);
+
+        return (result, ttl);
+    }
+
+    /// <summary>递减并获取过期时间,原子操作</summary>
+    /// <remarks>
+    /// 适用于需要同时获取递减结果和剩余过期时间的场景。
+    /// 远端实现(如 Redis)可重写此方法使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为递减后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    public virtual (Int64 Value, Int32 Ttl) DecrementWithTtl(String key, Int64 value = 1)
+    {
+        var result = Decrement(key, value);
+        var expire = GetExpire(key);
+
+        // 转换过期时间:TimeSpan.Zero 表示永不过期(-1),负值表示不存在(-2)
+        var ttl = expire < TimeSpan.Zero ? -2 : (expire == TimeSpan.Zero ? -1 : (Int32)expire.TotalSeconds);
+
+        return (result, ttl);
+    }
+
+    /// <summary>递减并获取过期时间,原子操作(浮点)</summary>
+    /// <remarks>
+    /// 适用于需要同时获取递减结果和剩余过期时间的场景。
+    /// 远端实现(如 Redis)可重写此方法使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为递减后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    public virtual (Double Value, Int32 Ttl) DecrementWithTtl(String key, Double value) => IncrementWithTtl(key, -value);
+
     /// <summary>搜索键</summary>
     /// <param name="pattern">匹配字符串。一般支持 *,远端实现可支持 ?</param>
     /// <param name="offset">开始行(分页偏移)</param>
Modified +40 -0
diff --git a/NewLife.Core/Caching/ICache.cs b/NewLife.Core/Caching/ICache.cs
index f051f20..d6b7ba3 100644
--- a/NewLife.Core/Caching/ICache.cs
+++ b/NewLife.Core/Caching/ICache.cs
@@ -199,6 +199,46 @@ public interface ICache
     /// <returns>更新后的值</returns>
     Double Decrement(String key, Double value);
 
+    /// <summary>累加并获取过期时间,原子操作</summary>
+    /// <remarks>
+    /// 适用于需要同时获取累加结果和剩余过期时间的场景,如限流计数器。
+    /// 远端实现(如 Redis)可使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为累加后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    (Int64 Value, Int32 Ttl) IncrementWithTtl(String key, Int64 value = 1);
+
+    /// <summary>累加并获取过期时间,原子操作(浮点)</summary>
+    /// <remarks>
+    /// 适用于需要同时获取累加结果和剩余过期时间的场景。
+    /// 远端实现(如 Redis)可使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为累加后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    (Double Value, Int32 Ttl) IncrementWithTtl(String key, Double value);
+
+    /// <summary>递减并获取过期时间,原子操作</summary>
+    /// <remarks>
+    /// 适用于需要同时获取递减结果和剩余过期时间的场景。
+    /// 远端实现(如 Redis)可使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为递减后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    (Int64 Value, Int32 Ttl) DecrementWithTtl(String key, Int64 value = 1);
+
+    /// <summary>递减并获取过期时间,原子操作(浮点)</summary>
+    /// <remarks>
+    /// 适用于需要同时获取递减结果和剩余过期时间的场景。
+    /// 远端实现(如 Redis)可使用 Pipeline 实现单次往返。
+    /// </remarks>
+    /// <param name="key">键</param>
+    /// <param name="value">变化量</param>
+    /// <returns>元组,Item1为递减后的值,Item2为剩余过期时间(秒),-1表示永不过期,-2表示键不存在</returns>
+    (Double Value, Int32 Ttl) DecrementWithTtl(String key, Double value);
+
     /// <summary>搜索匹配的键</summary>
     /// <param name="pattern">匹配字符串。一般支持 *,Redis 还支持 ?;在远端实现上请优先分页扫描</param>
     /// <param name="offset">开始偏移量。默认从0开始,Redis 对海量 key 搜索时需要分批</param>