消息导出脚本,收发消息简化

This commit is contained in:
liuxiaobo 2025-07-08 23:29:43 +08:00
parent e5fc17d1f6
commit e245377391
7 changed files with 271 additions and 42 deletions

View File

@ -4,3 +4,7 @@ export class PrefabNames {
return this.login
}
}
export enum EventType {
Ws = 'ws-message',
}

View File

@ -2,11 +2,8 @@ import { _decorator, Button, Component, EditBox, Label, Node } from 'cc'
import { WsClient } from '../network/wsclient'
import { WsConfig } from '../network/wsconfig'
import { PbHelper } from '../network/pbHelper'
import pb from '../pb/login.js'
import pb1 from '../pb/client.js'
import pb2 from '../pb/msgId.js'
import pb3 from '../pb/service.js'
// import * as pb from '../pb/login.d'
import { ReqUserLogin, ServiceTypeId, MsgId, RspUserLogin } from '../network/pbExport'
import { EventType } from '../constant'
const { ccclass, property } = _decorator
@ccclass('loginPanel')
@ -24,11 +21,11 @@ export class loginPanel extends Component {
start() {
this.loginButton.node.on(Button.EventType.CLICK, this.onLoginButtonClick, this)
// 监听websocket消息事件
window.addEventListener('ws-message', this.onMessage.bind(this))
window.addEventListener(EventType.Ws, this.onMessage.bind(this))
}
protected onDestroy(): void {
window.removeEventListener('ws-message', this.onMessage.bind(this))
window.removeEventListener(EventType.Ws, this.onMessage.bind(this))
}
// 登录按钮点击处理函数
@ -46,7 +43,7 @@ export class loginPanel extends Component {
const ws = WsClient.Instance()
console.log(typeof ws)
if (!ws.IsConnected) {
await ws.ConnectAsync(WsConfig.Address[0].Address[0])
await ws.ConnectAsync(WsConfig.Address[1].Address[0])
if (!ws.IsConnected) {
this.statusLable.string = '连接网络失败'
return
@ -54,33 +51,28 @@ export class loginPanel extends Component {
}
console.log('登陆帐号:', username, '密码:', pwd)
console.log('ReqUserLogin:', pb.ReqUserLogin)
const req = new pb.ReqUserLogin()
const req = new ReqUserLogin()
req.username = username
req.password = pwd
req.version = '20250601123030'
// const req = pb.ReqUserLogin.create({ username: username, password: pwd, ip: '', deviceId: '', version: '' })
const buffer = pb.ReqUserLogin.encode(req).finish()
const clinetMsg = new pb1.ClientMsg()
clinetMsg.msgId = pb2.MsgId.ReqUserLoginId
clinetMsg.data = buffer
clinetMsg.serviceTid = pb3.ServiceTypeId.STI_Login
const buffer1 = pb1.ClientMsg.encode(clinetMsg).finish()
ws.Send(buffer1)
const buffer = ReqUserLogin.encode(req).finish()
ws.Send(ServiceTypeId.STI_Login, MsgId.ReqUserLoginId, buffer)
}
private onMessage(event: CustomEvent) {
try {
const message = JSON.parse(event.detail) // 解析消息内容
if (message.type === 'loginSuccess') {
this.statusLable.string = '登录成功'
this.node.active = false
} else if (message.type === 'loginError') {
this.statusLable.string = '登录失败:' + message.reason
const [msgid, buffer] = PbHelper.DecodeClient(event.detail)
switch (msgid) {
case MsgId.RspUserLoginId:
const rsp = PbHelper.Decode(RspUserLogin, buffer)
console.log('收到返回消息:', rsp)
default:
// console.log('收到非json消息:', event.detail)
}
} catch (error) {
console.log('收到非json消息:', event.detail)
// console.log('解析错误失败:', error)
console.log(PbHelper.Uint8ToHex(event.detail))
}
}

View File

@ -0,0 +1,80 @@
// 由 pb-import-export-generator 自动生成,请勿手动修改
// 导入语句
import chat from '../pb/chat.js'
import client from '../pb/client.js'
import code from '../pb/code.js'
import colorgame from '../pb/colorgame.js'
import common from '../pb/common.js'
import login from '../pb/login.js'
import match from '../pb/match.js'
import msgId from '../pb/msgId.js'
import service from '../pb/service.js'
import user from '../pb/user.js'
// 解构赋值
const { ChatType, ReqChat, ChatUser, GameUser, ServiceTypeId } = chat
const { ClientMsg } = client
const { ErrCode } = code
const { ColorPrizeArea, ColorPrizeAreaRange, ColorRoomConfig, ColorType, ColorBetArea, ColorGameStatus, ColorRoomWaitStart, ColorRoomStart, ColorRoomBetting, ColorRoomEndBet, ColorRoomOpenThreeDice, ColorRoomSettle, NtfColorRoomInfo, NtfColorGameStart, NtfColorBetting, ReqColorBetting, RspColorBetting, ColorBetAreaInfo, NtfColorBetAreaInfo, ColorPrizeType, ColorBetAreaMul, NtfColorEndBetting, NtfColorOpenThreeDice, NtfColorSettle, ColorUser, NtfColorBigUser, NtfColorTrend, ColorBigUser, NtfColorMaintain } = colorgame
const { NtfKickOutUser, ReqEnterRoom, RspEnterRoom, ReqLeaveRoom, RspLeaveRoom, NtfPayoutFail } = common
const { ReqUserLogin, RspUserLogin, NtfUserOnline, NtfUserInService, ReqUserLogout, RspUserLogout, NtfUserOffline } = login
const { ReqMatchRoom, RspMatchRoom, NotifyUserEnterRoom } = match
const { MsgId } = msgId
// 导出语句
export {
ChatType,
ReqChat,
ChatUser,
GameUser,
ServiceTypeId,
ClientMsg,
ErrCode,
ColorPrizeArea,
ColorPrizeAreaRange,
ColorRoomConfig,
ColorType,
ColorBetArea,
ColorGameStatus,
ColorRoomWaitStart,
ColorRoomStart,
ColorRoomBetting,
ColorRoomEndBet,
ColorRoomOpenThreeDice,
ColorRoomSettle,
NtfColorRoomInfo,
NtfColorGameStart,
NtfColorBetting,
ReqColorBetting,
RspColorBetting,
ColorBetAreaInfo,
NtfColorBetAreaInfo,
ColorPrizeType,
ColorBetAreaMul,
NtfColorEndBetting,
NtfColorOpenThreeDice,
NtfColorSettle,
ColorUser,
NtfColorBigUser,
NtfColorTrend,
ColorBigUser,
NtfColorMaintain,
NtfKickOutUser,
ReqEnterRoom,
RspEnterRoom,
ReqLeaveRoom,
RspLeaveRoom,
NtfPayoutFail,
ReqUserLogin,
RspUserLogin,
NtfUserOnline,
NtfUserInService,
ReqUserLogout,
RspUserLogout,
NtfUserOffline,
ReqMatchRoom,
RspMatchRoom,
NotifyUserEnterRoom,
MsgId
};

View File

@ -1,8 +1,4 @@
/**
*
* pb helper
*/
// import { Message } from 'protobufjs'
import { ClientMsg } from '../network/pbExport'
interface ProtoEncodable<T> {
create(data: T): unknown
@ -16,9 +12,18 @@ export class PbHelper {
const message = protoClass.create(data)
return protoClass.encode(message).finish()
}
static DecodeClient(buffer: Uint8Array): [number, Uint8Array] {
const cMsg = ClientMsg.decode(buffer)
return [cMsg.msgId, cMsg.data]
}
static Decode<T>(protoClass: ProtoEncodable<T>, buffer: Uint8Array): T {
return protoClass.decode(buffer)
}
static Uint8ToHex(buffer: Uint8Array): string {
return Array.from(buffer)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join(' ')
}
}
// // 假设有一个生成的 Protobuf 类

View File

@ -1,3 +1,7 @@
import { ClientMsg } from '../network/pbExport'
import { EventType } from '../constant'
import { PbHelper } from '../network/pbHelper'
export class WsClient {
private socket: WebSocket | null = null
private url: string = 'ws://echo.websocket.org' // 测试服务器
@ -81,19 +85,18 @@ export class WsClient {
}
// 发送消息到服务器
Send(message: Uint8Array) {
Send(sid: number, msgId: number, msg: Uint8Array) {
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
// 如果连接不存在或未打开
console.error('websocket未连接,无法发送消息')
return false
}
// var msg: string = ''
// if (typeof message == 'string') {
// msg = message
// } else {
// msg = JSON.stringify(message)
// }
this.socket.send(message)
const clinetMsg = new ClientMsg()
clinetMsg.msgId = msgId
clinetMsg.data = msg
clinetMsg.serviceTid = sid
const buffer = ClientMsg.encode(clinetMsg).finish()
this.socket.send(buffer)
return true
}
@ -104,9 +107,16 @@ export class WsClient {
}
// 收到消息回调
private onMessage(event: MessageEvent) {
console.log('收到消息:', event.data)
window.dispatchEvent(new CustomEvent('ws-message', { detail: event.data }))
private async onMessage(event: MessageEvent) {
try {
const buffer = await event.data.arrayBuffer()
const uint8Array = new Uint8Array(buffer)
// console.log('收到消息:', uint8Array)
console.log('收到消息:', PbHelper.Uint8ToHex(uint8Array))
window.dispatchEvent(new CustomEvent(EventType.Ws, { detail: uint8Array }))
} catch (error) {
console.error('解析网络消息失败:', error)
}
}
// 连接关闭回调

View File

@ -6,7 +6,7 @@ class AddressInfo {
// 网关配置
export class WsConfig {
public static Address: Array<AddressInfo> = [
{ Name: 'super', Address: ['ws://127.0.0.1:5100', 'ws://127.0.0.1:5101'] },
{ Name: 'dev', Address: ['ws://127.0.0.1:5100', 'ws://127.0.0.1:5101'] },
{ Name: 'test', Address: ['ws://114.132.124.145:5100', 'ws://114.132.124.145:5101'] },
]
}

138
tools/pb_exports.js Normal file
View File

@ -0,0 +1,138 @@
const fs = require('fs')
const path = require('path')
/**
* PB 模块导入导出代码生成器
* @param {string} pbDir - PB 文件所在的目录路径
* @param {string} outputFile - 生成的代码输出文件路径
* @param {string} [relativePath='../pb'] - 导入语句中的相对路径
*/
function generatePbImportsAndExports(pbDir, outputFile, relativePath = '../pb') {
try {
// 读取 PB 目录下的所有文件
const files = fs.readdirSync(pbDir)
// 过滤出 .js 文件并去除扩展名
const jsFiles = files.filter((file) => file.endsWith('.js'))
if (jsFiles.length === 0) {
console.warn(`警告: 在目录 ${pbDir} 中没有找到任何 .js 文件`)
return
}
// 生成导入语句
const importStatements = jsFiles
.map((file) => {
const moduleName = file.replace('.js', '')
return `import ${moduleName} from '${relativePath}/${moduleName}.js'`
})
.join('\n')
// 为每个文件生成导出内容
const moduleExports = {}
const allExports = new Set()
const exportSources = {} // 记录每个导出项的来源
jsFiles.forEach((file) => {
const filePath = path.join(pbDir, file)
const code = fs.readFileSync(filePath, 'utf-8')
const moduleName = file.replace('.js', '')
// 提取 $root.X 形式的导出
const rootExports = extractRootExports(code)
// 提取 enum 定义
const enumExports = extractEnumExports(code)
// 合并所有导出
const exports = [...rootExports, ...enumExports]
if (exports.length > 0) {
moduleExports[moduleName] = exports
// 记录导出项的来源
exports.forEach((exp) => {
if (!exportSources[exp]) {
exportSources[exp] = moduleName
allExports.add(exp)
}
})
}
})
if (allExports.size === 0) {
console.warn(`警告: 没有找到任何可导出的类或枚举`)
return
}
// 生成解构赋值语句(只包含首次出现的导出项)
const destructureStatements = Object.entries(moduleExports)
.map(([moduleName, exports]) => {
// 过滤出该模块独有的导出项
const uniqueExports = exports.filter((exp) => exportSources[exp] === moduleName)
if (uniqueExports.length > 0) {
return `const { ${uniqueExports.join(', ')} } = ${moduleName}`
}
return ''
})
.filter(Boolean) // 移除空字符串
.join('\n')
// 生成导出语句(所有唯一导出项)
const exportList = Array.from(allExports)
// 完整的输出代码
const outputCode =
`// 由 pb-import-export-generator 自动生成,请勿手动修改\n\n` +
`// 导入语句\n${importStatements}\n\n` +
`// 解构赋值\n${destructureStatements}\n\n` +
`// 导出语句\nexport {\n ${exportList.join(',\n ')}\n};\n`
// 确保输出目录存在
const outputDir = path.dirname(outputFile)
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
// 写入输出文件
fs.writeFileSync(outputFile, outputCode)
console.log(`成功生成 PB 导入导出代码到 ${outputFile}`)
console.log(`共生成 ${jsFiles.length} 个导入语句和 ${exportList.length} 个导出变量`)
} catch (error) {
console.error('生成 PB 导入导出代码时出错:', error.message)
}
}
/**
* 提取 $root.X 形式的导出
*/
function extractRootExports(code) {
const exports = []
const rootExportRegex = /\$root\.([A-Za-z][A-Za-z0-9_]*)\s*=/g
let match
while ((match = rootExportRegex.exec(code)) !== null) {
exports.push(match[1])
}
return exports
}
/**
* 提取 enum 定义
*/
function extractEnumExports(code) {
const exports = []
const enumRegex = /@enum\s*{.*?}\s*\n\s*\*\s*@exports\s+([A-Za-z][A-Za-z0-9_]*)/g
let match
while ((match = enumRegex.exec(code)) !== null) {
exports.push(match[1])
}
return exports
}
// 示例用法
generatePbImportsAndExports('../assets/scripts/pb', '../assets/scripts/network/pbExport.ts')