@maiyunnet/kebab 5.3.1 → 6.1.0

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.
@@ -14,7 +14,6 @@ import * as lScan from '#kebab/lib/scan.js';
14
14
  import * as lSql from '#kebab/lib/sql.js';
15
15
  import * as lConsistent from '#kebab/lib/consistent.js';
16
16
  import * as lSsh from '#kebab/lib/ssh.js';
17
- import * as lJwt from '#kebab/lib/jwt.js';
18
17
  import * as lWs from '#kebab/lib/ws.js';
19
18
  import * as lS3 from '#kebab/lib/s3.js';
20
19
  import * as lZip from '#kebab/lib/zip.js';
@@ -86,6 +85,8 @@ export default class extends sCtr.Ctr {
86
85
  '<br>STATIC_PATH: ' + this._config.set.staticPath,
87
86
  '<br>STATIC_PATH_FULL: ' + this._config.set.staticPathFull,
88
87
  '<br>_internalUrl: ' + this._internalUrl,
88
+ '<br><br>ROOT_PATH: ' + this._config.const.rootPath,
89
+ '<br>ROOT_CWD: ' + kebab.ROOT_CWD,
89
90
  '<br><br>headers: ' + lText.htmlescape(JSON.stringify(this._headers)),
90
91
  '<br><br><b style="color: red;">Tips: The file can be deleted.</b>',
91
92
  '<br><br><b>Route (route.json):</b>',
@@ -124,6 +125,8 @@ export default class extends sCtr.Ctr {
124
125
  `<br><a href="${this._config.const.urlBase}test/mod-test">Click to see an example of a Test model</a> <a href="${this._config.const.urlBase}test/mod-test?s=pgsql">pgsql</a>`,
125
126
  `<br><a href="${this._config.const.urlBase}test/mod-split">View "test/mod-split"</a>`,
126
127
  `<br><a href="${this._config.const.urlBase}test/mod-insert">View "test/mod-insert"</a>`,
128
+ `<br><a href="${this._config.const.urlBase}test/mod-upsert">View "test/mod-upsert"</a>`,
129
+ `<br><a href="${this._config.const.urlBase}test/mod-update-list">View "test/mod-update-list"</a>`,
127
130
  '<br><br><b>Library test:</b>',
128
131
  `<br><br><b>Ai:</b>`,
129
132
  `<br><br><a href="${this._config.const.urlBase}test/ai">View "test/ai"</a>`,
@@ -182,15 +185,12 @@ export default class extends sCtr.Ctr {
182
185
  `<br><br><a href="${this._config.const.urlBase}test/sql?type=insert">View "test/sql?type=insert"</a> <a href="${this._config.const.urlBase}test/sql?type=insert&s=pgsql">pgsql</a>`,
183
186
  `<br><a href="${this._config.const.urlBase}test/sql?type=select">View "test/sql?type=select"</a> <a href="${this._config.const.urlBase}test/sql?type=select&s=pgsql">pgsql</a>`,
184
187
  `<br><a href="${this._config.const.urlBase}test/sql?type=update">View "test/sql?type=update"</a> <a href="${this._config.const.urlBase}test/sql?type=update&s=pgsql">pgsql</a>`,
188
+ `<br><a href="${this._config.const.urlBase}test/sql?type=upsert">View "test/sql?type=upsert"</a> <a href="${this._config.const.urlBase}test/sql?type=upsert&s=pgsql">pgsql</a>`,
185
189
  `<br><a href="${this._config.const.urlBase}test/sql?type=delete">View "test/sql?type=delete"</a> <a href="${this._config.const.urlBase}test/sql?type=delete&s=pgsql">pgsql</a>`,
186
190
  `<br><a href="${this._config.const.urlBase}test/sql?type=where">View "test/sql?type=where"</a> <a href="${this._config.const.urlBase}test/sql?type=where&s=pgsql">pgsql</a>`,
187
191
  `<br><a href="${this._config.const.urlBase}test/sql?type=having">View "test/sql?type=having"</a> <a href="${this._config.const.urlBase}test/sql?type=having&s=pgsql">pgsql</a>`,
188
192
  `<br><a href="${this._config.const.urlBase}test/sql?type=by">View "test/sql?type=by"</a> <a href="${this._config.const.urlBase}test/sql?type=by&s=pgsql">pgsql</a>`,
189
193
  `<br><a href="${this._config.const.urlBase}test/sql?type=field">View "test/sql?type=field"</a> <a href="${this._config.const.urlBase}test/sql?type=field&s=pgsql">pgsql</a>`,
190
- '<br><br><b>Jwt:</b>',
191
- `<br><br><a href="${this._config.const.urlBase}test/jwt">View "test/jwt"</a>`,
192
- `<br><a href="${this._config.const.urlBase}test/jwt?type=kv">View "test/jwt?type=kv"</a>`,
193
- `<br><a href="${this._config.const.urlBase}test/jwt?type=auth">View "test/jwt?type=auth" Header Authorization</a>`,
194
194
  '<br><br><b>Consistent:</b>',
195
195
  `<br><br><a href="${this._config.const.urlBase}test/consistent-hash">View "test/consistent-hash"</a>`,
196
196
  `<br><a href="${this._config.const.urlBase}test/consistent-distributed">View "test/consistent-distributed"</a>`,
@@ -281,7 +281,7 @@ Result:<pre id="result">Nothing.</pre>${this._getEnd()}`;
281
281
  'num2': ['> 0', '<= 100', [0, 'The num2 param must > 0.']],
282
282
  'reg': ['/^[A-CX-Z5-7]+$/', [0, 'The reg param is incorrect.']],
283
283
  'arr': [['a', 'x', 'hehe'], [0, 'The arr param is incorrect.']],
284
- 'type': [{ 'type': { 'a': 1, 'b': '' } }, [0, 'The reg param is incorrect']],
284
+ 'type': [{ 'type': { 'a': 1, 'b': '' } }, [0, 'The type param is incorrect']],
285
285
  'json': [{ 'type': { 'a': '1', 'b': [ { 'c': 1 } ] } }, [0, 'The type param is incorrect']],
286
286
  'json2': [{ 'type': { 'a': '1', 'b': 0, 'c?': { 'd': '1' } } }, [0, 'The json2 param is incorrect']],
287
287
  }</pre>`];
@@ -841,6 +841,127 @@ CREATE TABLE \`m_test_data_0\` (
841
841
  echo.push('<br><br>Result: ' + JSON.stringify(res));
842
842
  return echo.join('') + '<br><br>' + this._getEnd();
843
843
  }
844
+ async modUpsert() {
845
+ const echo = ['<b style="color: red;">In a production environment, please delete "mod/test.ts" and "mod/testdata.ts" files.</b>'];
846
+ const db = this._get['s'] === 'pgsql' ? lDb.get(this, {
847
+ 'service': lDb.ESERVICE.PGSQL,
848
+ }) : lDb.get(this);
849
+ // --- 1. 第一次 Upsert (插入新记录) ---
850
+ echo.push('<b>Test 1: Insert new record (default conflict field)</b><br>');
851
+ const test1 = mTest.getCreate(db, {
852
+ 'ctr': this,
853
+ 'pre': this._get['s'] === 'pgsql' ? 'm' : undefined,
854
+ });
855
+ test1.set({
856
+ 'name': lCore.random(4),
857
+ 'point': { 'x': 10, 'y': 10 },
858
+ 'time_add': lTime.stamp(),
859
+ });
860
+ const res1 = await test1.upsert('name');
861
+ echo.push(`<pre>const test1 = mTest.getCreate(db);
862
+ test1.set({
863
+ 'name': '${test1.name}',
864
+ 'point': { 'x': 10, 'y': 10 },
865
+ 'time_add': ${test1.time_add},
866
+ });
867
+ const res1 = await test1.upsert('name');
868
+ </pre>Result: ${res1}<br><br>`);
869
+ let stmt = await db.query(this._get['s'] === 'pgsql' ?
870
+ `SELECT * FROM "m"."test" WHERE "token" LIKE \'test_%\' ORDER BY "id" ASC;` :
871
+ 'SELECT * FROM `m_test` WHERE `token` LIKE \'test_%\' ORDER BY `id` ASC;');
872
+ this._dbTable(stmt, echo);
873
+ // --- 2. 第二次 Upsert (更新已有记录) ---
874
+ echo.push('<br><b>Test 2: Update existing record (same name)</b><br>');
875
+ const test2 = mTest.getCreate(db, {
876
+ 'ctr': this,
877
+ 'pre': this._get['s'] === 'pgsql' ? 'm' : undefined,
878
+ });
879
+ test2.set({
880
+ 'name': test1.name, // --- 相同的 name ---
881
+ 'point': { 'x': 20, 'y': 20 },
882
+ 'time_add': lTime.stamp(),
883
+ });
884
+ const res2 = await test2.upsert('name');
885
+ echo.push(`<pre>const test2 = mTest.getCreate(db);
886
+ test2.set({
887
+ 'name': '${test1.name}',
888
+ 'point': { 'x': 20, 'y': 20 },
889
+ 'time_add': ${test2.time_add},
890
+ });
891
+ const res2 = await test2.upsert('name');</pre>Result: ${res2}<br><br>`);
892
+ stmt = await db.query(this._get['s'] === 'pgsql' ?
893
+ `SELECT * FROM "m"."test" WHERE "token" LIKE \'test_%\' ORDER BY "id" ASC;` :
894
+ 'SELECT * FROM `m_test` WHERE `token` LIKE \'test_%\' ORDER BY `id` ASC;');
895
+ this._dbTable(stmt, echo);
896
+ // --- 清理测试数据 ---
897
+ await mTest.removeByWhere(db, [
898
+ ['token', 'LIKE', 'test_%']
899
+ ], {
900
+ 'ctr': this,
901
+ 'pre': this._get['s'] === 'pgsql' ? 'm' : undefined,
902
+ });
903
+ return echo.join('') + '<br>' + this._getEnd();
904
+ }
905
+ async modUpdateList() {
906
+ const echo = ['<b style="color: red;">In a production environment, please delete "mod/test.ts" and "mod/testdata.ts" files.</b>'];
907
+ const db = this._get['s'] === 'pgsql' ? lDb.get(this, {
908
+ 'service': lDb.ESERVICE.PGSQL,
909
+ }) : lDb.get(this);
910
+ // --- 准备数据 ---
911
+ const time = lTime.stamp();
912
+ const data = [];
913
+ for (let i = 0; i < 5; ++i) {
914
+ data.push({
915
+ 'name': 'ul_' + i,
916
+ 'token': 'ul_token_' + i,
917
+ 'point': { 'x': i, 'y': i },
918
+ 'time_add': time,
919
+ });
920
+ }
921
+ // --- 插入数据 ---
922
+ await mTest.insert(db, ['name', 'token', 'point', 'time_add'], data.map(item => [item.name, item.token, item.point, item.time_add]), {
923
+ 'ctr': this,
924
+ 'pre': this._get['s'] === 'pgsql' ? 'm' : undefined,
925
+ });
926
+ echo.push('<br><br>Inserted 5 rows.<br><br>');
927
+ // --- 查询插入的数据 ---
928
+ let stmt = await db.query(this._get['s'] === 'pgsql' ?
929
+ `SELECT * FROM "m"."test" WHERE "token" LIKE 'ul_token_%' ORDER BY "id" ASC;` :
930
+ `SELECT * FROM \`m_test\` WHERE \`token\` LIKE 'ul_token_%' ORDER BY \`id\` ASC;`);
931
+ this._dbTable(stmt, echo);
932
+ // --- 构造批量更新数据 ---
933
+ // --- 更新 name 和 point ---
934
+ const updateData = [];
935
+ if (stmt.rows) {
936
+ for (let i = 0; i < stmt.rows.length; ++i) {
937
+ const row = stmt.rows[i];
938
+ updateData.push({
939
+ 'id': row.id,
940
+ 'name': 'up_' + i,
941
+ 'point': { 'x': i + 10, 'y': i + 10 }
942
+ });
943
+ }
944
+ }
945
+ // --- 执行批量更新 ---
946
+ const res = await mTest.updateList(db, updateData, 'id', {
947
+ 'ctr': this,
948
+ 'pre': this._get['s'] === 'pgsql' ? 'm' : undefined,
949
+ });
950
+ echo.push(`<br><b>updateList result:</b> ${lText.stringifyJson(res)}<br><br>`);
951
+ // --- 查询更新后的数据 ---
952
+ stmt = await db.query(this._get['s'] === 'pgsql' ?
953
+ `SELECT * FROM "m"."test" WHERE "token" LIKE 'ul_token_%' ORDER BY "id" ASC;` :
954
+ `SELECT * FROM \`m_test\` WHERE \`token\` LIKE 'ul_token_%' ORDER BY \`id\` ASC;`);
955
+ this._dbTable(stmt, echo);
956
+ // --- 清理数据 ---
957
+ await mTest.removeByWhere(db, [
958
+ ['token', 'LIKE', 'ul_token_%']
959
+ ], {
960
+ 'ctr': this,
961
+ 'pre': this._get['s'] === 'pgsql' ? 'm' : undefined,
962
+ });
963
+ return echo.join('') + '<br>' + this._getEnd();
964
+ }
844
965
  captchaFastbuild() {
845
966
  return lCaptcha.get(400, 100).getBuffer();
846
967
  }
@@ -1089,7 +1210,7 @@ for (let i = 0; i < 30000; ++i) {
1089
1210
  const echo = [];
1090
1211
  echo.push('<table style="width: 100%;">');
1091
1212
  if (list) {
1092
- echo.push('<tr><th>TIME</th><th>UNIX</th><th>URL</th><th>COOKIE</th><th>SESSION</th><th>JWT</th><th>USER_AGENT</th><th>REALIP</th><th>CLIENTIP</th><th>MESSAGE</th></tr>');
1213
+ echo.push('<tr><th>TIME</th><th>UNIX</th><th>URL</th><th>COOKIE</th><th>SESSION</th><th>USER_AGENT</th><th>REALIP</th><th>CLIENTIP</th><th>OS</th><th>PROCESS</th><th>MESSAGE</th></tr>');
1093
1214
  for (const row of list) {
1094
1215
  echo.push('<tr>');
1095
1216
  for (const item of row) {
@@ -1766,7 +1887,7 @@ lCore.setCookie(this, 'test10', 'httponly', {
1766
1887
  });</pre>
1767
1888
  headers: <pre>${JSON.stringify(res.headers, null, 4)}</pre>
1768
1889
  content: <pre>${(await res.getContent())?.toString() ?? 'null'}</pre>
1769
- error: ${JSON.stringify(res.error)}</pre>`);
1890
+ error: ${JSON.stringify(res.error)}`);
1770
1891
  return echo.join('') + this._getEnd();
1771
1892
  }
1772
1893
  async netFollow() {
@@ -2192,65 +2313,6 @@ Result:<pre id="result">Nothing.</pre>`);
2192
2313
  }
2193
2314
  }
2194
2315
  }
2195
- async jwt() {
2196
- const retur = [];
2197
- if (!(this._checkInput(this._get, {
2198
- 'type': [['', 'kv', 'auth'], [0, 'Bad request.']]
2199
- }, retur))) {
2200
- return retur;
2201
- }
2202
- const echo = ['<pre>'];
2203
- let link = undefined;
2204
- if (this._get['type'] === 'kv') {
2205
- link = lKv.get(this);
2206
- echo.push('link = lKv.get(this);\n');
2207
- }
2208
- const origin = lJwt.getOrigin(this);
2209
- echo.push(`const origin = lJwt.getOrigin(this);
2210
- JSON.stringify(origin);</pre>`);
2211
- echo.push(JSON.stringify(origin));
2212
- // --- 创建 jwt 对象 ---
2213
- const jwt = await lJwt.get(this, {}, link);
2214
- echo.push(`<pre>const jwt = lJwt.get(this, {}, ${link ? 'link' : 'undefined'});
2215
- JSON.stringify(this._jwt);</pre>`);
2216
- echo.push(JSON.stringify(this._jwt));
2217
- this._jwt['test'] = 'a';
2218
- const value = jwt.renew();
2219
- echo.push(`<pre>this._jwt['test'] = 'a';
2220
- const value = jwt.renew();
2221
- JSON.stringify(this._jwt);</pre>`);
2222
- echo.push(JSON.stringify(this._jwt));
2223
- echo.push(`<pre>JSON.stringify(value);</pre>`);
2224
- echo.push(JSON.stringify(value));
2225
- const token = this._jwt['token'];
2226
- const rtn = await jwt.destory();
2227
- echo.push(`<pre>const token = this._jwt['token'];
2228
- const rtn = await jwt.destory();
2229
- JSON.stringify(rtn);</pre>`);
2230
- echo.push(JSON.stringify(rtn));
2231
- echo.push('<pre>JSON.stringify(this._jwt);</pre>');
2232
- echo.push(JSON.stringify(this._jwt));
2233
- const rtn2 = await lJwt.decode(this, origin, link);
2234
- echo.push(`<pre>const rtn2 = await lJwt.decode(this, origin, ${link ? 'link' : 'undefined'});
2235
- JSON.stringify(rtn2);</pre>`);
2236
- echo.push(JSON.stringify(rtn2));
2237
- if (this._get['type'] === 'auth') {
2238
- echo.push(`<br><br><input type="button" value="Post with header" onclick="document.getElementById('result').innerText='Waiting...';fetch('${this._config.const.urlBase}test/jwt1',{method:'POST',credentials:'omit',headers:{'Authorization':document.getElementById('_auth').innerText,'content-type':'application/x-www-form-urlencoded'},body:'key=val'}).then(function(r){return r.json();}).then(function(j){document.getElementById('result').innerText=j.txt;});"><input type='button' value="Post without header" style="margin-left: 10px;" onclick="document.getElementById('result').innerText='Waiting...';fetch('${this._config.const.urlBase}test/jwt1',{method:'POST',credentials:'omit',headers:{'content-type':'application/x-www-form-urlencoded'},body:'key=val'}).then(function(r){return r.json();}).then(function(j){document.getElementById('result').innerText=j.txt;});"><br><br>
2239
- Token: <span id="token">${token}</span><br>
2240
- Post Authorization header: <span id="_auth">Bearer ${origin}</span><br><br>
2241
- Result:<pre id="result">Nothing.</pre>`);
2242
- }
2243
- else {
2244
- echo.push('<br><br>');
2245
- }
2246
- return echo.join('') + this._getEnd();
2247
- }
2248
- async jwt1() {
2249
- await lJwt.get(this, {
2250
- 'auth': true
2251
- });
2252
- return [1, { 'txt': JSON.stringify(this._jwt) }];
2253
- }
2254
2316
  sql() {
2255
2317
  const echo = [];
2256
2318
  let sql = this._get['s'] === 'pgsql' ? lSql.get('test', {
@@ -2292,18 +2354,10 @@ Result:<pre id="result">Nothing.</pre>`);
2292
2354
  s = sql.insert('geo').values(['name', 'point', 'point2', 'polygon', 'json'], [
2293
2355
  [
2294
2356
  'POINT A', ['ST_POINTFROMTEXT(?)', ['POINT(122.147775 30.625015)']], { 'x': 1, 'y': 1 }, [
2295
- [
2296
- { 'x': 1, 'y': 1 },
2297
- { 'x': 2, 'y': 2 },
2298
- { 'x': 3, 'y': 3 },
2299
- { 'x': 1, 'y': 1 }
2300
- ],
2301
- [
2302
- { 'x': 6, 'y': 1 },
2303
- { 'x': 7, 'y': 2 },
2304
- { 'x': 8, 'y': 3 },
2305
- { 'x': 6, 'y': 1 }
2306
- ]
2357
+ { 'x': 1, 'y': 1 },
2358
+ { 'x': 2, 'y': 2 },
2359
+ { 'x': 3, 'y': 3 },
2360
+ { 'x': 1, 'y': 1 }
2307
2361
  ],
2308
2362
  { 'x': { 'y': 'ghi' } }
2309
2363
  ],
@@ -2315,18 +2369,10 @@ Result:<pre id="result">Nothing.</pre>`);
2315
2369
  echo.push(`<pre>sql.insert('geo').values(['name', 'point', 'point2', 'polygon', 'json'], [
2316
2370
  [
2317
2371
  'POINT A', ['ST_POINTFROMTEXT(?)', ['POINT(122.147775 30.625015)']], { 'x': 1, 'y': 1 }, [
2318
- [
2319
- { 'x': 1, 'y': 1 },
2320
- { 'x': 2, 'y': 2 },
2321
- { 'x': 3, 'y': 3 },
2322
- { 'x': 1, 'y': 1 }
2323
- ],
2324
- [
2325
- { 'x': 6, 'y': 1 },
2326
- { 'x': 7, 'y': 2 },
2327
- { 'x': 8, 'y': 3 },
2328
- { 'x': 6, 'y': 1 }
2329
- ]
2372
+ { 'x': 1, 'y': 1 },
2373
+ { 'x': 2, 'y': 2 },
2374
+ { 'x': 3, 'y': 3 },
2375
+ { 'x': 1, 'y': 1 }
2330
2376
  ],
2331
2377
  { 'x': { 'y': 'ghi' } }
2332
2378
  ],
@@ -2438,6 +2484,37 @@ Result:<pre id="result">Nothing.</pre>`);
2438
2484
  echo.push(`<pre>sql.update('json', { 'json1': { 'key': 'val', 'key2': 'val2' }, 'json2': [ { 'k1': 'v1' }, { 'k2': 'v2' } ], 'json3': { 'x': 1, 'y': 2 }, 'json4': [], 'json5': {} }).where({ 'id': 1 });</pre>
2439
2485
  <b>getSql() :</b> ${s}<br>
2440
2486
  <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
2487
+ <b>format() :</b> ${sql.format(s, sd)}`);
2488
+ break;
2489
+ }
2490
+ case 'upsert': {
2491
+ // --- 对象形式 ---
2492
+ let s = sql.insert('user').values({ 'id': 1, 'name': 'Bob', 'age': 25 }).upsert({ 'name': 'Updated Bob', 'age': 26 }).getSql();
2493
+ let sd = sql.getData();
2494
+ echo.push(`<pre>sql.insert('user').values({ 'id': 1, 'name': 'Bob', 'age': 25 }).upsert({ 'name': 'Updated Bob', 'age': 26 });</pre>
2495
+ <b>getSql() :</b> ${s}<br>
2496
+ <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
2497
+ <b>format() :</b> ${sql.format(s, sd)}<hr>`);
2498
+ // --- 数组形式 ---
2499
+ s = sql.insert('user').values(['id', 'name', 'age'], [1, 'Alice', 30]).upsert({ 'name': 'Updated Alice', 'age': 31 }).getSql();
2500
+ sd = sql.getData();
2501
+ echo.push(`<pre>sql.insert('user').values(['id', 'name', 'age'], [1, 'Alice', 30]).upsert({ 'name': 'Updated Alice', 'age': 31 });</pre>
2502
+ <b>getSql() :</b> ${s}<br>
2503
+ <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
2504
+ <b>format() :</b> ${sql.format(s, sd)}<hr>`);
2505
+ // --- 列引用 ---
2506
+ s = sql.insert('user').values({ 'id': 1, 'name': 'Charlie', 'age': 35 }).upsert([{ 'name': 'abc' }, ['age', '+', lSql.column('age')]]).getSql();
2507
+ sd = sql.getData();
2508
+ echo.push(`<pre>sql.insert('user').values({ 'id': 1, 'name': 'Charlie', 'age': 35 }).upsert([ { 'name': 'abc' }, ['age', '+', lSql.column('age')] ]);</pre>
2509
+ <b>getSql() :</b> ${s}<br>
2510
+ <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
2511
+ <b>format() :</b> ${sql.format(s, sd)}<hr>`);
2512
+ // --- 多字段 ---
2513
+ s = sql.insert('user').values({ 'id': 1, 'name': 'Dave', 'age': 40, 'city': 'Beijing' }).upsert({ 'name': 'Updated Dave', 'age': 41, 'city': 'Shanghai' }).getSql();
2514
+ sd = sql.getData();
2515
+ echo.push(`<pre>sql.insert('user').values({ 'id': 1, 'name': 'Dave', 'age': 40, 'city': 'Beijing' }).upsert({ 'name': 'Updated Dave', 'age': 41, 'city': 'Shanghai' });</pre>
2516
+ <b>getSql() :</b> ${s}<br>
2517
+ <b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
2441
2518
  <b>format() :</b> ${sql.format(s, sd)}`);
2442
2519
  break;
2443
2520
  }
@@ -2602,7 +2679,7 @@ Result:<pre id="result">Nothing.</pre>`);
2602
2679
  echo.push(`<pre>sql.field('SUM(num) all');</pre>` + sql.field('SUM(num) all'));
2603
2680
  echo.push(`<pre>sql.field('SUM(x.num) all');</pre>` + sql.field('SUM(x.num) all'));
2604
2681
  echo.push(`<pre>sql.field('SUM(x.\`num\`) all');</pre>` + sql.field('SUM(x.`num`) all'));
2605
- echo.push(`<pre>sql.field('FROM_UNIXTIME(time, \\'%Y-%m-%d\\') time');</pre>` + sql.field('FROM_UNIXTIME(time, \'%Y-%m-%d\') time'));
2682
+ echo.push(`<pre>sql.field('FROM_UNIXTIME(time, \\'%Y-%m-%d\\') time');</pre>` + sql.field(`FROM_UNIXTIME(time, '%Y-%m-%d') time`));
2606
2683
  echo.push(`<pre>sql.field('(6371 * ACOS(COS(RADIANS(31.239845)) * COS(RADIANS(lat)) * COS(RADIANS(\`lng\`) - RADIANS(121.499662)) + SIN(RADIANS(31.239845)) * SIN(RADIANS(\`lat\`))))');</pre>` + sql.field('(6371 * ACOS(COS(RADIANS(31.239845)) * COS(RADIANS(lat)) * COS(RADIANS(`lng`) - RADIANS(121.499662)) + SIN(RADIANS(31.239845)) * SIN(RADIANS(`lat`))))'));
2607
2684
  echo.push(`<pre>sql.field('MATCH(name_sc, name_tc) AGAINST("ok") tmp'));</pre>` + sql.field('MATCH(name_sc, name_tc) AGAINST("ok") tmp'));
2608
2685
  echo.push(`<pre>sql.field('a\\'bc');</pre>` + sql.field('a\'bc'));
@@ -3094,7 +3171,7 @@ rtn.push(reader.readBCDString());</pre>${JSON.stringify(rtn)}`);
3094
3171
  // --- 注意,这个只是演示,你实际需要在 ind 目录中创建计划任务 ---
3095
3172
  // --- 并用 --ind 单线程模式运行 ---
3096
3173
  const echo = [];
3097
- let rtn = await lCron.regular({
3174
+ const rtn = await lCron.regular({
3098
3175
  'name': 'test',
3099
3176
  'rule': '40 * * * *',
3100
3177
  'callback': (date, immediate) => {
package/lib/jwt.d.ts DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * Project: Kebab, User: JianSuoQiYue
3
- * Date: 2023-1-31 20:34:47
4
- * Last: 2023-1-31 20:34:47, 2023-12-8 13:55:09, 2025-11-6 16:22:06
5
- */
6
- import * as kebab from '#kebab/index.js';
7
- import * as lKv from '#kebab/lib/kv.js';
8
- import * as sCtr from '#kebab/sys/ctr.js';
9
- export interface IOptions {
10
- 'name'?: string;
11
- 'ttl'?: number;
12
- 'ssl'?: boolean;
13
- 'secret'?: string;
14
- 'auth'?: boolean;
15
- }
16
- export declare class Jwt {
17
- /** --- Kv --- */
18
- private _link?;
19
- /** --- 在前端或 Kv 中储存的名前缀 --- */
20
- private _name;
21
- /** --- 有效期 --- */
22
- private _ttl;
23
- /** --- cookie 模式时是否仅支持 SSL --- */
24
- private _ssl;
25
- /** --- 验证密钥 --- */
26
- private _secret;
27
- /** --- 是否从头部读取 --- */
28
- private _auth;
29
- /** --- ctr 对象 --- */
30
- private _ctr;
31
- /**
32
- * --- 初始化函数,相当于 construct ---
33
- * @param ctr 模型实例
34
- * @param link Kv 或 Db 实例
35
- * @param auth 设为 true 则优先从头 Authorization 或 post _auth 值读取 token
36
- * @param opt 选项
37
- */
38
- init(ctr: sCtr.Ctr, opt?: IOptions, link?: lKv.Kv): Promise<boolean>;
39
- /**
40
- * --- 将 _jwt 数据封装并返回(创建新的或者续期老的 token),默认会同时设置一个 cookie(data 值会自动设置 token、exp) ---
41
- */
42
- renew(): string;
43
- /**
44
- * --- 清除 cookie,仅仅清除 cookie,jwt 并不会失效 ---
45
- */
46
- clearCookie(): void;
47
- /**
48
- * --- 销毁 jwt,其实就是将 token block 信息写入 redis,如果没有 redis 则不能销毁,返回数组代表销毁成功的 token 和原 exp,否则失败返回 false ---
49
- */
50
- destory(): Promise<{
51
- token: string;
52
- exp: number;
53
- } | boolean>;
54
- }
55
- /**
56
- * --- 获取 jwt 原始字符串,不保证有效 ---
57
- */
58
- export declare function getOrigin(ctr: sCtr.Ctr, name?: string, auth?: boolean): string;
59
- /**
60
- * --- decode ---
61
- * 不传入 link 的话,将不做 block 有效校验,只做本身的 exp 有效校验
62
- */
63
- export declare function decode(ctr: sCtr.Ctr, val: string, link?: lKv.Kv, name?: string, secret?: string): Promise<Record<string, kebab.DbValue> | false>;
64
- /**
65
- * --- 仅往 redis 写禁止相关 token 的数据,一般用于异步通知时在异处的服务器来调用的 ---
66
- */
67
- export declare function block(ctr: sCtr.Ctr, token: string, exp: number, link: lKv.Kv, name?: string): Promise<boolean>;
68
- /**
69
- * @param ctr 模型实例
70
- * @param opt name, ttl, ssl, secret, auth: false, true 则优先从头 Authorization 或 post _auth 值读取 token
71
- * @param link 实例
72
- */
73
- export declare function get(ctr: sCtr.Ctr, opt?: IOptions, link?: lKv.Kv): Promise<Jwt>;
package/lib/jwt.js DELETED
@@ -1,226 +0,0 @@
1
- /**
2
- * Project: Kebab, User: JianSuoQiYue
3
- * Date: 2023-1-31 20:34:47
4
- * Last: 2023-1-31 20:34:47, 2023-12-8 13:55:09, 2025-11-6 16:22:06
5
- */
6
- import * as lCore from '#kebab/lib/core.js';
7
- import * as lTime from '#kebab/lib/time.js';
8
- import * as lText from '#kebab/lib/text.js';
9
- import * as lCrypto from '#kebab/lib/crypto.js';
10
- export class Jwt {
11
- /**
12
- * --- 初始化函数,相当于 construct ---
13
- * @param ctr 模型实例
14
- * @param link Kv 或 Db 实例
15
- * @param auth 设为 true 则优先从头 Authorization 或 post _auth 值读取 token
16
- * @param opt 选项
17
- */
18
- async init(ctr, opt = {}, link) {
19
- const config = ctr.getPrototype('_config');
20
- this._ctr = ctr;
21
- this._link = link;
22
- this._name = opt.name ?? config.jwt.name;
23
- this._ttl = opt.ttl ?? config.jwt.ttl;
24
- this._ssl = opt.ssl ?? config.jwt.ssl;
25
- this._secret = opt.secret ?? config.jwt.secret;
26
- this._auth = opt.auth ?? config.jwt.auth;
27
- let jwt = '';
28
- if (this._auth) {
29
- const a = this._ctr.getAuthorization();
30
- if (typeof a !== 'string') {
31
- return false;
32
- }
33
- jwt = a;
34
- }
35
- if (!jwt) {
36
- const cookie = this._ctr.getPrototype('_cookie');
37
- if (!cookie[this._name]) {
38
- return false;
39
- }
40
- jwt = cookie[this._name];
41
- }
42
- const data = await decode(this._ctr, jwt, this._link, this._name, this._secret);
43
- if (!data) {
44
- // --- 清除 cookie ---
45
- const cookie = this._ctr.getPrototype('_cookie');
46
- if (cookie[this._name]) {
47
- delete cookie[this._name];
48
- }
49
- return false;
50
- }
51
- this._ctr.setPrototype('_jwt', data);
52
- return true;
53
- }
54
- /**
55
- * --- 将 _jwt 数据封装并返回(创建新的或者续期老的 token),默认会同时设置一个 cookie(data 值会自动设置 token、exp) ---
56
- */
57
- renew() {
58
- const time = lTime.stamp();
59
- const data = this._ctr.getPrototype('_jwt');
60
- const token = lText.isFalsy(data['token']) ? lCore.random(16, lCore.RANDOM_LUN) : data['token'];
61
- data['exp'] = time + this._ttl;
62
- data['token'] = token;
63
- // --- 拼装 ---
64
- const header = lCrypto.base64Encode(lText.stringifyJson({
65
- 'alg': 'HS256',
66
- 'typ': 'JWT'
67
- }));
68
- const payload = lCrypto.base64Encode(lText.stringifyJson(data));
69
- const signature = lCrypto.hashHmac('sha256', header + '.' + payload, this._secret, 'base64');
70
- const jwt = header + '.' + payload + '.' + signature;
71
- if (!this._auth) {
72
- lCore.setCookie(this._ctr, this._name, jwt, {
73
- 'ttl': this._ttl,
74
- 'ssl': this._ssl
75
- });
76
- }
77
- return jwt;
78
- }
79
- /**
80
- * --- 清除 cookie,仅仅清除 cookie,jwt 并不会失效 ---
81
- */
82
- clearCookie() {
83
- if (this._auth) {
84
- return;
85
- }
86
- lCore.setCookie(this._ctr, this._name, '', {
87
- 'ttl': 0,
88
- 'ssl': this._ssl
89
- });
90
- }
91
- /**
92
- * --- 销毁 jwt,其实就是将 token block 信息写入 redis,如果没有 redis 则不能销毁,返回数组代表销毁成功的 token 和原 exp,否则失败返回 false ---
93
- */
94
- async destory() {
95
- if (!this._link) {
96
- return false;
97
- }
98
- const jwt = this._ctr.getPrototype('_jwt');
99
- if (!jwt.token) {
100
- return false;
101
- }
102
- const time = lTime.stamp();
103
- const token = jwt.token;
104
- const exp = jwt.exp;
105
- const ttl = exp - time;
106
- if (ttl <= 0) {
107
- lCore.emptyObject(jwt);
108
- return {
109
- token,
110
- exp
111
- };
112
- }
113
- lCore.emptyObject(jwt);
114
- await this._link.set(`${this._name}_block_${token}`, '1', ttl + 1);
115
- return {
116
- token,
117
- exp
118
- };
119
- }
120
- }
121
- /**
122
- * --- 获取 jwt 原始字符串,不保证有效 ---
123
- */
124
- export function getOrigin(ctr, name = '', auth = false) {
125
- if (!name) {
126
- name = ctr.getPrototype('_config').jwt.name;
127
- }
128
- let jwt = '';
129
- if (auth) {
130
- const a = ctr.getAuthorization();
131
- if (typeof a !== 'string') {
132
- return jwt;
133
- }
134
- jwt = a;
135
- }
136
- if (!jwt) {
137
- const cookie = ctr.getPrototype('_cookie');
138
- if (!cookie[name]) {
139
- return jwt;
140
- }
141
- jwt = cookie[name];
142
- }
143
- return jwt;
144
- }
145
- /**
146
- * --- decode ---
147
- * 不传入 link 的话,将不做 block 有效校验,只做本身的 exp 有效校验
148
- */
149
- export async function decode(ctr, val, link, name = '', secret = '') {
150
- if (!val) {
151
- return false;
152
- }
153
- const config = ctr.getPrototype('_config');
154
- if (!secret) {
155
- secret = config.jwt.secret;
156
- }
157
- if (!name) {
158
- name = config.jwt.name;
159
- }
160
- const jwtArray = val.split('.');
161
- if (!jwtArray[2]) {
162
- return false;
163
- }
164
- // jwtArray[1]: payload, jwtArray[2]: signature
165
- // --- 判断是否合法 ---
166
- const nsignature = lCrypto.hashHmac('sha256', jwtArray[0] + '.' + jwtArray[1], secret, 'base64');
167
- if (nsignature !== jwtArray[2]) {
168
- return false;
169
- }
170
- try {
171
- const payload = lCrypto.base64Decode(jwtArray[1]);
172
- if (!payload) {
173
- return false;
174
- }
175
- const data = lText.parseJson(payload);
176
- if (!data) {
177
- return false;
178
- }
179
- // --- 检测 token ---
180
- if (!data['token']) {
181
- return false;
182
- }
183
- // --- 检测 exp ---
184
- if (!data['exp']) {
185
- return false;
186
- }
187
- const time = lTime.stamp();
188
- if (data['exp'] < time) {
189
- // --- 过期 ---
190
- return false;
191
- }
192
- // --- 检测 token 是否有效 ---
193
- if (!link || !await link.get(name + '_block_' + data['token'])) {
194
- return data;
195
- }
196
- return false;
197
- }
198
- catch {
199
- return false;
200
- }
201
- }
202
- /**
203
- * --- 仅往 redis 写禁止相关 token 的数据,一般用于异步通知时在异处的服务器来调用的 ---
204
- */
205
- export async function block(ctr, token, exp, link, name = '') {
206
- const time = lTime.stamp();
207
- if (!name) {
208
- name = ctr.getPrototype('_config').jwt.name;
209
- }
210
- const ttl = exp - time;
211
- if (ttl <= 0) {
212
- return true;
213
- }
214
- await link.set(name + '_block_' + token, '1', ttl + 1);
215
- return true;
216
- }
217
- /**
218
- * @param ctr 模型实例
219
- * @param opt name, ttl, ssl, secret, auth: false, true 则优先从头 Authorization 或 post _auth 值读取 token
220
- * @param link 实例
221
- */
222
- export async function get(ctr, opt = {}, link) {
223
- const jwt = new Jwt();
224
- await jwt.init(ctr, opt, link);
225
- return jwt;
226
- }