gamerpc 9.0.0 → 9.0.3

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/src/utils/util.js CHANGED
@@ -5,16 +5,11 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- //const assert = require('assert');
9
- //const nodeUtil = require('util');
10
- // const aes = require('./aes');
11
- // const createHmac = require('create-hmac/browser');
12
- const io = require('socket.io-client');
13
- // const Buffer = require('safe-buffer').Buffer
14
-
15
- function assert(ok) {
16
- throw new Error('something wrong');
17
- }
8
+ const assert = require('assert');
9
+ const nodeUtil = require('util');
10
+ const aes = require('./aes');
11
+ const createHmac = require('create-hmac/browser');
12
+ const Buffer = require('safe-buffer').Buffer
18
13
 
19
14
  /**
20
15
  * @exports utils/util
@@ -86,6 +81,73 @@ util.sprintf = function sprintf() {
86
81
  return o.join('');
87
82
  }
88
83
 
84
+ /**
85
+ * 加密方法
86
+ * @param key 加密key
87
+ * @param iv 向量
88
+ * @param data 需要加密的数据
89
+ * @returns string
90
+ */
91
+ util.encrypt = function encrypt(key, iv, data) {
92
+ if(!Buffer.isBuffer(key)) {
93
+ assert(typeof key === 'string');
94
+ key = Buffer.from(key, 'binary')
95
+ }
96
+
97
+ if(!Buffer.isBuffer(iv)) {
98
+ assert(typeof iv === 'string');
99
+ iv = Buffer.from(iv, 'binary')
100
+ }
101
+
102
+ if(!Buffer.isBuffer(data)) {
103
+ assert(typeof data === 'string');
104
+ data = Buffer.from(data, 'binary')
105
+ }
106
+
107
+ var crypted = aes.encipher(data, key, iv);
108
+
109
+ crypted = Buffer.from(crypted, 'binary');
110
+
111
+ let ret = Buffer.alloc(crypted.length*2);
112
+ for(let i = 0; i < crypted.length; i++){
113
+ ret[2*i] = (crypted[i] >> 4) + 97;
114
+ ret[2*i+1] = (crypted[i] & 0x0F) + 97;
115
+ }
116
+ return ret.toString('binary');
117
+ };
118
+
119
+ /**
120
+ * 解密方法
121
+ * @param key 解密的key
122
+ * @param iv 向量
123
+ * @param data 密文
124
+ * @returns string
125
+ */
126
+ util.decrypt = function decrypt(key, iv, data) {
127
+ if(!Buffer.isBuffer(key)) {
128
+ assert(typeof key === 'string');
129
+ key = Buffer.from(key, 'binary')
130
+ }
131
+
132
+ if(!Buffer.isBuffer(iv)) {
133
+ assert(typeof iv === 'string');
134
+ iv = Buffer.from(iv, 'binary')
135
+ }
136
+
137
+ if(!Buffer.isBuffer(data)) {
138
+ assert(typeof data === 'string');
139
+ data = Buffer.from(data, 'binary')
140
+ }
141
+
142
+ let ret = Buffer.alloc(data.length/2);
143
+ for(let i = 0; i < ret.length; i++){
144
+ ret[i] = ((data[i*2]-97)<<4) | (data[i*2+1]-97);
145
+ }
146
+
147
+ var decoded = aes.decipher(ret, key, iv);
148
+ return decoded.toString('binary');
149
+ };
150
+
89
151
  /**
90
152
  * Test whether a number is Number,
91
153
  * finite, and below MAX_SAFE_INTEGER.
@@ -196,10 +258,9 @@ util.stringify = function(data, exclude) {
196
258
  }
197
259
  });
198
260
  return base;
261
+ } else if(Buffer.isBuffer(data)) {
262
+ return data.toString('base64');
199
263
  }
200
- // else if(Buffer.isBuffer(data)) {
201
- // return data.toString('base64');
202
- // }
203
264
 
204
265
  return data;
205
266
  }
@@ -413,39 +474,39 @@ util.isSafeAddition = function isSafeAddition(a, b) {
413
474
  * @return {String}
414
475
  */
415
476
 
416
- // util.inspectify = function inspectify(obj, color) {
417
- // if (typeof obj === 'string')
418
- // return obj;
477
+ util.inspectify = function inspectify(obj, color) {
478
+ if (typeof obj === 'string')
479
+ return obj;
419
480
 
420
- // inspectOptions.colors = color !== false;
481
+ inspectOptions.colors = color !== false;
421
482
 
422
- // return nodeUtil.inspect(obj, inspectOptions);
423
- // };
483
+ return nodeUtil.inspect(obj, inspectOptions);
484
+ };
424
485
 
425
- // /**
426
- // * Format a string.
427
- // * @function
428
- // * @param {...String} args
429
- // * @returns {String}
430
- // */
486
+ /**
487
+ * Format a string.
488
+ * @function
489
+ * @param {...String} args
490
+ * @returns {String}
491
+ */
431
492
 
432
- // util.fmt = nodeUtil.format;
493
+ util.fmt = nodeUtil.format;
433
494
 
434
- // /**
435
- // * Format a string.
436
- // * @param {Array} args
437
- // * @param {Boolean?} color
438
- // * @return {String}
439
- // */
495
+ /**
496
+ * Format a string.
497
+ * @param {Array} args
498
+ * @param {Boolean?} color
499
+ * @return {String}
500
+ */
440
501
 
441
- // util.format = function format(args, color) {
442
- // if (args.length > 0 && args[0] && typeof args[0] === 'object') {
443
- // if (color == null)
444
- // color = Boolean(process.stdout && process.stdout.isTTY);
445
- // return util.inspectify(args[0], color);
446
- // }
447
- // return util.fmt(...args);
448
- // };
502
+ util.format = function format(args, color) {
503
+ if (args.length > 0 && args[0] && typeof args[0] === 'object') {
504
+ if (color == null)
505
+ color = Boolean(process.stdout && process.stdout.isTTY);
506
+ return util.inspectify(args[0], color);
507
+ }
508
+ return util.fmt(...args);
509
+ };
449
510
 
450
511
  /**
451
512
  * Write a message to stdout (console in browser).
@@ -594,40 +655,40 @@ util.random = function random(min, max) {
594
655
  /**
595
656
  * Create a 32 or 64 bit nonce.
596
657
  * @param {Number} size
597
- * @returns {*}
658
+ * @returns {Buffer}
598
659
  */
599
660
 
600
- // util.nonce = function nonce(size) {
601
- // let n, data;
602
-
603
- // if (!size)
604
- // size = 8;
605
-
606
- // switch (size) {
607
- // case 8:
608
- // data = Buffer.allocUnsafe(8);
609
- // n = util.random(0, 0x100000000);
610
- // data.writeUInt32LE(n, 0, true);
611
- // n = util.random(0, 0x100000000);
612
- // data.writeUInt32LE(n, 4, true);
613
- // break;
614
- // case 4:
615
- // data = Buffer.allocUnsafe(4);
616
- // n = util.random(0, 0x100000000);
617
- // data.writeUInt32LE(n, 0, true);
618
- // break;
619
- // default:
620
- // assert(false, 'Bad nonce size.');
621
- // break;
622
- // }
623
-
624
- // return data;
625
- // };
661
+ util.nonce = function nonce(size) {
662
+ let n, data;
663
+
664
+ if (!size)
665
+ size = 8;
666
+
667
+ switch (size) {
668
+ case 8:
669
+ data = Buffer.allocUnsafe(8);
670
+ n = util.random(0, 0x100000000);
671
+ data.writeUInt32LE(n, 0, true);
672
+ n = util.random(0, 0x100000000);
673
+ data.writeUInt32LE(n, 4, true);
674
+ break;
675
+ case 4:
676
+ data = Buffer.allocUnsafe(4);
677
+ n = util.random(0, 0x100000000);
678
+ data.writeUInt32LE(n, 0, true);
679
+ break;
680
+ default:
681
+ assert(false, 'Bad nonce size.');
682
+ break;
683
+ }
684
+
685
+ return data;
686
+ };
626
687
 
627
688
  /**
628
689
  * String comparator (memcmp + length comparison).
629
- * @param {*} a
630
- * @param {*} b
690
+ * @param {Buffer} a
691
+ * @param {Buffer} b
631
692
  * @returns {Number} -1, 1, or 0.
632
693
  */
633
694
 
@@ -662,26 +723,26 @@ util.mb = function mb(size) {
662
723
 
663
724
  /**
664
725
  * Find index of a buffer in an array of buffers.
665
- * @param {buffer[]} items
666
- * @param {buffer} data - Target buffer to find.
726
+ * @param {Buffer[]} items
727
+ * @param {Buffer} data - Target buffer to find.
667
728
  * @returns {Number} Index (-1 if not found).
668
729
  */
669
730
 
670
- // util.indexOf = function indexOf(items, data) {
671
- // assert(Array.isArray(items));
672
- // assert(Buffer.isBuffer(data));
731
+ util.indexOf = function indexOf(items, data) {
732
+ assert(Array.isArray(items));
733
+ assert(Buffer.isBuffer(data));
673
734
 
674
- // for (let i = 0; i < items.length; i++) {
675
- // const item = items[i];
735
+ for (let i = 0; i < items.length; i++) {
736
+ const item = items[i];
676
737
 
677
- // assert(Buffer.isBuffer(item));
738
+ assert(Buffer.isBuffer(item));
678
739
 
679
- // if (item.equals(data))
680
- // return i;
681
- // }
740
+ if (item.equals(data))
741
+ return i;
742
+ }
682
743
 
683
- // return -1;
684
- // };
744
+ return -1;
745
+ };
685
746
 
686
747
  /**
687
748
  * Convert a number to a padded uint8
@@ -1223,7 +1284,7 @@ const CommMode = {
1223
1284
  */
1224
1285
  const NotifyType = {
1225
1286
  none: 0, //测试消息
1226
- test: 9999, //测试消息
1287
+ test: 9999, //测试消息
1227
1288
  };
1228
1289
 
1229
1290
  function Base64() {
@@ -1329,6 +1390,17 @@ function Base64() {
1329
1390
  }
1330
1391
  }
1331
1392
 
1393
+ /**
1394
+ * 利用HMAC算法,以及令牌固定量和令牌随机量,计算访问令牌
1395
+ * @param {*} token
1396
+ * @param {*} random
1397
+ */
1398
+ function signHMAC(token, random) {
1399
+ var hmac = createHmac('sha256', random);
1400
+ let sig = hmac.update(token).digest('hex');
1401
+ return sig;
1402
+ }
1403
+
1332
1404
  /**
1333
1405
  * 扩展对象,用于多个对象之间的属性注入
1334
1406
  * @note 对属性(get set)复制不会成功
@@ -1444,13 +1516,13 @@ function clone(obj) {
1444
1516
  throw new Error("Unable to copy obj! Its type isn't supported.");
1445
1517
  }
1446
1518
 
1447
- util.io = io;
1448
1519
  util.Base64 = Base64;
1520
+ util.signHMAC = signHMAC;
1449
1521
  util.ReturnCode = ReturnCode;
1450
1522
  util.ReturnCodeName = ReturnCodeName;
1451
1523
  util.CommMode = CommMode;
1452
1524
  util.NotifyType = NotifyType;
1453
- // util.createHmac = createHmac;
1525
+ util.createHmac = createHmac;
1454
1526
  util.extendObj = extendObj;
1455
1527
  util.clone = clone;
1456
1528
  util.CommStatus = CommStatus;
package/test/game/auth.js CHANGED
@@ -32,7 +32,7 @@ let phone_wx = ((Math.random() * 100000000) | 0).toString();
32
32
  //用于验证后期绑定成功的用户证书缓存变量
33
33
  let authUser = '';
34
34
 
35
- describe('钱包注册登录测试', () => {
35
+ describe.only('钱包注册登录测试', () => {
36
36
  it('用户注册并登录 - 使用两阶段认证模式', async () => {
37
37
  //执行登录操作,通过配置对象传入用户信息,并指定签证方式为两阶段认证
38
38
  let ret = await remote.init(/*初始化连接器,只保留原始配置信息*/).login({
@@ -0,0 +1,231 @@
1
+ /**
2
+ * CoreOfKow GEO 压力测试 — 双模式 (WS / POST)
3
+ * 用法: node test/game/auto.js --bots=100 --tick=5000
4
+ *
5
+ * 顶部 MODE 变量切换连接模式:
6
+ * 'ws' — WebSocket 长连接 (使用 gamerpc gameconn)
7
+ * 'post' — HTTP POST 短连接
8
+ */
9
+ const MODE = 'ws'; // ← 切换点: 'ws' | 'post'
10
+
11
+ const readline = require('readline');
12
+
13
+ const CFG = { host: "127.0.0.1", port: 9901, domain: 'CoreOfKow' };
14
+ const argv = {};
15
+ process.argv.slice(2).forEach(a => { const m = a.match(/^--(\w+)=(.+)$/); if (m) argv[m[1]] = isNaN(m[2]) ? m[2] : parseInt(m[2]); });
16
+ const GEO = { bots: argv.bots || 100, tick: argv.tick || 5000 };
17
+ console.log('[GEO]', JSON.stringify(GEO), 'MODE:', MODE);
18
+
19
+ // ============================================================
20
+ // POST 模式工具 (仅 POST 模式使用)
21
+ // ============================================================
22
+ const fetch = MODE === 'post' ? require('node-fetch') : null;
23
+ let POST = {};
24
+ if (MODE === 'post') {
25
+ POST.lHost = "127.0.0.1"; POST.lPort = 9301;
26
+
27
+ POST.getServerInfo = async function (oem) {
28
+ try {
29
+ const r = await fetch(`http://${CFG.host}:${CFG.port}/index.html`, {
30
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({ control: 'lb', func: 'getServerInfo', oemInfo: oem })
32
+ });
33
+ if (r.status === 200) { const j = await r.json(); if (j.code === 0 && j.data) { POST.lHost = j.data.ip; POST.lPort = j.data.port; } }
34
+ } catch (_) {}
35
+ };
36
+
37
+ POST.req = async function (body) {
38
+ try {
39
+ const r = await fetch(`http://${POST.lHost}:${POST.lPort}/index.html`, {
40
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify(body)
42
+ });
43
+ if (r.status === 200) { const j = await r.json(); return typeof j.code === 'undefined' ? { code: 0, data: j } : j; }
44
+ } catch (e) { return { code: -1, msg: String(e).slice(0, 40) }; }
45
+ return { code: -1 };
46
+ };
47
+
48
+ POST.oemFor = (openid) => ({ domain: CFG.domain, openid, auth: { openid, token: openid } });
49
+ }
50
+
51
+ // ============================================================
52
+ // WS 模式工具 (仅 WS 模式使用)
53
+ // ============================================================
54
+ const { gameconn } = MODE === 'ws' ? require('../../src/index') : { gameconn: null };
55
+
56
+ // ============================================================
57
+ // 统一 Bot 工厂 — 两种模式对外暴露相同的 { request, oem, close }
58
+ // ============================================================
59
+ async function createBot(openid) {
60
+ if (MODE === 'ws') {
61
+ const conn = new gameconn({
62
+ UrlHead: "http",
63
+ webserver: { host: CFG.host, port: CFG.port },
64
+ })
65
+ .setFetch(require('node-fetch'))
66
+ .setmode('webSocket');
67
+
68
+ const ok = await conn.login({ domain: CFG.domain, openid, auth: {} });
69
+ const oem = { domain: CFG.domain, openid };
70
+
71
+ return {
72
+ conn, // 内部引用 (world 快照用)
73
+ oem,
74
+ active: ok,
75
+ async request(params) { return conn.fetching(params); },
76
+ close() { conn.close(); },
77
+ };
78
+ } else {
79
+ const oem = POST.oemFor(openid);
80
+ await POST.getServerInfo(oem);
81
+ const ret = await POST.req({ control: 'index', func: 'login', oemInfo: oem });
82
+ return {
83
+ oem,
84
+ active: ret && ret.code === 0,
85
+ async request(params) { return POST.req({ ...params, oemInfo: this.oem }); },
86
+ close() {},
87
+ };
88
+ }
89
+ }
90
+
91
+ // ============================================================
92
+ // 统计
93
+ // ============================================================
94
+ const S = { actions: 0, errors: 0, lat: [], by: {}, ts: Date.now(),
95
+ rec(act, ms, e) { this.lat.push(ms); if (this.lat.length > 500) this.lat.shift(); if (!this.by[act]) this.by[act] = { ok: 0, err: 0 }; if (e) { this.errors++; this.by[act].err++; } else { this.actions++; this.by[act].ok++; } return this.actions + this.errors; },
96
+ avg() { const a = this.lat; return a.length ? (a.reduce((x, y) => x + y, 0) / a.length).toFixed(0) : 0; },
97
+ };
98
+
99
+ // ============================================================
100
+ // 世界快照
101
+ // ============================================================
102
+ async function world(bot) {
103
+ const [pop, atk, defend, ally, me] = await Promise.all([
104
+ bot.request({ control: 'P601001', func: 'Execute', oper: 91 }),
105
+ bot.request({ control: 'P601001', func: 'Execute', oper: 92 }),
106
+ bot.request({ control: 'P601001', func: 'Execute', oper: 93 }),
107
+ bot.request({ control: 'P601001', func: 'Execute', oper: 94 }),
108
+ bot.request({ control: 'P200001', func: 'query' }),
109
+ ]);
110
+ const top = (d, k) => d?.data?.list ? d.data.list.slice(0, 3).map(x => x[k] || x.username || x.AllyName || '?').join(', ') : '—';
111
+ const m = me?.data || {};
112
+ return `🏆人口:${top(pop, 'VillageName')} | ⚔攻击:${top(atk, 'VillageName')} | 🛡防御:${top(defend, 'VillageName')} | 🏰联盟:${top(ally, 'AllyName')} | 👤你:人口${m.people || '?'} 金币${m.gold || '?'} 钻石${m.diamond || '?'}`;
113
+ }
114
+
115
+ // ============================================================
116
+ // 行为池 — 统一通过 bot.request() 发送
117
+ // ============================================================
118
+ const A = (ctrl, fn, extra) => bot => bot.request({ control: ctrl, func: fn, ...(extra || {}) });
119
+
120
+ const B_k = [
121
+ { w: 8, e: '👤 ', d: '个人信息', f: A('P200001', 'query') },
122
+ { w: 4, e: '🏆 ', d: '人口排名', f: A('P601001', 'Execute', { oper: 91 }) },
123
+ { w: 3, e: '⚔️ ', d: '攻击排名', f: A('P601001', 'Execute', { oper: 92 }) },
124
+ { w: 2, e: '🛡️ ', d: '防御排名', f: A('P601001', 'Execute', { oper: 93 }) },
125
+ { w: 2, e: '🏰 ', d: '联盟排名', f: A('P601001', 'Execute', { oper: 94 }) },
126
+ { w: 1, e: '👑 ', d: '地区排名', f: A('P601001', 'Execute', { oper: 95 }) },
127
+ { w: 2, e: '👤🔍',d:'查看玩家', f: A('P500001', 'Execute', { uid: 90 }) },
128
+ { w: 5, e: '🗺️ ', d: '大地图', f: A('P100001', 'Execute', { params: { oper: 'bigmap', d: 0, size: 5 } }) },
129
+ { w: 3, e: '🏠 ', d: '回主村', f: A('P100001', 'Execute', { params: { oper: 'goHome', size: 5 } }) },
130
+ { w: 4, e: '🏗️ ', d: '建造升级', f: A('P200001', 'buildingOper', { oper: 'upgrade' }) },
131
+ { w: 3, e: '🏘️ ', d: '开分村', f: A('P200001', 'createVillage') },
132
+ { w: 2, e: '🎪 ', d: '举办庆典', f: A('P200001', 'partyAction') },
133
+ { w: 2, e: '🗼 ', d: '图腾查询', f: A('P200001', 'totemQuery') },
134
+ { w: 2, e: '🐉 ', d: '怪物查询', f: A('P200001', 'monsterQuery') },
135
+ { w: 2, e: '🏰🔍',d:'查看村庄', f: A('P200001', 'queryThird') },
136
+ { w: 1, e: '📜🔍',d:'村庄事件', f: A('P200001', 'getVillageEvents') },
137
+ { w: 2, e: '🤝 ', d: '创建联盟', f: A('P400001', 'Execute', { oper: 'createAlly', aName: 'GEO_' + Date.now().toString(36), intro: 'test' }) },
138
+ { w: 2, e: '🏛️ ', d: '副本列表', f: A('P100002', 'cityOper', { params: { a: 6 } }) },
139
+ { w: 2, e: '🎒 ', d: '道具列表', f: A('P501001', 'list') },
140
+ { w: 2, e: '⚡ ', d: '购买体力', f: A('P900001', 'purchaseAction') },
141
+ { w: 3, e: '🦸 ', d: '英雄列表', f: A('P502001', 'Execute', { oper: 1 }) },
142
+ ];
143
+
144
+ const ROLE_W = {
145
+ builder: [ 8, 4,3,2,2,1, 0, 5,3, 6,6, 4,4,2,2,1, 0, 0, 3,2, 4 ],
146
+ warlord: [ 8, 4,5,3,3,1, 2, 5,3, 3,3, 2,2,3,3,1, 5, 0, 3,2, 3 ],
147
+ trader: [ 8, 4,2,2,2,1, 0, 3,2, 2,2, 2,2,2,2,1, 0, 0, 4,4, 2 ],
148
+ adventurer: [ 8, 4,2,2,2,1, 0, 5,3, 2,3, 1,2,4,4,1, 0, 5, 3,2, 3 ],
149
+ diplomat: [ 8, 2,2,2,4,1, 2, 3,2, 2,2, 2,2,2,2,1, 8, 0, 3,2, 2 ],
150
+ raider: [ 8, 2,5,2,2,1, 2, 5,3, 2,3, 2,2,3,3,1, 2, 0, 3,2, 3 ],
151
+ };
152
+ const ROLE_RATIO = { builder: 2, warlord: 3, trader: 2, adventurer: 1, diplomat: 1, raider: 1 };
153
+
154
+ function buildPool(weights) {
155
+ const pool = B_k.map((b, i) => ({ ...b, w: weights[i] || b.w })).filter(b => b.w > 0);
156
+ let c = 0; pool.forEach(b => { b._min = c; c += b.w; b._max = c; }); return pool;
157
+ }
158
+ function pick(pool) { const r = Math.random() * pool[pool.length - 1]._max; for (const b of pool) if (r >= b._min && r < b._max) return b; return pool[0]; }
159
+ function assignRole() { const r = Math.random() * 10; let c = 0; for (const [k, v] of Object.entries(ROLE_RATIO)) { c += v; if (r <= c) return k; } return 'warlord'; }
160
+ function fmt(ret, ms) { if (!ret) return `❌ ${ms}ms`; return ret.code === 0 ? `✅ ${ms}ms` : `❌ code=${ret.code} ${ms}ms`; }
161
+
162
+ // ============================================================
163
+ // 主流程
164
+ // ============================================================
165
+ async function main() {
166
+ const modeLabel = MODE === 'ws' ? 'WS 长连接' : 'HTTP POST';
167
+ console.log(`\n═══ GEO 压力测试 (${modeLabel}) ═══`);
168
+ console.log(`bots: ${GEO.bots} tick: ${GEO.tick}ms 行为: ${B_k.length}种 角色: ${Object.keys(ROLE_RATIO).length}种\n`);
169
+
170
+ console.log(`[注册] ${GEO.bots} bots (${modeLabel})...`);
171
+ const bots = []; const ts = Date.now().toString(36);
172
+ for (let i = 0; i < GEO.bots; i++) {
173
+ const openid = `bxs.g${ts}.${i}`;
174
+ const role = assignRole();
175
+ const bot = await createBot(openid);
176
+ bot.role = role;
177
+ bot.pool = buildPool(ROLE_W[role]);
178
+ bots.push(bot);
179
+
180
+ if ((i + 1) % 50 === 0 || i === GEO.bots - 1) {
181
+ const okCount = bots.filter(b => b.active).length;
182
+ const pct = ((i + 1) / GEO.bots * 100).toFixed(0);
183
+ process.stdout.write(`\r [${'█'.repeat(pct / 5)}${'░'.repeat(20 - pct / 5)}] ${i + 1}/${GEO.bots} ✅${okCount} ❌${i + 1 - okCount}`);
184
+ }
185
+ }
186
+ const ok = bots.filter(b => b.active).length;
187
+ console.log(`\n 完成! ${ok}/${GEO.bots}`);
188
+ const rc = {}; bots.forEach(b => { if (!rc[b.role]) rc[b.role] = 0; rc[b.role]++; });
189
+ console.log(' 角色: ' + Object.entries(rc).map(([r, c]) => `${r}×${c}`).join(' ') + '\n');
190
+ if (!ok) { console.log('登录全部失败'); process.exit(1); }
191
+
192
+ console.log('[初始世界快照]\n ' + await world(bots[0]) + '\n');
193
+
194
+ let total = 0, tickNum = 0;
195
+ const timer = setInterval(async () => {
196
+ tickNum++;
197
+ const active = bots.filter(b => b.active);
198
+ for (let i = active.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [active[i], active[j]] = [active[j], active[i]]; }
199
+ for (const bot of active) {
200
+ if (Math.random() > 0.35) continue;
201
+ const act = pick(bot.pool); const st = Date.now();
202
+ try {
203
+ const ret = await act.f(bot); const ms = Date.now() - st;
204
+ total = S.rec(act.e, ms, !ret || ret.code !== 0);
205
+ if (tickNum <= 2 || Math.random() < 0.008) {
206
+ const id = (MODE === 'ws' ? bot.conn?.userInfo?.openid : bot.oem?.openid) || '';
207
+ console.log(` ${act.e}${act.d} [${id.slice(-6)}:${bot.role}] → ${fmt(ret, ms)}`);
208
+ }
209
+ } catch (e) { total = S.rec(act.e, Date.now() - st, true); }
210
+ }
211
+ if (tickNum % 6 === 0) {
212
+ const el = ((Date.now() - S.ts) / 1000).toFixed(0);
213
+ console.log(`\n─── tick#${tickNum} ${el}s ${total}请求(${(total / el).toFixed(1)}/s) err:${S.errors} avg:${S.avg()}ms ───`);
214
+ console.log(' ' + Object.entries(S.by).map(([k, v]) => `${k}${v.ok}/${v.ok + v.err}`).join(' '));
215
+ console.log(' ' + await world(bots[0]));
216
+ }
217
+ }, GEO.tick);
218
+
219
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
220
+ rl.on('line', async (input) => {
221
+ if (input.trim() === 's') { console.log('\n' + await world(bots[0]) + `\n在线: ${bots.filter(b => b.active).length}/${GEO.bots}`); }
222
+ else if (input.trim() === 'q') {
223
+ clearInterval(timer);
224
+ bots.forEach(b => b.close());
225
+ console.log(`\n结束 ${total}请求 ${S.errors}错误`);
226
+ rl.close(); process.exit(0);
227
+ }
228
+ });
229
+ console.log('[交互] s=快照 q=退出\n');
230
+ }
231
+ main().catch(e => { console.error(e); process.exit(1); });
package/test/game/crm.js CHANGED
@@ -22,7 +22,7 @@ let username = `${(Math.random()*1000000)|0}@vallnet.cn`;
22
22
  let password = crypto.createHash("sha1").update(((Math.random()*1000000)|0).toString() + salt).digest("hex");
23
23
  let mobilephone = `139${((Math.random()*100000000)|0).toString()}`;
24
24
 
25
- describe('CRM注册登录', () => {
25
+ describe.only('CRM注册登录', () => {
26
26
  it('用户注册 - 通过两阶段认证模式实现', async () => {
27
27
  //当用户点击'获取验证码'时执行如下流程:
28
28
  let ret = await remote.init(/*初始化连接器,只保留原始配置信息*/).login({
package/test/test.html CHANGED
@@ -12,7 +12,7 @@
12
12
  // V9.0.0 统一入口,通过全局 toolkit 对象访问所有功能
13
13
  console.log('toolkit:', toolkit);
14
14
 
15
- // 1. 区块链授权式连接器 (V7.1.2 API)
15
+ // 1. 区块链授权式连接器 (V7.1.3 API)
16
16
  var blockchainConn = new toolkit.conn();
17
17
  // 或: new toolkit.authConn();
18
18
 
@@ -28,8 +28,8 @@
28
28
  console.log('签名验证结果:', ver);
29
29
 
30
30
  // 4. AES 加解密测试
31
- var encKey = '$-._s1ZshKZ6WissH5gOs1ZshKZ6Wiss'; // 32位
32
- var encIv = '$-._aB9601152555'; // 16位
31
+ var encKey = '$-._s1ZshKZ6WissH5gOs1ZshKZ6Wiss';
32
+ var encIv = '$-._aB9601152555';
33
33
  var testData = 'hello gamerpc V9.0.0';
34
34
  var encrypted = toolkit.encrypt(encKey, encIv, testData);
35
35
  var decrypted = toolkit.decrypt(encKey, encIv, encrypted);