gamerpc 9.0.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gamerpc",
3
- "version": "9.0.1",
3
+ "version": "9.0.3",
4
4
  "author": "bookmansoft <ceo@920.cc>",
5
5
  "contributors": [
6
6
  "bookmansoft <ceo@920.cc>"
package/src/authConn.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * authConn - 授权式连接器 (V7.1.3)
2
+ * authConn - 授权式连接器
3
3
  *
4
4
  * 完整封装了客户端通过授权模式访问远程全节点的方法,仅仅依赖 create-hmac
5
5
  * 可以直接用于 node 环境下的服务端程序,来访问远程全节点丰富的API接口
@@ -7,7 +7,8 @@
7
7
  */
8
8
 
9
9
  const assert = require('./utils/assert')
10
- const {io, signHMAC, Base64, now, CommStatus, createHmac, ReturnCode, CommMode, NotifyType, encrypt, decrypt, stringify} = require('./utils/util');
10
+ const io = require('socket.io-client');
11
+ const {signHMAC, Base64, now, CommStatus, createHmac, ReturnCode, CommMode, NotifyType, encrypt, decrypt, stringify} = require('./utils/util');
11
12
  let {sha1, hash160, hash256, verifyData, generateKey, signObj, verifyObj, verifyAddress} = require('./utils/verifyData');
12
13
  const Secret = require('./utils/secret')
13
14
 
package/src/remote.js CHANGED
@@ -5,7 +5,8 @@
5
5
  * 支持 Notify、JSONP、Watching 等报文通讯模式。
6
6
  * 支持 LBS 重定向功能和 HTTPS 协议。
7
7
  */
8
- const { stringify, NotifyType, extendObj, CommStatus, clone, ReturnCodeName, ReturnCode, CommMode, io } = require('./utils/util');
8
+ const { stringify, NotifyType, extendObj, CommStatus, clone, ReturnCodeName, ReturnCode, CommMode } = require('./utils/util');
9
+ const io = require('socket.io-client');
9
10
  const EventEmitter = require('events').EventEmitter;
10
11
  const Indicator = require('./utils/Indicator');
11
12
 
@@ -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); });