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/README.md +0 -12
- package/auth3.0.md +189 -0
- package/package.json +7 -8
- package/src/authConn.js +534 -349
- package/src/gameConn.js +3 -4
- package/src/index.js +32 -49
- package/src/remote.js +2 -1
- package/src/utils/secp256k1.js +79 -101
- package/src/utils/socket.io.min.js +7 -0
- package/src/utils/util.js +157 -85
- package/test/game/auth.js +1 -1
- package/test/game/auto.js +231 -0
- package/test/game/crm.js +1 -1
- package/test/test.html +3 -3
- package/test/utils/secp256k1.js +444 -0
- package/webpack.config.js +2 -2
- package/.babelrc +0 -16
- package/docs/2026.07.15.md +0 -142
- package/test/game/kow.js +0 -43
- package/test/game/mgr.js +0 -80
package/src/utils/util.js
CHANGED
|
@@ -5,16 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
477
|
+
util.inspectify = function inspectify(obj, color) {
|
|
478
|
+
if (typeof obj === 'string')
|
|
479
|
+
return obj;
|
|
419
480
|
|
|
420
|
-
|
|
481
|
+
inspectOptions.colors = color !== false;
|
|
421
482
|
|
|
422
|
-
|
|
423
|
-
|
|
483
|
+
return nodeUtil.inspect(obj, inspectOptions);
|
|
484
|
+
};
|
|
424
485
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
486
|
+
/**
|
|
487
|
+
* Format a string.
|
|
488
|
+
* @function
|
|
489
|
+
* @param {...String} args
|
|
490
|
+
* @returns {String}
|
|
491
|
+
*/
|
|
431
492
|
|
|
432
|
-
|
|
493
|
+
util.fmt = nodeUtil.format;
|
|
433
494
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
495
|
+
/**
|
|
496
|
+
* Format a string.
|
|
497
|
+
* @param {Array} args
|
|
498
|
+
* @param {Boolean?} color
|
|
499
|
+
* @return {String}
|
|
500
|
+
*/
|
|
440
501
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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 {
|
|
630
|
-
* @param {
|
|
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 {
|
|
666
|
-
* @param {
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
731
|
+
util.indexOf = function indexOf(items, data) {
|
|
732
|
+
assert(Array.isArray(items));
|
|
733
|
+
assert(Buffer.isBuffer(data));
|
|
673
734
|
|
|
674
|
-
|
|
675
|
-
|
|
735
|
+
for (let i = 0; i < items.length; i++) {
|
|
736
|
+
const item = items[i];
|
|
676
737
|
|
|
677
|
-
|
|
738
|
+
assert(Buffer.isBuffer(item));
|
|
678
739
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
740
|
+
if (item.equals(data))
|
|
741
|
+
return i;
|
|
742
|
+
}
|
|
682
743
|
|
|
683
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
var encIv = '$-._aB9601152555';
|
|
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);
|