拆分网络模块

This commit is contained in:
gongxh
2025-06-08 15:30:12 +08:00
parent 6953f738b0
commit 5b5e4b5936
17 changed files with 24 additions and 894 deletions

View File

@@ -5,8 +5,8 @@
*/
import { game, native, sys } from "cc";
import { ReadNetFile } from "kunpocc-net";
import { ICheckUpdatePromiseResult, IPromiseResult } from "../interface/PromiseResult";
import { ReadNetFile } from "../net/nettools/ReadNetFile";
import { debug, warn } from "../tool/log";
import { Time } from "../tool/Time";
import { Utils } from "../tool/Utils";

View File

@@ -12,19 +12,6 @@ export { MathTool } from "./tool/Math";
export { md5 } from "./tool/MD5";
export { Time } from "./tool/Time";
/** Http */
export * from "./net/http/HttpManager";
export { HttpTask } from "./net/http/HttpTask";
export { IHttpEvent } from "./net/http/IHttpEvent";
export { IHttpRequest } from "./net/http/IHttpRequest";
export { IHttpResponse } from "./net/http/IHttpResponse";
/** Socket */
export { Socket } from "./net/socket/Socket";
/** 读取网络文件 */
export { ReadNetFile } from "./net/nettools/ReadNetFile";
/** UI */
export { Window } from "./fgui/Window";
export { WindowHeader } from "./fgui/WindowHeader";

View File

@@ -1,102 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-28
* @Description: 网络请求管理器
*/
import { HttpRequest } from "./HttpRequest";
import { IHttpEvent } from "./IHttpEvent";
import { IHttpResponse } from "./IHttpResponse";
/** http请求方法 */
export type HttpRequestMethod = "GET" | "POST" | "HEAD" | "PUT"
/** http响应类型 */
export type HttpResponseType = "text" | "json" | "arraybuffer";
/** http响应数据类型 */
export type HttpResponseDataType = string | ArrayBuffer | object;
export class HttpManager {
public static HttpEvent: string = "event::http";
/**
* 发送post请求
* @param {string} url 请求地址
* @param {any} data 请求数据
* @param {HttpResponseType} responseType 响应类型
* @param {IHttpEvent} netEvent 网络事件
* @param {any[]} headers 请求头 [key1, value1, key2, value2, ...] 形式
* @param {number} timeout (单位s) 请求超时时间 默认0 (0表示不超时)
*/
public static post(url: string, data: any, responseType: HttpResponseType = "json", netEvent: IHttpEvent, headers?: any[], timeout: number = 0): HttpRequest {
return this._send("POST", url, data, responseType, netEvent, headers, timeout);
}
/**
* 发送get请求
* @param {string} url 请求地址
* @param {any} data 请求数据
* @param {HttpResponseType} responseType 响应类型
* @param {IHttpEvent} netEvent 网络事件
* @param {any[]} headers 请求头 [key1, value1, key2, value2, ...] 形式
* @param {number} timeout (单位s) 请求超时时间 默认0 (0表示不超时)
*/
public static get(url: string, data: any, responseType: HttpResponseType = "json", netEvent: IHttpEvent, headers?: any[], timeout: number = 0): HttpRequest {
return this._send("GET", url, data, responseType, netEvent, headers, timeout);
}
/**
* 发送put请求
* @param {string} url 请求地址
* @param {any} data 请求数据
* @param {HttpResponseType} responseType 响应类型
* @param {IHttpEvent} netEvent 网络事件
* @param {any[]} headers 请求头 [key1, value1, key2, value2, ...] 形式
* @param {number} timeout (单位s) 请求超时时间 默认0 (0表示不超时)
*/
public static put(url: string, data: any, responseType: HttpResponseType = "json", netEvent: IHttpEvent, headers?: any[], timeout: number = 0): HttpRequest {
return this._send("PUT", url, data, responseType, netEvent, headers, timeout);
}
/**
* 发送head请求
* @param {string} url 请求地址
* @param {any} data 请求数据
* @param {HttpResponseType} responseType 响应类型
* @param {IHttpEvent} netEvent 网络事件
* @param {any[]} headers 请求头 [key1, value1, key2, value2, ...] 形式
* @param {number} timeout (单位s) 请求超时时间 默认0 (0表示不超时)
*/
public static head(url: string, data: any, responseType: HttpResponseType = "json", netEvent: IHttpEvent, headers?: any[], timeout: number = 0): HttpRequest {
return this._send("HEAD", url, data, responseType, netEvent, headers, timeout);
}
/**
* 发送http请求
* @param {HttpRequestMethod} method 请求方式
* @param {string} url 请求地址
* @param {any} data 请求数据
* @param {HttpResponseType} responseType 响应类型
* @param {IHttpEvent} netEvent 网络事件
* @param {any[]} headers 请求头 [key1, value1, key2, value2, ...] 形式
* @param {number} timeout (单位s) 请求超时时间 默认0 (0表示不超时)
* @internal
*/
private static _send(method: HttpRequestMethod, url: string, data: any, responseType: HttpResponseType, netEvent: IHttpEvent, headers?: any[], timeout?: number): HttpRequest {
let http = new HttpRequest()
http.setNetCallback((result: "succeed" | "fail", response: IHttpResponse) => {
switch (result) {
case "succeed":
netEvent?.onComplete(response);
break;
case "fail":
netEvent?.onError(response);
break;
}
});
http.method = method;
http.timeout = timeout;
http.responseType = responseType;
http.send(url, data, headers);
return http;
}
}

View File

@@ -1,168 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-28
* @Description: 网络请求
*/
import { Platform } from "../../global/Platform";
import { HttpRequestMethod, HttpResponseDataType, HttpResponseType } from "./HttpManager";
import { IHttpRequest } from "./IHttpRequest";
import { IHttpResponse } from "./IHttpResponse";
export class HttpRequest implements IHttpRequest, IHttpResponse {
/** 请求方法 */
public method: HttpRequestMethod;
/** xhr实例 @internal */
private _xhr: XMLHttpRequest;
/** 请求超时时间 (s) */
public timeout: number;
/** 响应类型 */
public responseType: HttpResponseType;
/** 信息 */
public message: string;
/** 响应数据 */
public data: HttpResponseDataType;
/** 网络事件回调 @internal */
private _callback: (result: "succeed" | "fail", response: IHttpResponse) => void;
/**
* http相应状态码
* @readonly
* @type {number}
*/
public get statusCode(): number {
return this._xhr.status;
}
/** 相应头 */
public get headers(): any {
return this._xhr.getAllResponseHeaders();
}
constructor() {
this._xhr = new XMLHttpRequest();
}
public setNetCallback(callback: (result: "succeed" | "fail", response: IHttpResponse) => void): void {
this._callback = callback;
}
public send(url: string, data: any, headers: any[]): void {
let xhr = this._xhr;
/** 设置请求超时时间 */
xhr.timeout = this.timeout * 1000;
/** 设置响应类型 */
xhr.responseType = this.responseType;
xhr.onabort = this._onHttpAbort.bind(this);
xhr.onerror = this._onHttpError.bind(this);
xhr.onload = this._onHttpLoad.bind(this);
xhr.ontimeout = this._onHttpTimeout.bind(this);
xhr.open(this.method, encodeURI(url));
if (headers) {
for (let i = 0; i < headers.length; i += 2) {
xhr.setRequestHeader(headers[i], headers[i + 1]);
}
} else if (!Platform.isMobile && Platform.isBrowser) {
if (!data || typeof data == "string") {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
} else {
xhr.setRequestHeader("Content-Type", "application/json");
}
}
xhr.send(data);
}
/**
* 终止Http请求
* @param {boolean} [silent=false] 如果为true则不会回调错误信息
*/
public abort(silent: boolean = false): void {
if (silent) {
this._clear();
}
this._xhr.abort();
}
/**
* 请求中断
* @internal
*/
private _onHttpAbort(): void {
this.message = "request aborted by user";
this.onError();
}
/**
* 请求错误
* @internal
*/
private _onHttpError(): void {
this.message = "request error";
this.onError();
}
/**
* @internal
*/
private _onHttpLoad(): void {
const xhr = this._xhr;
const status = xhr.status !== undefined ? xhr.status : 200;
if (status === 200 || status === 204 || status === 0) {
this.onComplete();
} else {
this.message = 'status:' + xhr.status + 'statusText:' + xhr.statusText + "responseURL:" + xhr.responseURL;
this.onError();
}
}
/**
* 请求超时
* @internal
*/
private _onHttpTimeout(): void {
this.message = "request timeout";
this.onError();
}
/**
* 请求发生错误
* @internal
*/
private onError(): void {
this._callback?.("fail", this);
this._clear();
}
/**
* 请求完成
* @internal
*/
private onComplete(): void {
try {
if (this.responseType == "json") {
this.data = this._xhr.response;
} else if (this.responseType == "arraybuffer") {
this.data = this._xhr.response;
} else if (this.responseType == "text") {
this.data = this._xhr.responseText;
}
this._callback?.("succeed", this);
this._clear();
} catch (e) {
console.warn(`http响应数据解析错误HttpResponseType(${this.responseType})\n url: ${this._xhr.responseURL}\n error: ` + e);
this.onError();
}
}
/**
* 清除请求
* @internal
*/
private _clear(): void {
this._xhr.onabort = null;
this._xhr.onerror = null;
this._xhr.onload = null;
this._xhr.ontimeout = null;
this._callback = null;
}
}

View File

@@ -1,21 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-28
* @Description: 网络任务
*/
import { IHttpEvent } from "./IHttpEvent";
import { IHttpResponse } from "./IHttpResponse";
export abstract class HttpTask implements IHttpEvent {
/** 名称 */
public name: string;
/** 自定义参数 */
public data?: any;
/** 请求完成 */
public abstract onComplete(response: IHttpResponse): void;
/** 请求错误 */
public abstract onError(response: IHttpResponse): void;
/** 请求开始 */
public abstract start(): void;
}

View File

@@ -1,18 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-28
* @Description: 网络事件
*/
import { IHttpResponse } from "./IHttpResponse";
export interface IHttpEvent {
/** 名称 */
name?: string;
/** 自定义参数 */
data?: any;
/** 网络请求成功 */
onComplete(response: IHttpResponse): void;
/** 网络请求失败 */
onError(response: IHttpResponse): void;
}

View File

@@ -1,15 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-28
* @Description: 网络请求接口
*/
import { HttpRequestMethod, HttpResponseType } from "./HttpManager";
export interface IHttpRequest {
/** 请求方法 */
readonly method: HttpRequestMethod;
/** 请求超时时间 (s) */
readonly timeout: number;
/** 响应类型 */
readonly responseType: HttpResponseType;
}

View File

@@ -1,20 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-28
* @Description: 网络响应接口
*/
import { HttpResponseDataType } from "./HttpManager";
export interface IHttpResponse {
/** 信息 */
readonly message: string;
/** 响应数据 */
readonly data: HttpResponseDataType;
/** http状态码 */
readonly statusCode: number;
/** 相应头 */
readonly headers: any;
}

View File

@@ -1,25 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-04-18
* @Description: 读取网络文件内容
*/
import { Time } from "../../tool/Time";
import { Utils } from "../../tool/Utils";
import { HttpManager } from "../http/HttpManager";
import { IHttpResponse } from "../http/IHttpResponse";
export class ReadNetFile {
constructor(res: { url: string, timeout: number, responseType: "text" | "json" | "arraybuffer", onComplete: (data: any) => void, onError: (code: number, message: string) => void }) {
// 地址上带时间戳参数 确保每次请求都到服务器上请求最新配置,而不是拿到上次请求的缓存数据
let url = Utils.addUrlParam(res.url, "timeStamp", `${Time.now()}`);
HttpManager.get(url, null, res.responseType, {
onComplete: (response: IHttpResponse) => {
res.onComplete(response.data);
},
onError: (response: IHttpResponse) => {
res.onError(response.statusCode, response.message);
}
}, null, res.timeout || 6);
}
}

View File

@@ -1,320 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-03-28
* @Description: 网络socket
*/
import { Platform } from "../../global/Platform";
import { debug, warn } from "../../tool/log";
type BinaryType = "blob" | "arraybuffer";
interface SocketOptions {
/**
* 给原生平台 和 web 用
* 一个协议字符串或者一个包含协议字符串的数组。
* 这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议(
* 例如你可能希望一台服务器能够根据指定的协议protocol处理不同类型的交互。
* 如果不指定协议字符串,则假定为空字符串。
*/
protocols?: string[];
/**
* 使用 Blob 对象处理二进制数据。这是默认值
* 使用 ArrayBuffer 对象处理二进制数据
* @url https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType
*/
binaryType?: BinaryType;
/** 超时时间 默认3000毫秒 */
timeout?: number;
}
export class Socket {
/**
* socket对象
* @internal
*/
private _socket: WebSocket | WechatMiniprogram.SocketTask | AliyMiniprogram.SocketTask | BytedanceMiniprogram.SocketTask;
/**
* @param {string} url 要连接的 URL这应该是 WebSocket 服务器将响应的 URL
* @param {SocketOptions} options 可选参数 针对不同平台的一些特殊参数 详细信息见定义
*/
constructor(url: string, options?: SocketOptions) {
if (Platform.isWX) {
this._socket = this.createWechatSocket(url, options?.timeout || 3000, options?.protocols);
} else if (Platform.isAlipay) {
this._socket = this.createAliSocket(url, options?.timeout || 3000, options?.protocols);
} else if (Platform.isBytedance) {
this._socket = this.createBytedanceSocket(url, options?.timeout || 3000, options?.protocols);
} else {
this._socket = this.createOtherSocket(url, options?.binaryType, options?.timeout || 3000, options?.protocols);
}
}
/**
* 微信小游戏创建socket
* @internal
*/
private createWechatSocket(url: string, timeout?: number, protocols?: string[]): WechatMiniprogram.SocketTask {
let socket = wx.connectSocket({
url,
protocols: protocols,
timeout: timeout,
success: () => { debug("socket success") },
fail: () => { warn("socket fail") }
});
socket.onOpen(() => {
this.onopen && this.onopen();
});
socket.onMessage((res: { data: string | ArrayBuffer }) => {
this.onmessage && this.onmessage(res.data);
});
socket.onError((res: { errMsg: string }) => {
// 微信上socket和原生平台以及浏览器不一致 所以这里特殊处理 给他一个默认的错误码
this.onclose?.(1000, res?.errMsg);
});
socket.onClose((res: { code: number, reason: string }) => {
this.onclose?.(res.code, res.reason);
});
return socket;
}
/**
* 支付宝小游戏创建socket
* @internal
*/
private createAliSocket(url: string, timeout?: number, protocols?: string[]): AliyMiniprogram.SocketTask {
let socket = my.connectSocket({
url,
protocols: protocols,
multiple: true,
timeout: timeout,
success: () => { debug("socket success") },
fail: () => { warn("socket fail") }
});
socket.onOpen((info: AliyMiniprogram.OnOpenData) => {
this.onopen && this.onopen();
});
socket.onMessage((info: AliyMiniprogram.OnMessageData) => {
if (!this.onmessage) {
return;
}
if (info.isBuffer) {
if (my.base64ToArrayBuffer) {
this.onmessage(my.base64ToArrayBuffer(info.data.data));
} else if (atob) {
this.onmessage(this.base64ToArrayBuffer(info.data.data));
} else {
this.onmessage(info.data.data);
}
this.onmessage(info.data.data);
} else {
this.onmessage(info.data.data);
}
});
socket.onError((info: { data: AliyMiniprogram.CallBack.Fail }) => {
this.onclose && this.onclose(info.data.error, info.data.errorMessage);
});
socket.onClose((info: { code: number, reason: string, data: { code: number, reason: string } }) => {
this.onclose && this.onclose(info.code, info.reason);
});
return socket;
}
/**
* 抖音小游戏创建socket
* @internal
*/
private createBytedanceSocket(url: string, timeout?: number, protocols?: string[]): BytedanceMiniprogram.SocketTask {
let socket: BytedanceMiniprogram.SocketTask = tt.connectSocket({
url,
protocols: protocols,
success: () => { debug("socket success") },
fail: () => { warn("socket fail") }
});
let timer = setTimeout(() => {
socket.close({});
}, timeout);
socket.onOpen(() => {
timer && clearTimeout(timer);
timer = null;
this.onopen && this.onopen();
});
socket.onMessage((info: { data: string | ArrayBuffer }) => {
this.onmessage && this.onmessage(info.data);
});
socket.onError((res: { errMsg: string }) => {
timer && clearTimeout(timer);
timer = null;
// 微信上socket和原生平台以及浏览器不一致 所以这里特殊处理 给他一个默认的错误码
this.onclose?.(1000, res.errMsg);
});
socket.onClose((res: { code: string, reason: string }) => {
timer && clearTimeout(timer);
timer = null;
this.onclose?.(Number(res.code), res.reason);
});
return socket;
}
/**
* 除微信小游戏、支付宝小游戏、抖音小游戏之外的平台创建socket
* @internal
*/
private createOtherSocket(url: string, binaryType: BinaryType, timeout?: number, protocols?: string[]): WebSocket {
let socket = new WebSocket(url, protocols);
if (binaryType) {
socket.binaryType = binaryType;
}
let timer = setTimeout(() => {
socket.close();
}, timeout);
socket.onopen = () => {
timer && clearTimeout(timer);
timer = null;
this.onopen?.();
}
socket.onmessage = (event: MessageEvent) => {
this.onmessage?.(event.data);
}
socket.onerror = () => {
timer && clearTimeout(timer);
timer = null;
this.onerror?.();
}
socket.onclose = (event: CloseEvent) => {
timer && clearTimeout(timer);
timer = null;
this.onclose?.(event?.code, event?.reason);
}
return socket;
}
/**
* 发送文本数据
* @param data - 文本数据
*/
public send(data: string): void {
if (Platform.isWX) {
(this._socket as WechatMiniprogram.SocketTask).send({ data: data });
} else if (Platform.isAlipay) {
(this._socket as AliyMiniprogram.SocketTask).send({ data: data });
} else if (Platform.isBytedance) {
(this._socket as BytedanceMiniprogram.SocketTask).send({ data: data });
} else {
(this._socket as WebSocket).send(data);
}
}
/**
* 发送二进制数据
* @param data - 二进制数据
*/
public sendBuffer(data: ArrayBuffer): void {
if (Platform.isWX) {
(this._socket as WechatMiniprogram.SocketTask).send({ data: data });
} else if (Platform.isAlipay) {
if (my.arrayBufferToBase64) {
(this._socket as AliyMiniprogram.SocketTask).send({ data: my.arrayBufferToBase64(data), isBuffer: true });
} else if (btoa) {
(this._socket as AliyMiniprogram.SocketTask).send({ data: this.uint8ArrayToBase64(new Uint8Array(data)), isBuffer: true });
} else {
(this._socket as AliyMiniprogram.SocketTask).send({ data: data });
}
} else if (Platform.isBytedance) {
(this._socket as BytedanceMiniprogram.SocketTask).send({ data: data });
} else {
(this._socket as WebSocket).send(data);
}
}
/**
* 客户端主动断开
* @param code - 关闭代码: 如果没有传这个参数默认使用1000, 客户端可使用的数字范围: [3001-3999]
* @param reason - 关闭原因: 一个人类可读的字符串,它解释了连接关闭的原因。这个 UTF-8 编码的字符串不能超过 123 个字节
*/
public close(code?: number, reason?: string): void {
if (Platform.isWX) {
(this._socket as WechatMiniprogram.SocketTask).close({ code: code, reason: reason });
} else if (Platform.isAlipay) {
(this._socket as AliyMiniprogram.SocketTask).close({ code: code, reason: reason });
} else if (Platform.isBytedance) {
(this._socket as BytedanceMiniprogram.SocketTask).close({ code: code, reason: reason });
} else {
(this._socket as WebSocket).close(code, reason);
}
}
/**
* 获取socket示例
* 在微信小游戏、支付宝小游戏、抖音小游戏 返回的是他们平台的socket实例类型
*/
public socket<T>(): T {
return this._socket as T;
}
/**
* socket已准备好 open成功
* 当前连接已经准备好发送和接受数据
*/
public onopen: () => void;
/**
* 接收到服务端发送的消息
* @param data - 消息数据
*/
public onmessage: (data: string | ArrayBuffer) => void;
/**
* 监听可能发生的错误,一般用不到
*/
public onerror: () => void;
/**
* 关闭连接
* @param code - 关闭代码
* @param reason - 关闭原因
*/
public onclose: (code: number, reason: string) => void;
/**
* 二进制数据转成Base64 (支付宝用)
* @internal
*/
private uint8ArrayToBase64(u8Array: Uint8Array): string {
let CHUNK_SIZE = 0x8000; // 32768
let index = 0;
let length = u8Array.length;
let result = '';
let slice: Uint8Array<ArrayBufferLike>;;
// 分段处理,避免`btoa`输入字符串过长
for (; index < length; index += CHUNK_SIZE) {
slice = u8Array.subarray(index, Math.min(index + CHUNK_SIZE, length));
// 将Uint8Array转换为字符串并使用btoa进行Base64编码
result += btoa(String.fromCharCode(...slice));
}
return result;
}
/**
* base64转成ArrayBuffer (支付宝用)
* @internal
*/
private base64ToArrayBuffer(base64: string): ArrayBuffer {
let binary_string = atob(base64);
let len = binary_string.length;
let bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
}