Skip to content

Commit e5548e1

Browse files
Copilotnnhy
andcommitted
改进 KvController 注释,简化 Get 返回(避免双重分配),修复 DecodeNullableValue 协议
Co-authored-by: nnhy <506367+nnhy@users.noreply.github.com>
1 parent deb846b commit e5548e1

2 files changed

Lines changed: 156 additions & 71 deletions

File tree

NewLife.NovaDb/Server/KvController.cs

Lines changed: 149 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ namespace NewLife.NovaDb.Server;
88
/// <remarks>
99
/// 控制器方法通过 Remoting RPC 暴露为远程接口。
1010
/// 路由格式:Kv/{方法名},如 Kv/Set、Kv/Get。
11-
/// 所有方法入参均为 IPacket,通过 SpanReader 直接读取二进制参数,跳过 JSON 序列化。
12-
/// 返回值也为 IPacket,通过 SpanWriter 写入 ArrayPacket,跳过 JSON 序列化。
13-
/// Remoting 框架检测到 IsPacketParameter/IsPacketReturn 后自动走原生二进制通道。
11+
/// 所有方法入参均为 IPacket(Remoting 检测到 IsPacketParameter=True 后直接传原始消息体),
12+
/// 通过 SpanReader 直接读取二进制参数,跳过 JSON 序列化。
13+
/// 返回值也为 IPacket(IsPacketReturn=True),通过 SpanWriter 写入 ArrayPacket,
14+
/// 或直接返回 KvStore 的 IOwnerPacket(网络层发送后自动释放回池)。
1415
/// 控制器实例由 Remoting 框架按请求创建,通过静态字段共享引擎。
16+
/// 二进制协议说明:
17+
/// EncodedString = EncodedInt(UTF8字节数) + UTF8字节
18+
/// EncodedInt = 变长整数(1-5 字节,最高位 1 表示后续还有字节)
19+
/// NullableBytes = EncodedInt(-1)=null / EncodedInt(0)=空 / EncodedInt(n)+n字节
1520
/// </remarks>
1621
internal class KvController : IApi
1722
{
@@ -35,9 +40,15 @@ internal class KvController : IApi
3540
return SharedServer?.GetKvStore(tableName);
3641
}
3742

38-
/// <summary>KV 设置键值对(IPacket 二进制协议)</summary>
39-
/// <param name="data">请求包:[tableName][key][value(nullable)][ttlSeconds]</param>
40-
/// <returns>响应包:[1B: 0=false, 1=true]</returns>
43+
/// <summary>KV 设置键值对</summary>
44+
/// <param name="data">
45+
/// 请求包:
46+
/// [EncodedString tableName] 表名
47+
/// [EncodedString key] 键
48+
/// [NullableBytes value] 值(null 或任意字节)
49+
/// [Int32 ttlSeconds] 过期秒数,0=永不过期(4字节小端)
50+
/// </param>
51+
/// <returns>响应包:[1B: 0=失败, 1=成功]</returns>
4152
public IPacket Set(IPacket data)
4253
{
4354
var (tableName, key, value, ttlSeconds) = KvPacket.DecodeSet(data);
@@ -49,22 +60,37 @@ public IPacket Set(IPacket data)
4960
return KvPacket.EncodeBoolean(true);
5061
}
5162

52-
/// <summary>KV 获取值(IPacket 二进制协议,跳过 Base64 与 JSON 开销)</summary>
53-
/// <param name="data">请求包:[tableName][key]</param>
54-
/// <returns>响应包:[1B 0=notfound] 或 [1B 1=found][value bytes]</returns>
63+
/// <summary>KV 获取值(跳过 Base64 与 JSON 开销,直接返回原始字节包)</summary>
64+
/// <param name="data">
65+
/// 请求包:
66+
/// [EncodedString tableName] 表名
67+
/// [EncodedString key] 键
68+
/// </param>
69+
/// <returns>
70+
/// 响应包:
71+
/// 键存在时返回存储的原始字节(ArrayPacket);
72+
/// 键不存在或存储未初始化时返回空包(Length=0)
73+
/// </returns>
5574
public IPacket Get(IPacket data)
5675
{
5776
var (tableName, key) = KvPacket.DecodeTableKey(data);
5877
var store = GetStore(tableName);
59-
if (store == null) return KvPacket.EncodeNullableValue(null);
78+
if (store == null) return KvPacket.EncodeEmpty();
6079

6180
using var pk = store.Get(key);
62-
return KvPacket.EncodeNullableValue(pk?.ReadBytes());
81+
if (pk == null) return KvPacket.EncodeEmpty();
82+
83+
// GetSpan() 直接读取底层缓冲区,避免 ReadBytes() 分配中间字节数组,再包装 IPacket 的双重分配
84+
return new ArrayPacket(pk.GetSpan().ToArray());
6385
}
6486

65-
/// <summary>KV 删除键(IPacket 二进制协议)</summary>
66-
/// <param name="data">请求包:[tableName][key]</param>
67-
/// <returns>响应包:[1B: 0=false, 1=true]</returns>
87+
/// <summary>KV 删除键</summary>
88+
/// <param name="data">
89+
/// 请求包:
90+
/// [EncodedString tableName] 表名
91+
/// [EncodedString key] 键
92+
/// </param>
93+
/// <returns>响应包:[1B: 0=键不存在/失败, 1=删除成功]</returns>
6894
public IPacket Delete(IPacket data)
6995
{
7096
var (tableName, key) = KvPacket.DecodeTableKey(data);
@@ -74,9 +100,13 @@ public IPacket Delete(IPacket data)
74100
return KvPacket.EncodeBoolean(store.Delete(key));
75101
}
76102

77-
/// <summary>KV 检查键是否存在(IPacket 二进制协议)</summary>
78-
/// <param name="data">请求包:[tableName][key]</param>
79-
/// <returns>响应包:[1B: 0=false, 1=true]</returns>
103+
/// <summary>KV 检查键是否存在</summary>
104+
/// <param name="data">
105+
/// 请求包:
106+
/// [EncodedString tableName] 表名
107+
/// [EncodedString key] 键
108+
/// </param>
109+
/// <returns>响应包:[1B: 0=不存在, 1=存在]</returns>
80110
public IPacket Exists(IPacket data)
81111
{
82112
var (tableName, key) = KvPacket.DecodeTableKey(data);
@@ -86,9 +116,13 @@ public IPacket Exists(IPacket data)
86116
return KvPacket.EncodeBoolean(store.Exists(key));
87117
}
88118

89-
/// <summary>按通配符模式删除键(IPacket 二进制协议)</summary>
90-
/// <param name="data">请求包:[tableName][pattern]</param>
91-
/// <returns>响应包:[4B Int32: 删除数量]</returns>
119+
/// <summary>按通配符模式删除键</summary>
120+
/// <param name="data">
121+
/// 请求包:
122+
/// [EncodedString tableName] 表名
123+
/// [EncodedString pattern] 通配符模式(* 匹配任意字符,? 匹配单个字符)
124+
/// </param>
125+
/// <returns>响应包:[Int32 删除数量](4字节小端)</returns>
92126
public IPacket DeleteByPattern(IPacket data)
93127
{
94128
var (tableName, pattern) = KvPacket.DecodeDeleteByPattern(data);
@@ -98,39 +132,57 @@ public IPacket DeleteByPattern(IPacket data)
98132
return KvPacket.EncodeInt32(store.DeleteByPattern(pattern));
99133
}
100134

101-
/// <summary>获取缓存项总数(IPacket 二进制协议)</summary>
102-
/// <param name="data">请求包:[tableName]</param>
103-
/// <returns>响应包:[4B Int32: 总数]</returns>
135+
/// <summary>获取缓存项总数</summary>
136+
/// <param name="data">
137+
/// 请求包:
138+
/// [EncodedString tableName] 表名
139+
/// </param>
140+
/// <returns>响应包:[Int32 总数](4字节小端)</returns>
104141
public IPacket GetCount(IPacket data)
105142
{
106143
var tableName = KvPacket.DecodeTableOnly(data);
107144
var store = GetStore(tableName);
108145
return KvPacket.EncodeInt32(store?.Count ?? 0);
109146
}
110147

111-
/// <summary>获取所有缓存键(IPacket 二进制协议)</summary>
112-
/// <param name="data">请求包:[tableName]</param>
113-
/// <returns>响应包:[4B count][key1...][keyN]</returns>
148+
/// <summary>获取所有缓存键</summary>
149+
/// <param name="data">
150+
/// 请求包:
151+
/// [EncodedString tableName] 表名
152+
/// </param>
153+
/// <returns>
154+
/// 响应包:
155+
/// [Int32 count] 键数量(4字节小端)
156+
/// [EncodedString key1] ... 各键字符串(count 个)
157+
/// </returns>
114158
public IPacket GetAllKeys(IPacket data)
115159
{
116160
var tableName = KvPacket.DecodeTableOnly(data);
117161
var store = GetStore(tableName);
118162
return KvPacket.EncodeStringArray(store?.GetAllKeys().ToArray() ?? []);
119163
}
120164

121-
/// <summary>清空所有缓存项(IPacket 二进制协议)</summary>
122-
/// <param name="data">请求包:[tableName]</param>
123-
/// <returns>空响应包</returns>
165+
/// <summary>清空所有缓存项</summary>
166+
/// <param name="data">
167+
/// 请求包:
168+
/// [EncodedString tableName] 表名
169+
/// </param>
170+
/// <returns>空响应包(Length=0)</returns>
124171
public IPacket Clear(IPacket data)
125172
{
126173
var tableName = KvPacket.DecodeTableOnly(data);
127174
GetStore(tableName)?.Clear();
128175
return KvPacket.EncodeEmpty();
129176
}
130177

131-
/// <summary>设置缓存项有效期(IPacket 二进制协议)</summary>
132-
/// <param name="data">请求包:[tableName][key][ttlSeconds]</param>
133-
/// <returns>响应包:[1B: 0=false, 1=true]</returns>
178+
/// <summary>设置缓存项有效期</summary>
179+
/// <param name="data">
180+
/// 请求包:
181+
/// [EncodedString tableName] 表名
182+
/// [EncodedString key] 键
183+
/// [Int32 ttlSeconds] 新的过期秒数(4字节小端)
184+
/// </param>
185+
/// <returns>响应包:[1B: 0=键不存在/失败, 1=设置成功]</returns>
134186
public IPacket SetExpire(IPacket data)
135187
{
136188
var (tableName, key, ttlSeconds) = KvPacket.DecodeSetExpire(data);
@@ -140,9 +192,16 @@ public IPacket SetExpire(IPacket data)
140192
return KvPacket.EncodeBoolean(store.SetExpiration(key, TimeSpan.FromSeconds(ttlSeconds)));
141193
}
142194

143-
/// <summary>获取缓存项剩余有效期(IPacket 二进制协议)</summary>
144-
/// <param name="data">请求包:[tableName][key]</param>
145-
/// <returns>响应包:[8B Double: TTL 秒数]</returns>
195+
/// <summary>获取缓存项剩余有效期</summary>
196+
/// <param name="data">
197+
/// 请求包:
198+
/// [EncodedString tableName] 表名
199+
/// [EncodedString key] 键
200+
/// </param>
201+
/// <returns>
202+
/// 响应包:[Double TTL秒数](8字节小端)
203+
/// 正数=剩余秒数,负数=键不存在或永不过期
204+
/// </returns>
146205
public IPacket GetExpire(IPacket data)
147206
{
148207
var (tableName, key) = KvPacket.DecodeTableKey(data);
@@ -152,9 +211,14 @@ public IPacket GetExpire(IPacket data)
152211
return KvPacket.EncodeDouble(store.GetTtl(key).TotalSeconds);
153212
}
154213

155-
/// <summary>原子递增(整数,IPacket 二进制协议)</summary>
156-
/// <param name="data">请求包:[tableName][key][Int64 delta]</param>
157-
/// <returns>响应包:[8B Int64: 更新后的值]</returns>
214+
/// <summary>原子递增(整数)</summary>
215+
/// <param name="data">
216+
/// 请求包:
217+
/// [EncodedString tableName] 表名
218+
/// [EncodedString key] 键
219+
/// [Int64 delta] 变化量(8字节小端,可为负数)
220+
/// </param>
221+
/// <returns>响应包:[Int64 更新后的值](8字节小端)</returns>
158222
public IPacket Increment(IPacket data)
159223
{
160224
var (tableName, key, delta) = KvPacket.DecodeIncrement(data);
@@ -164,9 +228,14 @@ public IPacket Increment(IPacket data)
164228
return KvPacket.EncodeInt64(store.Inc(key, delta));
165229
}
166230

167-
/// <summary>原子递增(浮点,IPacket 二进制协议)</summary>
168-
/// <param name="data">请求包:[tableName][key][Double delta]</param>
169-
/// <returns>响应包:[8B Double: 更新后的值]</returns>
231+
/// <summary>原子递增(浮点)</summary>
232+
/// <param name="data">
233+
/// 请求包:
234+
/// [EncodedString tableName] 表名
235+
/// [EncodedString key] 键
236+
/// [Double delta] 变化量(8字节小端,可为负数)
237+
/// </param>
238+
/// <returns>响应包:[Double 更新后的值](8字节小端)</returns>
170239
public IPacket IncrementDouble(IPacket data)
171240
{
172241
var (tableName, key, delta) = KvPacket.DecodeIncrementDouble(data);
@@ -176,9 +245,19 @@ public IPacket IncrementDouble(IPacket data)
176245
return KvPacket.EncodeDouble(store.IncDouble(key, delta));
177246
}
178247

179-
/// <summary>搜索匹配的键(IPacket 二进制协议)</summary>
180-
/// <param name="data">请求包:[tableName][pattern][offset][count]</param>
181-
/// <returns>响应包:[4B count][key1...][keyN]</returns>
248+
/// <summary>搜索匹配的键</summary>
249+
/// <param name="data">
250+
/// 请求包:
251+
/// [EncodedString tableName] 表名
252+
/// [EncodedString pattern] 搜索模式
253+
/// [Int32 offset] 偏移量(4字节小端)
254+
/// [Int32 count] 最大返回数量,-1=不限(4字节小端)
255+
/// </param>
256+
/// <returns>
257+
/// 响应包:
258+
/// [Int32 count] 匹配键数量(4字节小端)
259+
/// [EncodedString key1] ... 各键字符串(count 个)
260+
/// </returns>
182261
public IPacket Search(IPacket data)
183262
{
184263
var (tableName, pattern, offset, count) = KvPacket.DecodeSearch(data);
@@ -188,9 +267,21 @@ public IPacket Search(IPacket data)
188267
return KvPacket.EncodeStringArray(store.Search(pattern, offset, count).ToArray());
189268
}
190269

191-
/// <summary>批量获取键值对(IPacket 二进制协议,跳过 Base64 与 JSON 开销)</summary>
192-
/// <param name="data">请求包:[tableName][keyCount][key1...][keyN]</param>
193-
/// <returns>响应包:[count][key1][value1Flag][value1Len][value1]...[keyN][valueNFlag]</returns>
270+
/// <summary>批量获取键值对(跳过 Base64 与 JSON 开销)</summary>
271+
/// <param name="data">
272+
/// 请求包:
273+
/// [EncodedString tableName] 表名
274+
/// [Int32 keyCount] 键数量(4字节小端)
275+
/// [EncodedString key1] ... 各键字符串(keyCount 个)
276+
/// </param>
277+
/// <returns>
278+
/// 响应包:
279+
/// [Int32 count] 键数量(4字节小端,与请求一致)
280+
/// 对每个键,依次写入:
281+
/// [EncodedString key] 键字符串
282+
/// [1B flag] 0=键不存在/null, 1=有值
283+
/// [if flag=1: EncodedInt(valueLen)][valueLen 字节 value]
284+
/// </returns>
194285
public IPacket GetAll(IPacket data)
195286
{
196287
var (tableName, keys) = KvPacket.DecodeGetAll(data);
@@ -208,9 +299,17 @@ public IPacket GetAll(IPacket data)
208299
}
209300
}
210301

211-
/// <summary>批量设置键值对(IPacket 二进制协议)</summary>
212-
/// <param name="data">请求包:[tableName][ttlSeconds][count][key1][value1]...[keyN][valueN]</param>
213-
/// <returns>响应包:[4B Int32: 设置的键个数]</returns>
302+
/// <summary>批量设置键值对</summary>
303+
/// <param name="data">
304+
/// 请求包:
305+
/// [EncodedString tableName] 表名
306+
/// [Int32 ttlSeconds] 过期秒数,0=永不过期(4字节小端)
307+
/// [Int32 count] 键值对数量(4字节小端)
308+
/// 对每个键值对,依次写入:
309+
/// [EncodedString key] 键
310+
/// [NullableBytes value] 值(null 或任意字节)
311+
/// </param>
312+
/// <returns>响应包:[Int32 成功设置的键个数](4字节小端)</returns>
214313
public IPacket SetAll(IPacket data)
215314
{
216315
var (tableName, values, ttlSeconds) = KvPacket.DecodeSetAll(data);

NewLife.NovaDb/Server/KvPacket.cs

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -305,16 +305,8 @@ public static IPacket EncodeDouble(Double value)
305305
return new ArrayPacket(buf);
306306
}
307307

308-
/// <summary>编码可空字节数组响应(null → 1 字节 0x00;非 null → 1 字节 0x01 + 值字节)</summary>
309-
public static IPacket EncodeNullableValue(Byte[]? value)
310-
{
311-
if (value == null)
312-
return new ArrayPacket(new Byte[] { 0 });
313-
var result = new Byte[1 + value.Length];
314-
result[0] = 1;
315-
value.CopyTo(result, 1);
316-
return new ArrayPacket(result);
317-
}
308+
/// <summary>编码空响应(用于 Clear 等无返回值操作,以及 Get 未找到键时的空包)</summary>
309+
public static IPacket EncodeEmpty() => new ArrayPacket(new Byte[0]);
318310

319311
/// <summary>编码字符串数组响应(Int32 count + 每项 EncodedString)</summary>
320312
public static IPacket EncodeStringArray(String[] keys)
@@ -356,10 +348,10 @@ public static IPacket EncodeGetAllResponse(String[] keys, IDictionary<String, IO
356348
WriteString(ref writer, keyBytesArr[i]);
357349
if (data.TryGetValue(keys[i], out var pk) && pk != null)
358350
{
359-
var valueBytes = pk.ReadBytes();
351+
var valueSpan = pk.GetSpan();
360352
writer.WriteByte(1);
361-
writer.WriteEncodedInt(valueBytes.Length);
362-
writer.Write(valueBytes);
353+
writer.WriteEncodedInt(valueSpan.Length);
354+
writer.Write(valueSpan);
363355
}
364356
else
365357
{
@@ -369,9 +361,6 @@ public static IPacket EncodeGetAllResponse(String[] keys, IDictionary<String, IO
369361
return new ArrayPacket(buf, 0, writer.Position);
370362
}
371363

372-
/// <summary>编码空响应(用于 Clear 等无返回值操作)</summary>
373-
public static IPacket EncodeEmpty() => new ArrayPacket(new Byte[0]);
374-
375364
#endregion
376365

377366
#region 解码响应
@@ -400,14 +389,11 @@ public static Double DecodeDouble(IPacket? pk)
400389
return new SpanReader(pk).ReadDouble();
401390
}
402391

403-
/// <summary>解码可空字节数组响应(第一字节 0=null,1=有值)</summary>
392+
/// <summary>解码 Get 响应(空包=未找到;非空=存储的原始字节)</summary>
404393
public static Byte[]? DecodeNullableValue(IPacket? pk)
405394
{
406395
if (pk == null || pk.Length == 0) return null;
407-
var span = pk.GetSpan();
408-
if (span[0] == 0) return null;
409-
if (pk.Length == 1) return new Byte[0];
410-
return span[1..].ToArray();
396+
return pk.ReadBytes();
411397
}
412398

413399
/// <summary>解码字符串数组响应</summary>

0 commit comments

Comments
 (0)