@less-is-more/less-js 1.4.42 → 1.5.0-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@less-is-more/less-js",
3
- "version": "1.4.42",
3
+ "version": "1.5.0-2",
4
4
  "description": "Fast develop kit for nodejs",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/cache.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const Redis = require("./redis");
2
2
  const Param = require("./param");
3
3
  const zlib = require("zlib");
4
+ const Nas = require("./nas");
4
5
 
5
6
  module.exports = class Cache {
6
7
  /**
@@ -39,9 +40,10 @@ module.exports = class Cache {
39
40
  * @param {*} args 直接传arguments,用于拼接缓存key
40
41
  * @param {function} fn 没有缓存的实现
41
42
  * @param {boolean} zip 是否压缩
43
+ * @param {boolean} nas 是否使用Nas缓存
42
44
  * @returns 优先返回对象
43
45
  */
44
- static async auto(keyPrefix, timeSecond, args, fn, zip = false) {
46
+ static async auto(keyPrefix, timeSecond, args, fn, zip = false, nas = false) {
45
47
  let fullKey = keyPrefix;
46
48
  for (let i = 0; i < args.length; i++) {
47
49
  fullKey += ":" + args[i].toString();
@@ -50,7 +52,11 @@ module.exports = class Cache {
50
52
  let savedData = Cache._getLocal(fullKey);
51
53
  let hasLocal = false;
52
54
  if (Param.isBlank(savedData)) {
53
- savedData = await Redis.exec("get", fullKey);
55
+ if (nas) {
56
+ savedData = Nas.get(fullKey);
57
+ } else {
58
+ savedData = await Redis.exec("get", fullKey);
59
+ }
54
60
  if (!Param.isBlank(savedData)) {
55
61
  // 本地缓存1分钟
56
62
  Cache._setLocal(fullKey, savedData, 60000);
@@ -76,7 +82,12 @@ module.exports = class Cache {
76
82
  if (Cache._needZip(redisContent, zip)) {
77
83
  redisContent = Cache._zip(redisContent);
78
84
  }
79
- await Redis.exec("setex", fullKey, timeSecond + "", redisContent);
85
+ if (nas) {
86
+ Nas.set(fullKey, result, timeSecond);
87
+ } else {
88
+ await Redis.exec("setex", fullKey, timeSecond + "", redisContent);
89
+ }
90
+
80
91
  // 本地缓存1分钟
81
92
  Cache._setLocal(fullKey, redisContent, 60000);
82
93
  }
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ var Service = require("./service.js");
9
9
  var Oss = require("./oss.js");
10
10
  var GroupMessage = require("./group-message.js");
11
11
  var JsonKit = require("./json-kit.js");
12
+ var Nas = require("./nas.js");
12
13
  module.exports = {
13
14
  Ret,
14
15
  Router,
@@ -21,4 +22,5 @@ module.exports = {
21
22
  Oss,
22
23
  GroupMessage,
23
24
  JsonKit,
25
+ Nas,
24
26
  };
package/src/nas.js ADDED
@@ -0,0 +1,251 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
+
5
+ /**
6
+ * 本地硬盘缓存实现类
7
+ * 提供类似Redis的缓存功能,支持超时时间
8
+ * 适用于多服务器共享NAS存储
9
+ */
10
+ class Nas {
11
+ static _getCacheDir() {
12
+ let cacheDir = null;
13
+
14
+ // 如果提供了缓存目录参数,则使用该目录,否则使用默认目录
15
+ const defaultCacheDir = path.join(os.tmpdir(), "cache");
16
+ cacheDir =
17
+ cacheDir || (fs.existsSync("/cache") ? "/cache" : defaultCacheDir);
18
+
19
+ // 确保缓存目录存在
20
+ try {
21
+ if (!fs.existsSync(cacheDir)) {
22
+ fs.mkdirSync(cacheDir, { recursive: true });
23
+ }
24
+ } catch (e) {
25
+ console.error("创建缓存目录失败:", cacheDir, e);
26
+ }
27
+ return cacheDir;
28
+ }
29
+
30
+ /**
31
+ * 获取缓存值
32
+ * @param {string} key - 缓存键
33
+ * @returns {any} 缓存值,如果过期或不存在则返回null
34
+ */
35
+ static get(key) {
36
+ return Nas._loadFromDisk(key);
37
+ }
38
+
39
+ /**
40
+ * 获取缓存值,如果不存在则返回默认值
41
+ * @param {string} key - 缓存键
42
+ * @param {any} defaultValue - 默认值
43
+ * @returns {any} 缓存值或默认值
44
+ */
45
+ static getOrDefault(key, defaultValue) {
46
+ const value = Nas.get(key);
47
+ return value !== null ? value : defaultValue;
48
+ }
49
+
50
+ /**
51
+ * 设置缓存值
52
+ * @param {string} key - 缓存键
53
+ * @param {any} value - 缓存值
54
+ * @param {number} timeout - 超时时间(秒),-1表示永不过期
55
+ */
56
+ static set(key, value, timeout = -1) {
57
+ const expireTime = timeout === -1 ? -1 : Date.now() + timeout * 1000;
58
+ Nas._saveToDisk(key, value, expireTime);
59
+ }
60
+
61
+ /**
62
+ * 检查缓存是否存在且未过期
63
+ * @param {string} key - 缓存键
64
+ * @returns {boolean} 是否存在且未过期
65
+ */
66
+ static exists(key) {
67
+ return Nas.get(key) !== null;
68
+ }
69
+
70
+ /**
71
+ * 删除指定的缓存项
72
+ * @param {string} key - 缓存键
73
+ */
74
+ static del(key) {
75
+ Nas._deleteCacheFile(key);
76
+ }
77
+
78
+ /**
79
+ * 从硬盘加载缓存值
80
+ * @param {string} key - 缓存键
81
+ * @returns {any} 缓存值,如果过期或不存在则返回null
82
+ */
83
+ static _loadFromDisk(key) {
84
+ const filePath = Nas._getFilePath(key);
85
+
86
+ try {
87
+ if (!fs.existsSync(filePath)) {
88
+ return null;
89
+ }
90
+
91
+ // 读取缓存文件
92
+ const content = fs.readFileSync(filePath, "utf8");
93
+ const lines = content.split("\n");
94
+
95
+ if (lines.length >= 2) {
96
+ const expireTimeString = lines[0];
97
+ const value = lines.slice(1).join("\n"); // 处理值中可能包含换行符的情况
98
+
99
+ const expireTime = parseInt(expireTimeString);
100
+
101
+ if (!isNaN(expireTime)) {
102
+ if (expireTime === -1 || Date.now() < expireTime) {
103
+ // 文件未过期,返回值
104
+ return Buffer.from(value, "base64").toString("utf8");
105
+ } else {
106
+ // 文件已过期,删除文件
107
+ Nas._deleteCacheFile(key);
108
+ return null;
109
+ }
110
+ }
111
+ }
112
+ } catch (e) {
113
+ console.warn("读取缓存文件失败:", filePath, e);
114
+ }
115
+
116
+ return null;
117
+ }
118
+
119
+ /**
120
+ * 保存缓存值到硬盘
121
+ * @param {string} key - 缓存键
122
+ * @param {any} value - 缓存值
123
+ * @param {number} expireTime - 过期时间戳,-1表示永不过期
124
+ */
125
+ static _saveToDisk(key, value, expireTime) {
126
+ // 将值转换为字符串,如果是对象则转换为JSON字符串
127
+ const content = Buffer.from(value, "utf8").toString("base64");
128
+ const filePath = Nas._getFilePath(key);
129
+
130
+ try {
131
+ // 写入过期时间戳和值,用换行符分隔
132
+ const fileContent = `${expireTime}\n${content}`;
133
+ fs.writeFileSync(filePath, fileContent, "utf8");
134
+ } catch (e) {
135
+ console.error("保存缓存文件失败:", filePath, e);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 删除缓存文件
141
+ * @param {string} key - 缓存键
142
+ */
143
+ static _deleteCacheFile(key) {
144
+ const filePath = Nas._getFilePath(key);
145
+ try {
146
+ if (fs.existsSync(filePath)) {
147
+ fs.unlinkSync(filePath);
148
+ }
149
+ } catch (e) {
150
+ console.warn("删除缓存文件失败:", filePath, e);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 获取缓存文件路径
156
+ * @param {string} key - 缓存键
157
+ * @returns {string} 文件路径
158
+ */
159
+ static _getFilePath(key) {
160
+ // 为键生成安全的文件名
161
+ const safeKey = key.replace(/[^a-zA-Z0-9-_.]/g, "_");
162
+ return path.join(Nas._getCacheDir(), `${safeKey}.cache`);
163
+ }
164
+
165
+ /**
166
+ * 清空所有缓存
167
+ */
168
+ static flushAll() {
169
+ try {
170
+ const cacheDir = Nas._getCacheDir();
171
+ if (fs.existsSync(cacheDir) && fs.statSync(cacheDir).isDirectory()) {
172
+ const files = fs.readdirSync(cacheDir);
173
+ for (const file of files) {
174
+ if (file.endsWith(".cache")) {
175
+ const filePath = path.join(cacheDir, file);
176
+ fs.unlinkSync(filePath);
177
+ }
178
+ }
179
+ }
180
+ } catch (e) {
181
+ console.error("清空缓存失败", e);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * 设置指定键的过期时间
187
+ * @param {string} key - 缓存键
188
+ * @param {number} second - 过期时间(秒)
189
+ */
190
+ static expire(key, second) {
191
+ const currentValue = Nas.get(key);
192
+ if (currentValue !== null) {
193
+ const expireTime = Date.now() + second * 1000;
194
+ Nas._saveToDisk(key, currentValue, expireTime);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * 设置指定键的过期时间戳
200
+ * @param {string} key - 缓存键
201
+ * @param {number} expireTime - 过期时间戳(毫秒)
202
+ */
203
+ static expireAt(key, expireTime) {
204
+ const currentValue = Nas.get(key);
205
+ if (currentValue !== null) {
206
+ Nas._saveToDisk(key, currentValue, expireTime);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 获取指定键的剩余生存时间
212
+ * @param {string} key - 缓存键
213
+ * @returns {number} 剩余生存时间(秒),如果不存在或已过期则返回-1
214
+ */
215
+ static ttl(key) {
216
+ const filePath = Nas._getFilePath(key);
217
+
218
+ try {
219
+ if (!fs.existsSync(filePath)) {
220
+ return -1;
221
+ }
222
+
223
+ // 读取缓存文件
224
+ const content = fs.readFileSync(filePath, "utf8");
225
+ const lines = content.split("\n");
226
+
227
+ if (lines.length >= 1) {
228
+ const expireTimeString = lines[0];
229
+ const expireTime = parseInt(expireTimeString);
230
+
231
+ if (!isNaN(expireTime)) {
232
+ if (Date.now() < expireTime) {
233
+ // 文件未过期,计算剩余时间
234
+ const remainingTime = Math.floor((expireTime - Date.now()) / 1000);
235
+ return Math.max(0, remainingTime); // 确保返回非负值
236
+ } else {
237
+ // 文件已过期,删除文件
238
+ Nas._deleteCacheFile(key);
239
+ return -1;
240
+ }
241
+ }
242
+ }
243
+ } catch (e) {
244
+ console.warn("读取缓存文件失败:", filePath, e);
245
+ }
246
+
247
+ return -1;
248
+ }
249
+ }
250
+
251
+ module.exports = Nas;
@@ -0,0 +1,99 @@
1
+ const Nas = require("../src/nas");
2
+ const assert = require("assert");
3
+
4
+ describe("Nas Class Tests", function () {
5
+ it("should set and get a string value", function () {
6
+ Nas.set("testKey", "testValue");
7
+ const value = Nas.get("testKey");
8
+ assert.equal(value, "testValue");
9
+ });
10
+
11
+ it("should handle expiration correctly", function (done) {
12
+ Nas.set("expiringKey", "expiringValue", 1); // 1秒后过期
13
+
14
+ setTimeout(() => {
15
+ const expiredValue = Nas.get("expiringKey");
16
+ assert.equal(expiredValue, null);
17
+ done();
18
+ }, 1100);
19
+ });
20
+
21
+ it("should return null for expired keys", function () {
22
+ Nas.set("shortLivedKey", "value", 0.5); // 0.5秒后过期
23
+ setTimeout(() => {
24
+ const value = Nas.get("shortLivedKey");
25
+ assert.equal(value, null);
26
+ }, 1000);
27
+ });
28
+
29
+ it("should check if key exists", function () {
30
+ Nas.set("existKey", "value");
31
+ assert(Nas.exists("existKey"));
32
+ assert(Nas.exists("nonExistKey") == false);
33
+ });
34
+
35
+ it("should delete a key", function () {
36
+ Nas.set("deleteKey", "value");
37
+ assert(Nas.exists("deleteKey"));
38
+ Nas.del("deleteKey");
39
+ assert(Nas.exists("deleteKey") == false);
40
+ });
41
+
42
+ it("should return default value when key does not exist", function () {
43
+ const defaultValue = Nas.getOrDefault("nonExistingKey", "default");
44
+ assert.equal(defaultValue, "default");
45
+ });
46
+
47
+ it("should return actual value when key exists with getOrDefault", function () {
48
+ Nas.set("existingKey", "actualValue");
49
+ const value = Nas.getOrDefault("existingKey", "defaultValue");
50
+ assert.equal(value, "actualValue");
51
+ });
52
+
53
+ it("should handle expiration with expire method", function (done) {
54
+ Nas.set("expireTest", "value", 10); // 设置10秒后过期
55
+ const initialTtl = Nas.ttl("expireTest");
56
+ assert(initialTtl > 5); // 初始TTL应该大于5秒
57
+
58
+ Nas.expire("expireTest", 1); // 重新设置为1秒后过期
59
+ const updatedTtl = Nas.ttl("expireTest");
60
+ assert(updatedTtl <= 1); // TTL应该小于等于1秒
61
+
62
+ setTimeout(() => {
63
+ const expiredTtl = Nas.ttl("expireTest");
64
+ assert.equal(expiredTtl, -1); // 已过期,返回-1
65
+ done();
66
+ }, 1100);
67
+ });
68
+
69
+ it("should handle expiration with expireAt method", function (done) {
70
+ Nas.set("expireAtTest", "value");
71
+ const futureTime = Date.now() + 1000; // 1秒后
72
+ Nas.expireAt("expireAtTest", futureTime);
73
+
74
+ setTimeout(() => {
75
+ const expiredAtValue = Nas.get("expireAtTest");
76
+ assert(expiredAtValue == null); // 已过期,应该返回null
77
+ done();
78
+ }, 1100);
79
+ });
80
+
81
+ it("should flush all cache entries", function () {
82
+ Nas.set("flushTest1", "value1");
83
+ Nas.set("flushTest2", "value2");
84
+
85
+ assert.isTrue(Nas.exists("flushTest1"));
86
+ assert.isTrue(Nas.exists("flushTest2"));
87
+
88
+ Nas.flushAll();
89
+
90
+ assert.isFalse(Nas.exists("flushTest1"));
91
+ assert.isFalse(Nas.exists("flushTest2"));
92
+ });
93
+
94
+ it("should handle non-expiring keys (timeout = -1)", function () {
95
+ Nas.set("permanentKey", "permanentValue", -1);
96
+ const value = Nas.get("permanentKey");
97
+ assert.equal(value, "permanentValue");
98
+ });
99
+ });