mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-06-03 18:49:23 +08:00
1.优化MVVM框架代码
2.MVVM组件支持脚本绑定数据 3.VMLabel组件支持时间格式化
This commit is contained in:
@@ -12,14 +12,27 @@ Date.prototype.format = function (format: string): string {
|
||||
const hours: number = this.getHours();
|
||||
const minutes: number = this.getMinutes();
|
||||
const seconds: number = this.getSeconds();
|
||||
const milliseconds: number = this.getMilliseconds();
|
||||
|
||||
return format
|
||||
let r = format
|
||||
.replace('yy', year.toString())
|
||||
.replace('mm', (month < 10 ? '0' : '') + month)
|
||||
.replace('dd', (day < 10 ? '0' : '') + day)
|
||||
.replace('hh', (hours < 10 ? '0' : '') + hours)
|
||||
.replace('mm', (minutes < 10 ? '0' : '') + minutes)
|
||||
.replace('ss', (seconds < 10 ? '0' : '') + seconds);
|
||||
|
||||
if (milliseconds < 10) {
|
||||
r = r.replace('ms', '00' + milliseconds);
|
||||
}
|
||||
else if (milliseconds < 100) {
|
||||
r = r.replace('ms', '0' + milliseconds);
|
||||
}
|
||||
else {
|
||||
r = r.replace('ms', milliseconds.toString());
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
export { };
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/**
|
||||
* 数值格式化函数, 通过语义解析自动设置值的范围
|
||||
* //整数
|
||||
* 1:def(0)//显示一个默认值
|
||||
*/
|
||||
import { oops } from "../../core/Oops";
|
||||
|
||||
/** 数值格式化函数, 通过语义解析自动设置值的范围 */
|
||||
class StringFormat {
|
||||
deal(value: number | string, format: string): string {
|
||||
if (format === '') return value as string;
|
||||
@@ -21,58 +19,58 @@ class StringFormat {
|
||||
switch (func) {
|
||||
case 'int': res = this.int(value); break;
|
||||
case 'fix': res = this.fix(value, num); break;
|
||||
case 'kmbt': res = this.KMBT(value); break;
|
||||
case 'kmbt': res = this.kmbt(value); break;
|
||||
case 'per': res = this.per(value, num); break;
|
||||
case 'sep': res = this.sep(value); break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case 'tstamp': res = this.time_stamp(value); break;
|
||||
case 'tm': res = this.time_m(value); break;
|
||||
case 'ts': res = this.time_s(value); break;
|
||||
case 'tms': res = this.time_ms(value); break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (func) {
|
||||
case 'limit': res = this.limit(value, num); break;
|
||||
|
||||
default:
|
||||
break;
|
||||
default: res = value; break;
|
||||
}
|
||||
res = value;
|
||||
}
|
||||
|
||||
return res as string;
|
||||
}
|
||||
|
||||
// 将数字按分号显示
|
||||
/** 将数字按分号显示 */
|
||||
private sep(value: number) {
|
||||
let num = Math.round(value).toString();
|
||||
return num.replace(new RegExp('(\\d)(?=(\\d{3})+$)', 'ig'), "$1,");
|
||||
}
|
||||
|
||||
// 将数字按分显示 00:00 显示 (ms制)
|
||||
/** 将数字按分显示 00:00 显示 (时:分) */
|
||||
private time_m(value: number) {
|
||||
//todo
|
||||
return new Date(value).format('hh:ss');
|
||||
}
|
||||
|
||||
// 将数字按秒显示 00:00:00 显示 (ms制)
|
||||
/** 将数字按秒显示 00:00:00 显示 (时:分:秒) */
|
||||
private time_s(value: number) {
|
||||
//todo
|
||||
return new Date(value).format('hh:mm:ss');
|
||||
}
|
||||
|
||||
// 将数字按 0:00:00:000 显示 (ms制)
|
||||
/** 将数字按 0:00:00:000 显示 (时:分:秒:毫秒) */
|
||||
private time_ms(value: number) {
|
||||
//todo
|
||||
return new Date(value).format('hh:mm:ss:ms');
|
||||
}
|
||||
|
||||
// 将时间戳显示为详细的内容
|
||||
private timeStamp(value: number) {
|
||||
//todo
|
||||
return new Date(value).toString()
|
||||
/** 将时间戳显示为详细的内容 */
|
||||
private time_stamp(value: number) {
|
||||
return new Date(value).format('yy-mm-dd hh:mm:ss');
|
||||
}
|
||||
|
||||
/** [value:int] 将取值0~1 变成 1~100,可以指定修饰的小数位数 */
|
||||
private per(value: number, fd: number) {
|
||||
return Math.round(value * 100).toFixed(fd);
|
||||
let r = value * 100;
|
||||
return r.toFixed(fd);
|
||||
}
|
||||
|
||||
/** [value:int] 将取值变成整数 */
|
||||
@@ -80,7 +78,7 @@ class StringFormat {
|
||||
return Math.round(value);
|
||||
}
|
||||
|
||||
/** [value:fix2]数值转换为小数*/
|
||||
/** [value:fix2]数值转换为小数 */
|
||||
private fix(value: number, fd: number) {
|
||||
return value.toFixed(fd)
|
||||
}
|
||||
@@ -91,18 +89,21 @@ class StringFormat {
|
||||
}
|
||||
|
||||
/** 将数字缩短显示为KMBT单位 大写,目前只支持英文 */
|
||||
private KMBT(value: number, lang: string = 'en') {
|
||||
private kmbt(value: number) {
|
||||
//10^4=万, 10^8=亿,10^12=兆,10^16=京,
|
||||
let counts = [1000, 1000000, 1000000000, 1000000000000];
|
||||
let units = ['', 'K', 'M', 'B', 'T'];
|
||||
let counts: number[] = null!;
|
||||
let units: string[] = null!;
|
||||
|
||||
switch (lang) {
|
||||
switch (oops.language.current) {
|
||||
case 'zh':
|
||||
//10^4=万, 10^8=亿,10^12=兆,10^16=京,
|
||||
let counts = [10000, 100000000, 1000000000000, 10000000000000000];
|
||||
let units = ['', '万', '亿', '兆', '京'];
|
||||
counts = [10000, 100000000, 1000000000000, 10000000000000000];
|
||||
units = ['', '万', '亿', '兆', '京'];
|
||||
break;
|
||||
case 'en':
|
||||
counts = [1000, 1000000, 1000000000, 1000000000000];
|
||||
units = ['', 'K', 'M', 'B', 'T'];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -110,12 +111,12 @@ class StringFormat {
|
||||
return this.compressUnit(value, counts, units, 2);
|
||||
}
|
||||
|
||||
//压缩任意单位的数字,后缀加上单位文字
|
||||
/** 压缩任意单位的数字,后缀加上单位文字 */
|
||||
private compressUnit(value: any, valueArr: number[], unitArr: string[], fixNum: number = 2): string {
|
||||
let counts = valueArr;
|
||||
let units = unitArr;
|
||||
let res: string = "";
|
||||
let index;
|
||||
let index: number;
|
||||
for (index = 0; index < counts.length; index++) {
|
||||
const e = counts[index];
|
||||
if (value < e) {
|
||||
@@ -127,11 +128,10 @@ class StringFormat {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return res + units[index];
|
||||
}
|
||||
}
|
||||
|
||||
/**格式化处理函数 */
|
||||
/** 格式化处理函数 */
|
||||
export let StringFormatFunction = new StringFormat();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CCString, Component, Enum, log, Node, _decorator } from "cc";
|
||||
import { _decorator, CCString, Component, Enum, log, Node } from "cc";
|
||||
import { VMEnv } from "./VMEnv";
|
||||
|
||||
const { ccclass, property, executeInEditMode, menu, help } = _decorator;
|
||||
@@ -36,10 +36,10 @@ export default class MVCompsEdit extends Component {
|
||||
return this.actionType === ACTION_MODE.SEARCH_COMPONENT;
|
||||
}
|
||||
})
|
||||
public get findTrigger() {
|
||||
get findTrigger() {
|
||||
return false;
|
||||
}
|
||||
public set findTrigger(v: boolean) {
|
||||
set findTrigger(v: boolean) {
|
||||
this.setComponents(0);
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ export default class MVCompsEdit extends Component {
|
||||
return this.actionType === ACTION_MODE.ENABLE_COMPONENT;
|
||||
}
|
||||
})
|
||||
public get enableTrigger() {
|
||||
get enableTrigger() {
|
||||
return false;
|
||||
}
|
||||
public set enableTrigger(v: boolean) {
|
||||
set enableTrigger(v: boolean) {
|
||||
this.setComponents(1);
|
||||
}
|
||||
|
||||
@@ -64,10 +64,10 @@ export default class MVCompsEdit extends Component {
|
||||
return this.actionType === ACTION_MODE.ENABLE_COMPONENT;
|
||||
}
|
||||
})
|
||||
public get disableTrigger() {
|
||||
get disableTrigger() {
|
||||
return false;
|
||||
}
|
||||
public set disableTrigger(v: boolean) {
|
||||
set disableTrigger(v: boolean) {
|
||||
this.setComponents(2);
|
||||
}
|
||||
|
||||
@@ -88,10 +88,10 @@ export default class MVCompsEdit extends Component {
|
||||
return this.allowDelete && this.actionType === ACTION_MODE.DELETE_COMPONENT;
|
||||
}
|
||||
})
|
||||
public get deleteTrigger() {
|
||||
get deleteTrigger() {
|
||||
return false;
|
||||
}
|
||||
public set deleteTrigger(v: boolean) {
|
||||
set deleteTrigger(v: boolean) {
|
||||
this.setComponents(3);
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ export default class MVCompsEdit extends Component {
|
||||
return this.actionType === ACTION_MODE.REPLACE_WATCH_PATH;
|
||||
}
|
||||
})
|
||||
public get replaceTrigger() {
|
||||
get replaceTrigger() {
|
||||
return false;
|
||||
}
|
||||
public set replaceTrigger(v: boolean) {
|
||||
set replaceTrigger(v: boolean) {
|
||||
this.setComponents(4);
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ export default class MVCompsEdit extends Component {
|
||||
|
||||
getNodePath(node: Node) {
|
||||
let parent = node;
|
||||
let array = [];
|
||||
let array: string[] = [];
|
||||
while (parent) {
|
||||
let p = parent.getParent();
|
||||
if (p) {
|
||||
@@ -282,4 +282,4 @@ export default class MVCompsEdit extends Component {
|
||||
}
|
||||
return array.reverse().join('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ const COMP_ARRAY_CHECK = [
|
||||
['cc.Toggle', 'isChecked', true]
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* [VM-Custom]
|
||||
* 自定义数值监听, 可以快速对该节点上任意一个组件上的属性进行双向绑定
|
||||
@@ -170,4 +169,4 @@ export class VMCustom extends VMBase {
|
||||
this._oldValue = this.getComponentValue();
|
||||
this.onValueController(newValue, oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { EDITOR } from "cc/env";
|
||||
export class VMEnv {
|
||||
/** 编辑状态 */
|
||||
static get editor() {
|
||||
// @ts-ignore
|
||||
return EDITOR && !cc.GAME_VIEW;
|
||||
return EDITOR;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export default class VMEvent extends VMBase {
|
||||
@property({
|
||||
tooltip: '使用模板模式,可以使用多路径监听'
|
||||
})
|
||||
public templateMode: boolean = false;
|
||||
templateMode: boolean = false;
|
||||
|
||||
@property({
|
||||
tooltip: '监听获取值的路径',
|
||||
@@ -65,7 +65,7 @@ export default class VMEvent extends VMBase {
|
||||
tooltip: '过滤模式,会根据条件过滤掉时间的触发',
|
||||
type: Enum(FILTER_MODE)
|
||||
})
|
||||
public filterMode: FILTER_MODE = FILTER_MODE.none;
|
||||
filterMode: FILTER_MODE = FILTER_MODE.none;
|
||||
|
||||
@property({
|
||||
visible: function () {
|
||||
@@ -73,7 +73,7 @@ export default class VMEvent extends VMBase {
|
||||
return this.filterMode !== FILTER_MODE.none
|
||||
}
|
||||
})
|
||||
public compareValue: string = '';
|
||||
compareValue: string = '';
|
||||
|
||||
@property([EventHandler])
|
||||
changeEvents: EventHandler[] = [];
|
||||
|
||||
@@ -186,4 +186,4 @@ export default class VMLabel extends VMBase {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,4 +146,4 @@ export default class VMModify extends VMBase {
|
||||
if (int) { a = Math.round(a) }
|
||||
this.VM.setValue(this.watchPath, this.clampValue(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ export default class VMParent extends GameComponent {
|
||||
*/
|
||||
onLoad() {
|
||||
if (this.data == null) return;
|
||||
this.onBind();
|
||||
|
||||
this.tag = '_temp' + '<' + this.node.uuid.replace('.', '') + '>';
|
||||
VM.add(this.data, this.tag);
|
||||
// log(VM['_mvs'],this.tag)
|
||||
@@ -47,8 +49,6 @@ export default class VMParent extends GameComponent {
|
||||
this.replaceVMPath(comp, this.tag)
|
||||
}
|
||||
// console.groupEnd()
|
||||
|
||||
this.onBind();
|
||||
}
|
||||
|
||||
/**在 onLoad 完成 和 start() 之前调用,你可以在这里进行初始化数据等操作 */
|
||||
@@ -119,4 +119,4 @@ export default class VMParent extends GameComponent {
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,4 +95,4 @@ export default class VMProgress extends VMCustom {
|
||||
|
||||
this.setComponentValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,4 +295,4 @@ export default class VMState extends VMBase {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ function getValueFromPath(obj: any, path: string, def?: any, tag: string | null
|
||||
/**
|
||||
* ModelViewer 类
|
||||
*/
|
||||
class ViewModel<T>{
|
||||
class ViewModel<T> {
|
||||
constructor(data: T, tag: string) {
|
||||
new JsonOb(data, this._callback.bind(this));
|
||||
this.$data = data;
|
||||
@@ -149,6 +149,9 @@ class VMManager {
|
||||
*/
|
||||
getValue(path: string, def?: any): any {
|
||||
path = path.trim(); // 防止空格,自动剔除
|
||||
|
||||
if (path === '') return '';
|
||||
|
||||
let rs = path.split('.');
|
||||
if (rs.length < 2) { console.error('Get Value Cant find path:' + path); return; };
|
||||
let vm = this.get(rs[0]);
|
||||
@@ -215,10 +218,8 @@ class VMManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 整数、小数、时间、缩写
|
||||
|
||||
/**
|
||||
* VM管理对象,使用文档:
|
||||
* https://github.com/wsssheep/cocos_creator_mvvm_tools/blob/master/docs/ViewModelScript.md
|
||||
* https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12037849&doc_id=2873565
|
||||
*/
|
||||
export let VM = new VMManager();
|
||||
@@ -45,10 +45,10 @@ export class BhvRollNumber extends Component {
|
||||
@property({
|
||||
tooltip: '滚动的目标值'
|
||||
})
|
||||
public get targetValue(): number {
|
||||
get targetValue(): number {
|
||||
return this._targetValue;
|
||||
}
|
||||
public set targetValue(v: number) {
|
||||
set targetValue(v: number) {
|
||||
this._targetValue = v;
|
||||
this.scroll();//数据变动了就开始滚动
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ export class BhvSwitchPage extends Component {
|
||||
|
||||
@property
|
||||
private _index: number = 0;
|
||||
public get index(): number {
|
||||
get index(): number {
|
||||
return this._index;
|
||||
}
|
||||
@property({
|
||||
type: CCInteger
|
||||
})
|
||||
public set index(v: number) {
|
||||
set index(v: number) {
|
||||
if (this.isChanging) return;
|
||||
v = Math.round(v);
|
||||
let count = this.node.children.length - 1;
|
||||
@@ -49,7 +49,7 @@ export class BhvSwitchPage extends Component {
|
||||
|
||||
private _isChanging: boolean = false;
|
||||
/**只读,是否在changing 的状态 */
|
||||
public get isChanging(): boolean {
|
||||
get isChanging(): boolean {
|
||||
return this._isChanging;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class BhvSwitchPage extends Component {
|
||||
showNode.active = true;
|
||||
}
|
||||
|
||||
public next(): boolean {
|
||||
next(): boolean {
|
||||
if (this.isChanging) {
|
||||
return false;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export class BhvSwitchPage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
public previous(): boolean {
|
||||
previous(): boolean {
|
||||
if (this.isChanging) {
|
||||
return false;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export class BhvSwitchPage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
public setEventIndex(e: any, index: any): boolean {
|
||||
setEventIndex(e: any, index: any): boolean {
|
||||
if (this.index >= 0 && this.index != null && this.isChanging === false) {
|
||||
this.index = index;
|
||||
return true;
|
||||
@@ -114,4 +114,4 @@ export class BhvSwitchPage extends Component {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/libs/network.meta
Normal file
9
assets/libs/network.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "c378c808-8d92-4f96-9eb6-a122b5f1716f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
3
assets/libs/network/.network.md
Normal file
3
assets/libs/network/.network.md
Normal file
@@ -0,0 +1,3 @@
|
||||
游戏网络库
|
||||
1. Http
|
||||
2. WebSocket
|
||||
317
assets/libs/network/HttpRequest.ts
Normal file
317
assets/libs/network/HttpRequest.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 18:10:50
|
||||
*/
|
||||
import { error, warn } from "cc";
|
||||
|
||||
/**
|
||||
* 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容
|
||||
* https://store.cocos.com/app/detail/5877
|
||||
*/
|
||||
|
||||
/** 当前请求地址集合 */
|
||||
var urls: any = {};
|
||||
/** 请求参数 */
|
||||
var reqparams: any = {};
|
||||
|
||||
type HttpCallback = (ret: HttpReturn) => void;
|
||||
|
||||
/** 请求事件 */
|
||||
export enum HttpEvent {
|
||||
/** 断网 */
|
||||
NO_NETWORK = "http_request_no_network",
|
||||
/** 未知错误 */
|
||||
UNKNOWN_ERROR = "http_request_unknown_error",
|
||||
/** 请求超时 */
|
||||
TIMEOUT = "http_request_timout"
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP请求返回值
|
||||
*/
|
||||
export class HttpReturn {
|
||||
/** 是否请求成功 */
|
||||
isSucc: boolean = false;
|
||||
/** 请求返回数据 */
|
||||
res?: any;
|
||||
/** 请求错误数据 */
|
||||
err?: any;
|
||||
}
|
||||
|
||||
/** HTTP请求 */
|
||||
export class HttpRequest {
|
||||
/** 服务器地址 */
|
||||
server: string = "http://127.0.0.1/";
|
||||
/** 请求超时时间 */
|
||||
timeout: number = 10000;
|
||||
/** 自定义请求头信息 */
|
||||
private header: Map<string, string> = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* 添加自定义请求头信息
|
||||
* @param name 信息名
|
||||
* @param value 信息值
|
||||
*/
|
||||
addHeader(name: string, value: string) {
|
||||
this.header.set(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET请求
|
||||
* @param name 协议名
|
||||
* @param onComplete 请求完整回调方法
|
||||
* @param params 查询参数
|
||||
* @example
|
||||
var param = '{"uid":12345}'
|
||||
var complete = (ret: HttpReturn) => {
|
||||
console.log(ret.res);
|
||||
}
|
||||
oops.http.getWithParams(name, complete, param);
|
||||
*/
|
||||
get(name: string, onComplete: HttpCallback, params: any = null) {
|
||||
this.sendRequest(name, params, false, onComplete)
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET请求
|
||||
* @param name 协议名
|
||||
* @param params 查询参数
|
||||
* @example
|
||||
var txt = await oops.http.getAsync(name);
|
||||
if (txt.isSucc) {
|
||||
console.log(txt.res);
|
||||
}
|
||||
*/
|
||||
getAsync(name: string, params: any = null): Promise<HttpReturn> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRequest(name, params, false, (ret: HttpReturn) => {
|
||||
resolve(ret);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET请求非文本格式数据
|
||||
* @param name 协议名
|
||||
* @param onComplete 请求完整回调方法
|
||||
* @param params 查询参数
|
||||
*/
|
||||
getByArraybuffer(name: string, onComplete: HttpCallback, params: any = null) {
|
||||
this.sendRequest(name, params, false, onComplete, 'arraybuffer', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP GET请求非文本格式数据
|
||||
* @param name 协议名
|
||||
* @param params 查询参数
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
getAsyncByArraybuffer(name: string, params: any = null): Promise<HttpReturn> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRequest(name, params, false, (ret: HttpReturn) => {
|
||||
resolve(ret);
|
||||
}, 'arraybuffer', false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST请求
|
||||
* @param name 协议名
|
||||
* @param params 查询参数
|
||||
* @param onComplete 请求完整回调方法
|
||||
* @example
|
||||
var param = '{"LoginCode":"donggang_dev","Password":"e10adc3949ba59abbe56e057f20f883e"}'
|
||||
var complete = (ret: HttpReturn) => {
|
||||
console.log(ret.res);
|
||||
}
|
||||
oops.http.post(name, complete, param);
|
||||
*/
|
||||
post(name: string, onComplete: HttpCallback, params: any = null) {
|
||||
this.sendRequest(name, params, true, onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST请求
|
||||
* @param name 协议名
|
||||
* @param params 查询参数
|
||||
*/
|
||||
postAsync(name: string, params: any = null): Promise<HttpReturn> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRequest(name, params, true, (ret: HttpReturn) => {
|
||||
resolve(ret);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消请求中的请求
|
||||
* @param name 协议名
|
||||
*/
|
||||
abort(name: string) {
|
||||
var xhr = urls[this.server + name];
|
||||
if (xhr) {
|
||||
xhr.abort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字符串形式的参数
|
||||
* @param params 参数对象
|
||||
* @returns 参数字符串
|
||||
*/
|
||||
private getParamString(params: any) {
|
||||
var result = "";
|
||||
for (var name in params) {
|
||||
let data = params[name];
|
||||
if (data instanceof Object) {
|
||||
for (var key in data)
|
||||
result += `${key}=${data[key]}&`;
|
||||
}
|
||||
else {
|
||||
result += `${name}=${data}&`;
|
||||
}
|
||||
}
|
||||
return result.substring(0, result.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Http请求
|
||||
* @param name(string) 请求地址
|
||||
* @param params(JSON) 请求参数
|
||||
* @param isPost(boolen) 是否为POST方式
|
||||
* @param callback(function) 请求成功回调
|
||||
* @param responseType(string) 响应类型
|
||||
* @param isOpenTimeout(boolean) 是否触发请求超时错误
|
||||
*/
|
||||
private sendRequest(name: string,
|
||||
params: any,
|
||||
isPost: boolean,
|
||||
onComplete: HttpCallback,
|
||||
responseType?: string,
|
||||
isOpenTimeout: boolean = true) {
|
||||
if (name == null || name == '') {
|
||||
error("请求地址不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
var url: string, newUrl: string, paramsStr: string = "";
|
||||
if (name.toLocaleLowerCase().indexOf("http") == 0) {
|
||||
url = name;
|
||||
}
|
||||
else {
|
||||
url = this.server + name;
|
||||
}
|
||||
|
||||
if (params) {
|
||||
paramsStr = this.getParamString(params);
|
||||
if (url.indexOf("?") > -1)
|
||||
newUrl = url + "&" + paramsStr;
|
||||
else
|
||||
newUrl = url + "?" + paramsStr;
|
||||
}
|
||||
else {
|
||||
newUrl = url;
|
||||
}
|
||||
|
||||
if (urls[newUrl] != null && reqparams[newUrl] == paramsStr) {
|
||||
warn(`地址【${url}】已正在请求中,不能重复请求`);
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
// 防重复请求功能
|
||||
urls[newUrl] = xhr;
|
||||
reqparams[newUrl] = paramsStr;
|
||||
|
||||
if (isPost) {
|
||||
xhr.open("POST", url);
|
||||
}
|
||||
else {
|
||||
xhr.open("GET", newUrl);
|
||||
}
|
||||
|
||||
// 添加自定义请求头信息
|
||||
for (const [key, value] of this.header) {
|
||||
xhr.setRequestHeader(key, value);
|
||||
}
|
||||
// xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
|
||||
// xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
|
||||
|
||||
var data: any = {};
|
||||
data.url = url;
|
||||
data.params = params;
|
||||
|
||||
// 请求超时
|
||||
if (isOpenTimeout) {
|
||||
xhr.timeout = this.timeout;
|
||||
xhr.ontimeout = () => {
|
||||
this.deleteCache(newUrl);
|
||||
|
||||
ret.isSucc = false;
|
||||
ret.err = HttpEvent.TIMEOUT; // 超时
|
||||
onComplete(data);
|
||||
}
|
||||
}
|
||||
|
||||
// 响应结果
|
||||
var ret: HttpReturn = new HttpReturn();
|
||||
|
||||
xhr.onloadend = () => {
|
||||
if (xhr.status == 500) {
|
||||
this.deleteCache(newUrl);
|
||||
|
||||
ret.isSucc = false;
|
||||
ret.err = HttpEvent.NO_NETWORK; // 断网
|
||||
onComplete(ret);
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onerror = () => {
|
||||
this.deleteCache(newUrl);
|
||||
|
||||
ret.isSucc = false;
|
||||
if (xhr.readyState == 0 || xhr.readyState == 1 || xhr.status == 0) {
|
||||
ret.err = HttpEvent.NO_NETWORK; // 断网
|
||||
}
|
||||
else {
|
||||
ret.err = HttpEvent.UNKNOWN_ERROR; // 未知错误
|
||||
}
|
||||
|
||||
onComplete(ret);
|
||||
};
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState != 4) return;
|
||||
|
||||
this.deleteCache(newUrl);
|
||||
|
||||
if (xhr.status == 200 && onComplete) {
|
||||
ret.isSucc = true;
|
||||
if (responseType == 'arraybuffer') {
|
||||
xhr.responseType = responseType; // 加载非文本格式
|
||||
ret.res = xhr.response;
|
||||
}
|
||||
else {
|
||||
ret.res = JSON.parse(xhr.response);
|
||||
}
|
||||
onComplete(ret);
|
||||
}
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
if (params == null || params == "") {
|
||||
xhr.send();
|
||||
}
|
||||
else {
|
||||
xhr.send(paramsStr);
|
||||
}
|
||||
}
|
||||
|
||||
private deleteCache(url: string) {
|
||||
delete urls[url];
|
||||
delete reqparams[url];
|
||||
}
|
||||
}
|
||||
9
assets/libs/network/HttpRequest.ts.meta
Normal file
9
assets/libs/network/HttpRequest.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "806e5b7c-51eb-45cb-8b29-9fcf5d9ee6a7",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
92
assets/libs/network/NetInterface.ts
Normal file
92
assets/libs/network/NetInterface.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 18:31:18
|
||||
*/
|
||||
|
||||
/*
|
||||
* 网络相关接口定义
|
||||
*/
|
||||
export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView);
|
||||
export type NetCallFunc = (data: any) => void;
|
||||
|
||||
/** 请求协议 */
|
||||
export interface IRequestProtocol {
|
||||
/** 协议命令编号 */
|
||||
cmd: string,
|
||||
/** 回调方法名 */
|
||||
callback?: string,
|
||||
/** 是否压缩 */
|
||||
isCompress: boolean,
|
||||
/** 渠道编号 */
|
||||
channelid: number,
|
||||
/** 消息内容 */
|
||||
data?: any;
|
||||
}
|
||||
|
||||
/** 响应协议 */
|
||||
export interface IResponseProtocol {
|
||||
/** 响应协议状态码 */
|
||||
code: number,
|
||||
/** 数据是否压缩 */
|
||||
isCompress: boolean,
|
||||
/** 协议数据 */
|
||||
data?: any,
|
||||
/** 协议回调方法名 */
|
||||
callback?: string
|
||||
}
|
||||
|
||||
/** 回调对象 */
|
||||
export interface CallbackObject {
|
||||
target: any, // 回调对象,不为null时调用target.callback(xxx)
|
||||
callback: NetCallFunc, // 回调函数
|
||||
}
|
||||
|
||||
/** 请求对象 */
|
||||
export interface RequestObject {
|
||||
buffer: NetData, // 请求的Buffer
|
||||
rspCmd: string, // 等待响应指令
|
||||
rspObject: CallbackObject | null, // 等待响应的回调对象
|
||||
}
|
||||
|
||||
/** 协议辅助接口 */
|
||||
export interface IProtocolHelper {
|
||||
/** 返回包头长度 */
|
||||
getHeadlen(): number;
|
||||
/** 返回一个心跳包 */
|
||||
getHearbeat(): NetData;
|
||||
/** 返回整个包的长度 */
|
||||
getPackageLen(msg: NetData): number;
|
||||
/** 检查包数据是否合法(避免客户端报错崩溃) */
|
||||
checkResponsePackage(msg: IResponseProtocol): boolean;
|
||||
/** 处理请求包数据 */
|
||||
handlerRequestPackage(reqProtocol: IRequestProtocol): string;
|
||||
/** 处理响应包数据 */
|
||||
handlerResponsePackage(respProtocol: IResponseProtocol): boolean;
|
||||
/** 返回包的id或协议类型 */
|
||||
getPackageId(msg: IResponseProtocol): string;
|
||||
}
|
||||
|
||||
export type SocketFunc = (event: any) => void;
|
||||
export type MessageFunc = (msg: NetData) => void;
|
||||
|
||||
/** Socket接口 */
|
||||
export interface ISocket {
|
||||
onConnected: SocketFunc | null; // 连接回调
|
||||
onMessage: MessageFunc | null; // 消息回调
|
||||
onError: SocketFunc | null; // 错误回调
|
||||
onClosed: SocketFunc | null; // 关闭回调
|
||||
|
||||
connect(options: any): any; // 连接接口
|
||||
send(buffer: NetData): number; // 数据发送接口
|
||||
close(code?: number, reason?: string): void; // 关闭接口
|
||||
}
|
||||
|
||||
/** 网络提示接口 */
|
||||
export interface INetworkTips {
|
||||
connectTips(isShow: boolean): void;
|
||||
reconnectTips(isShow: boolean): void;
|
||||
requestTips(isShow: boolean): void;
|
||||
responseErrorCode(code: number): void;
|
||||
}
|
||||
11
assets/libs/network/NetInterface.ts.meta
Normal file
11
assets/libs/network/NetInterface.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d9f8bf82-57af-45c8-ac27-51d0a33ad69d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
148
assets/libs/network/NetManager.ts
Normal file
148
assets/libs/network/NetManager.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 18:10:50
|
||||
*/
|
||||
import { CallbackObject, IRequestProtocol, NetData } from "./NetInterface";
|
||||
import { NetConnectOptions, NetNode } from "./NetNode";
|
||||
|
||||
/**
|
||||
* 使用流程文档可参考、简化与服务器对接、使用新版API体验,可进入下面地址获取新版本,替换network目录中的内容
|
||||
* https://store.cocos.com/app/detail/5877
|
||||
*/
|
||||
|
||||
/*
|
||||
* 网络节点管理类
|
||||
*/
|
||||
export class NetManager {
|
||||
private static _instance: NetManager;
|
||||
protected _channels: { [key: number]: NetNode } = {};
|
||||
|
||||
/** 网络管理单例对象 */
|
||||
static getInstance(): NetManager {
|
||||
if (!this._instance) {
|
||||
this._instance = new NetManager();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加网络节点
|
||||
* @param node 网络节点
|
||||
* @param channelId 通道编号
|
||||
* @example
|
||||
// 游戏服务器心跳协议
|
||||
class GameProtocol extends NetProtocolPako {
|
||||
// 自定义心跳协议
|
||||
getHearbeat(): NetData {
|
||||
return '{"action":"LoginAction","method":"heart","data":"null","callback":"LoginAction_heart"}';
|
||||
}
|
||||
}
|
||||
|
||||
var net = new NetNodeGame();
|
||||
var ws = new WebSock(); // WebSocket 网络连接对象
|
||||
var gp = new GameProtocol(); // 网络通讯协议对象
|
||||
var gt = new NetGameTips() // 网络提示对象
|
||||
net.init(ws, gp, gt);
|
||||
NetManager.getInstance().setNetNode(net, NetChannelType.Game);
|
||||
*/
|
||||
setNetNode(node: NetNode, channelId: number = 0) {
|
||||
this._channels[channelId] = node;
|
||||
}
|
||||
|
||||
/** 移除Node */
|
||||
removeNetNode(channelId: number) {
|
||||
delete this._channels[channelId];
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络节点连接服务器
|
||||
* @param options 连接参数
|
||||
* @param channelId 通道编号
|
||||
* @example
|
||||
var options = {
|
||||
url: 'ws://127.0.0.1:3000',
|
||||
autoReconnect: 0 // -1 永久重连,0不自动重连,其他正整数为自动重试次数
|
||||
}
|
||||
NetManager.getInstance().connect(options, NetChannelType.Game);
|
||||
*/
|
||||
connect(options: NetConnectOptions, channelId: number = 0): boolean {
|
||||
if (this._channels[channelId]) {
|
||||
return this._channels[channelId].connect(options);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 节点连接发送数据*/
|
||||
send(buf: NetData, force: boolean = false, channelId: number = 0): number {
|
||||
let node = this._channels[channelId];
|
||||
if (node) {
|
||||
return node!.send(buf, force);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求,并在在结果返回时调用指定好的回调函数
|
||||
* @param reqProtocol 请求协议
|
||||
* @param rspObject 回调对象
|
||||
* @param showTips 是否触发请求提示
|
||||
* @param force 是否强制发送
|
||||
* @param channelId 通道编号
|
||||
* @example
|
||||
let protocol: IRequestProtocol = {
|
||||
action: action,
|
||||
method: method,
|
||||
data: JSON.stringify(data),
|
||||
isCompress: this.isCompress,
|
||||
channelid: netConfig.channelid
|
||||
}
|
||||
return this.request(protocol, rspObject, showTips, force);
|
||||
*/
|
||||
request(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0) {
|
||||
let node = this._channels[channelId];
|
||||
if (node) {
|
||||
node.request(reqProtocol, rspObject, showTips, force);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同request功能一致,但在request之前会先判断队列中是否已有rspCmd,如有重复的则直接返回
|
||||
* @param reqProtocol 请求协议
|
||||
* @param rspObject 回调对象
|
||||
* @param showTips 是否触发请求提示
|
||||
* @param force 是否强制发送
|
||||
* @param channelId 通道编号
|
||||
* @example
|
||||
let protocol: IRequestProtocol = {
|
||||
action: action,
|
||||
method: method,
|
||||
data: JSON.stringify(data),
|
||||
isCompress: this.isCompress,
|
||||
channelid: netConfig.channelid
|
||||
}
|
||||
return this.request(protocol, rspObject, showTips, force);
|
||||
*/
|
||||
requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0): boolean {
|
||||
let node = this._channels[channelId];
|
||||
if (node) {
|
||||
return node.requestUnique(reqProtocol, rspObject, showTips, force);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点网络断开
|
||||
* @param code 关闭码
|
||||
* @param reason 关闭原因
|
||||
* @param channelId 通道编号
|
||||
* @example
|
||||
* NetManager.getInstance().close(undefined, undefined, NetChannelType.Game);
|
||||
*/
|
||||
close(code?: number, reason?: string, channelId: number = 0) {
|
||||
if (this._channels[channelId]) {
|
||||
return this._channels[channelId].closeSocket(code, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
assets/libs/network/NetManager.ts.meta
Normal file
11
assets/libs/network/NetManager.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d8cd57a5-e860-464d-84d6-f8df1bf04b89",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
491
assets/libs/network/NetNode.ts
Normal file
491
assets/libs/network/NetNode.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
import { error, warn } from "cc";
|
||||
import { Logger } from "../../core/common/log/Logger";
|
||||
import { CallbackObject, INetworkTips, IProtocolHelper, IRequestProtocol, ISocket, NetCallFunc, NetData, RequestObject } from "./NetInterface";
|
||||
|
||||
/*
|
||||
* CocosCreator网络节点基类,以及网络相关接口定义
|
||||
* 1. 网络连接、断开、请求发送、数据接收等基础功能
|
||||
* 2. 心跳机制
|
||||
* 3. 断线重连 + 请求重发
|
||||
* 4. 调用网络屏蔽层
|
||||
*/
|
||||
|
||||
type ExecuterFunc = (callback: CallbackObject, buffer: NetData) => void;
|
||||
type CheckFunc = (checkedFunc: VoidFunc) => void;
|
||||
type VoidFunc = () => void;
|
||||
type BoolFunc = () => boolean;
|
||||
|
||||
var NetNodeStateStrs = ["已关闭", "连接中", "验证中", "可传输数据"];
|
||||
|
||||
/** 网络提示类型枚举 */
|
||||
export enum NetTipsType {
|
||||
Connecting,
|
||||
ReConnecting,
|
||||
Requesting,
|
||||
}
|
||||
|
||||
/** 网络状态枚举 */
|
||||
export enum NetNodeState {
|
||||
Closed, // 已关闭
|
||||
Connecting, // 连接中
|
||||
Checking, // 验证中
|
||||
Working, // 可传输数据
|
||||
}
|
||||
|
||||
/** 网络连接参数 */
|
||||
export interface NetConnectOptions {
|
||||
host?: string, // 地址
|
||||
port?: number, // 端口
|
||||
url?: string, // url,与地址+端口二选一
|
||||
autoReconnect?: number, // -1 永久重连,0不自动重连,其他正整数为自动重试次数
|
||||
}
|
||||
|
||||
/** 网络节点 */
|
||||
export class NetNode {
|
||||
protected _connectOptions: NetConnectOptions | null = null;
|
||||
protected _autoReconnect: number = 0;
|
||||
protected _isSocketInit: boolean = false; // Socket是否初始化过
|
||||
protected _isSocketOpen: boolean = false; // Socket是否连接成功过
|
||||
protected _state: NetNodeState = NetNodeState.Closed; // 节点当前状态
|
||||
protected _socket: ISocket | null = null; // Socket对象(可能是原生socket、websocket、wx.socket...)
|
||||
|
||||
protected _networkTips: INetworkTips | null = null; // 网络提示ui对象(请求提示、断线重连提示等)
|
||||
protected _protocolHelper: IProtocolHelper | null = null; // 包解析对象
|
||||
protected _connectedCallback: CheckFunc | null = null; // 连接完成回调
|
||||
protected _disconnectCallback: BoolFunc | null = null; // 断线回调
|
||||
protected _callbackExecuter: ExecuterFunc | null = null; // 回调执行
|
||||
|
||||
protected _keepAliveTimer: any = null; // 心跳定时器
|
||||
protected _receiveMsgTimer: any = null; // 接收数据定时器
|
||||
protected _reconnectTimer: any = null; // 重连定时器
|
||||
protected _heartTime: number = 10000; // 心跳间隔
|
||||
protected _receiveTime: number = 6000000; // 多久没收到数据断开
|
||||
protected _reconnetTimeOut: number = 8000000; // 重连间隔
|
||||
protected _requests: RequestObject[] = Array<RequestObject>(); // 请求列表
|
||||
protected _listener: { [key: string]: CallbackObject[] | null } = {} // 监听者列表
|
||||
|
||||
/********************** 网络相关处理 *********************/
|
||||
init(socket: ISocket, protocol: IProtocolHelper, networkTips: INetworkTips | null = null, execFunc: ExecuterFunc | null = null) {
|
||||
Logger.instance.logNet(`网络初始化`);
|
||||
this._socket = socket;
|
||||
this._protocolHelper = protocol;
|
||||
this._networkTips = networkTips;
|
||||
this._callbackExecuter = execFunc ? execFunc : (callback: CallbackObject, buffer: NetData) => {
|
||||
callback.callback.call(callback.target, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求连接服务器
|
||||
* @param options 连接参数
|
||||
*/
|
||||
connect(options: NetConnectOptions): boolean {
|
||||
if (this._socket && this._state == NetNodeState.Closed) {
|
||||
if (!this._isSocketInit) {
|
||||
this.initSocket();
|
||||
}
|
||||
this._state = NetNodeState.Connecting;
|
||||
if (!this._socket.connect(options)) {
|
||||
this.updateNetTips(NetTipsType.Connecting, false);
|
||||
return false;
|
||||
}
|
||||
if (this._connectOptions == null && typeof options.autoReconnect == "number") {
|
||||
this._autoReconnect = options.autoReconnect;
|
||||
}
|
||||
this._connectOptions = options;
|
||||
this.updateNetTips(NetTipsType.Connecting, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected initSocket() {
|
||||
if (this._socket) {
|
||||
this._socket.onConnected = (event) => { this.onConnected(event) };
|
||||
this._socket.onMessage = (msg) => { this.onMessage(msg) };
|
||||
this._socket.onError = (event) => { this.onError(event) };
|
||||
this._socket.onClosed = (event) => { this.onClosed(event) };
|
||||
this._isSocketInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected updateNetTips(tipsType: NetTipsType, isShow: boolean) {
|
||||
if (this._networkTips) {
|
||||
if (tipsType == NetTipsType.Requesting) {
|
||||
this._networkTips.requestTips(isShow);
|
||||
}
|
||||
else if (tipsType == NetTipsType.Connecting) {
|
||||
this._networkTips.connectTips(isShow);
|
||||
}
|
||||
else if (tipsType == NetTipsType.ReConnecting) {
|
||||
this._networkTips.reconnectTips(isShow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 网络连接成功 */
|
||||
protected onConnected(event: any) {
|
||||
Logger.instance.logNet("网络已连接")
|
||||
this._isSocketOpen = true;
|
||||
// 如果设置了鉴权回调,在连接完成后进入鉴权阶段,等待鉴权结束
|
||||
if (this._connectedCallback !== null) {
|
||||
this._state = NetNodeState.Checking;
|
||||
this._connectedCallback(() => { this.onChecked() });
|
||||
}
|
||||
else {
|
||||
this.onChecked();
|
||||
}
|
||||
Logger.instance.logNet(`网络已连接当前状态为【${NetNodeStateStrs[this._state]}】`);
|
||||
}
|
||||
|
||||
/** 连接验证成功,进入工作状态 */
|
||||
protected onChecked() {
|
||||
Logger.instance.logNet("连接验证成功,进入工作状态");
|
||||
this._state = NetNodeState.Working;
|
||||
// 关闭连接或重连中的状态显示
|
||||
this.updateNetTips(NetTipsType.Connecting, false);
|
||||
this.updateNetTips(NetTipsType.ReConnecting, false);
|
||||
|
||||
// 重发待发送信息
|
||||
var requests = this._requests.concat();
|
||||
if (requests.length > 0) {
|
||||
Logger.instance.logNet(`请求【${this._requests.length}】个待发送的信息`);
|
||||
|
||||
for (var i = 0; i < requests.length;) {
|
||||
let req = requests[i];
|
||||
this._socket!.send(req.buffer);
|
||||
if (req.rspObject == null || req.rspCmd != "") {
|
||||
requests.splice(i, 1);
|
||||
}
|
||||
else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
// 如果还有等待返回的请求,启动网络请求层
|
||||
this.updateNetTips(NetTipsType.Requesting, this._requests.length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** 接收到一个完整的消息包 */
|
||||
protected onMessage(msg: any): void {
|
||||
// Logger.logNet(`接受消息状态为【${NetNodeStateStrs[this._state]}】`);
|
||||
|
||||
var json = JSON.parse(msg);
|
||||
|
||||
// 进行头部的校验(实际包长与头部长度是否匹配)
|
||||
if (!this._protocolHelper!.checkResponsePackage(json)) {
|
||||
error(`校验接受消息数据异常`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理相应包数据
|
||||
if (!this._protocolHelper!.handlerResponsePackage(json)) {
|
||||
if (this._networkTips)
|
||||
this._networkTips.responseErrorCode(json.code);
|
||||
}
|
||||
|
||||
// 接受到数据,重新定时收数据计时器
|
||||
this.resetReceiveMsgTimer();
|
||||
// 重置心跳包发送器
|
||||
this.resetHearbeatTimer();
|
||||
// 触发消息执行
|
||||
let rspCmd = this._protocolHelper!.getPackageId(json);
|
||||
|
||||
Logger.instance.logNet(`接受到命令【${rspCmd}】的消息`);
|
||||
// 优先触发request队列
|
||||
if (this._requests.length > 0) {
|
||||
for (let reqIdx in this._requests) {
|
||||
let req = this._requests[reqIdx];
|
||||
if (req.rspCmd == rspCmd && req.rspObject) {
|
||||
Logger.instance.logNet(`触发请求命令【${rspCmd}】的回调`);
|
||||
this._callbackExecuter!(req.rspObject, json.data);
|
||||
this._requests.splice(parseInt(reqIdx), 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._requests.length == 0) {
|
||||
this.updateNetTips(NetTipsType.Requesting, false);
|
||||
}
|
||||
else {
|
||||
Logger.instance.logNet(`请求队列中还有【${this._requests.length}】个请求在等待`);
|
||||
}
|
||||
}
|
||||
|
||||
let listeners = this._listener[rspCmd];
|
||||
if (null != listeners) {
|
||||
for (const rsp of listeners) {
|
||||
Logger.instance.logNet(`触发监听命令【${rspCmd}】的回调`);
|
||||
this._callbackExecuter!(rsp, json.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected onError(event: any) {
|
||||
error(event);
|
||||
}
|
||||
|
||||
protected onClosed(event: any) {
|
||||
this.clearTimer();
|
||||
|
||||
// 执行断线回调,返回false表示不进行重连
|
||||
if (this._disconnectCallback && !this._disconnectCallback()) {
|
||||
Logger.instance.logNet(`断开连接`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 自动重连
|
||||
if (this.isAutoReconnect()) {
|
||||
this.updateNetTips(NetTipsType.ReConnecting, true);
|
||||
this._reconnectTimer = setTimeout(() => {
|
||||
this._socket!.close();
|
||||
this._state = NetNodeState.Closed;
|
||||
this.connect(this._connectOptions!);
|
||||
if (this._autoReconnect > 0) {
|
||||
this._autoReconnect -= 1;
|
||||
}
|
||||
}, this._reconnetTimeOut);
|
||||
}
|
||||
else {
|
||||
this._state = NetNodeState.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开网络
|
||||
* @param code 关闭码
|
||||
* @param reason 关闭原因
|
||||
*/
|
||||
close(code?: number, reason?: string) {
|
||||
this.clearTimer();
|
||||
this._listener = {};
|
||||
this._requests.length = 0;
|
||||
if (this._networkTips) {
|
||||
this._networkTips.connectTips(false);
|
||||
this._networkTips.reconnectTips(false);
|
||||
this._networkTips.requestTips(false);
|
||||
}
|
||||
if (this._socket) {
|
||||
this._socket.close(code, reason);
|
||||
}
|
||||
else {
|
||||
this._state = NetNodeState.Closed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只是关闭Socket套接字(仍然重用缓存与当前状态)
|
||||
* @param code 关闭码
|
||||
* @param reason 关闭原因
|
||||
*/
|
||||
closeSocket(code?: number, reason?: string) {
|
||||
if (this._socket) {
|
||||
this._socket.close(code, reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求,如果当前处于重连中,进入缓存列表等待重连完成后发送
|
||||
* @param buf 网络数据
|
||||
* @param force 是否强制发送
|
||||
*/
|
||||
send(buf: NetData, force: boolean = false): number {
|
||||
if (this._state == NetNodeState.Working || force) {
|
||||
return this._socket!.send(buf);
|
||||
}
|
||||
else if (this._state == NetNodeState.Checking ||
|
||||
this._state == NetNodeState.Connecting) {
|
||||
this._requests.push({
|
||||
buffer: buf,
|
||||
rspCmd: "",
|
||||
rspObject: null
|
||||
});
|
||||
Logger.instance.logNet(`当前状态为【${NetNodeStateStrs[this._state]}】,繁忙并缓冲发送数据`);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
error(`当前状态为【${NetNodeStateStrs[this._state]}】,请求错误`);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起请求,并进入缓存列表
|
||||
* @param reqProtocol 请求协议
|
||||
* @param rspObject 回调对象
|
||||
* @param showTips 是否触发请求提示
|
||||
* @param force 是否强制发送
|
||||
*/
|
||||
request<T>(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) {
|
||||
var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol);
|
||||
this.base_request(reqProtocol, rspCmd, rspObject, showTips, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* 唯一request,确保没有同一响应的请求(避免一个请求重复发送,netTips界面的屏蔽也是一个好的方法)
|
||||
* @param reqProtocol 请求协议
|
||||
* @param rspObject 回调对象
|
||||
* @param showTips 是否触发请求提示
|
||||
* @param force 是否强制发送
|
||||
*/
|
||||
requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false): boolean {
|
||||
var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol);
|
||||
|
||||
for (let i = 0; i < this._requests.length; ++i) {
|
||||
if (this._requests[i].rspCmd == rspCmd) {
|
||||
Logger.instance.logNet(`命令【${rspCmd}】重复请求`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.base_request(reqProtocol, rspCmd, rspObject, showTips, force);
|
||||
return true;
|
||||
}
|
||||
|
||||
private base_request(reqProtocol: IRequestProtocol, rspCmd: string, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) {
|
||||
var buf: NetData = JSON.stringify(reqProtocol); // 转为二进制流发送
|
||||
|
||||
if (this._state == NetNodeState.Working || force) {
|
||||
this._socket!.send(buf);
|
||||
}
|
||||
|
||||
Logger.instance.logNet(`队列命令为【${rspCmd}】的请求,等待请求数据的回调`);
|
||||
|
||||
// 进入发送缓存列表
|
||||
this._requests.push({
|
||||
buffer: buf, rspCmd, rspObject
|
||||
});
|
||||
// 启动网络请求层
|
||||
if (showTips) {
|
||||
this.updateNetTips(NetTipsType.Requesting, true);
|
||||
}
|
||||
}
|
||||
|
||||
/********************** 回调相关处理 *********************/
|
||||
/**
|
||||
* 设置一个唯一的服务器推送监听
|
||||
* @param cmd 命令字串
|
||||
* @param callback 回调方法
|
||||
* @param target 目标对象
|
||||
*/
|
||||
setResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean {
|
||||
if (callback == null) {
|
||||
error(`命令为【${cmd}】设置响应处理程序错误`);
|
||||
return false;
|
||||
}
|
||||
this._listener[cmd] = [{ target, callback }];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可添加多个同类返回消息的监听
|
||||
* @param cmd 命令字串
|
||||
* @param callback 回调方法
|
||||
* @param target 目标对象
|
||||
* @returns
|
||||
*/
|
||||
addResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean {
|
||||
if (callback == null) {
|
||||
error(`命令为【${cmd}】添加响应处理程序错误`);
|
||||
return false;
|
||||
}
|
||||
let rspObject = { target, callback };
|
||||
if (null == this._listener[cmd]) {
|
||||
this._listener[cmd] = [rspObject];
|
||||
}
|
||||
else {
|
||||
let index = this.getNetListenersIndex(cmd, rspObject);
|
||||
if (-1 == index) {
|
||||
this._listener[cmd]!.push(rspObject);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个监听中指定子回调
|
||||
* @param cmd 命令字串
|
||||
* @param callback 回调方法
|
||||
* @param target 目标对象
|
||||
*/
|
||||
removeResponeHandler(cmd: string, callback: NetCallFunc, target?: any) {
|
||||
if (null != this._listener[cmd] && callback != null) {
|
||||
let index = this.getNetListenersIndex(cmd, { target, callback });
|
||||
if (-1 != index) {
|
||||
this._listener[cmd]!.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有监听或指定命令的监听
|
||||
* @param cmd 命令字串(默认不填为清除所有)
|
||||
*/
|
||||
cleanListeners(cmd: string = "") {
|
||||
if (cmd == "") {
|
||||
this._listener = {}
|
||||
}
|
||||
else {
|
||||
delete this._listener[cmd];
|
||||
}
|
||||
}
|
||||
|
||||
protected getNetListenersIndex(cmd: string, rspObject: CallbackObject): number {
|
||||
let index = -1;
|
||||
for (let i = 0; i < this._listener[cmd]!.length; i++) {
|
||||
let iterator = this._listener[cmd]![i];
|
||||
if (iterator.callback == rspObject.callback
|
||||
&& iterator.target == rspObject.target) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/********************** 心跳、超时相关处理 *********************/
|
||||
protected resetReceiveMsgTimer() {
|
||||
if (this._receiveMsgTimer !== null) {
|
||||
clearTimeout(this._receiveMsgTimer);
|
||||
}
|
||||
|
||||
this._receiveMsgTimer = setTimeout(() => {
|
||||
warn("接收消息定时器关闭网络连接");
|
||||
this._socket!.close();
|
||||
}, this._receiveTime);
|
||||
}
|
||||
|
||||
protected resetHearbeatTimer() {
|
||||
if (this._keepAliveTimer !== null) {
|
||||
clearTimeout(this._keepAliveTimer);
|
||||
}
|
||||
|
||||
this._keepAliveTimer = setTimeout(() => {
|
||||
Logger.instance.logNet("网络节点保持活跃发送心跳信息");
|
||||
this.send(this._protocolHelper!.getHearbeat());
|
||||
}, this._heartTime);
|
||||
}
|
||||
|
||||
protected clearTimer() {
|
||||
if (this._receiveMsgTimer !== null) {
|
||||
clearTimeout(this._receiveMsgTimer);
|
||||
}
|
||||
if (this._keepAliveTimer !== null) {
|
||||
clearTimeout(this._keepAliveTimer);
|
||||
}
|
||||
if (this._reconnectTimer !== null) {
|
||||
clearTimeout(this._reconnectTimer);
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否自动重连接 */
|
||||
isAutoReconnect() {
|
||||
return this._autoReconnect != 0;
|
||||
}
|
||||
|
||||
/** 拒绝重新连接 */
|
||||
rejectReconnect() {
|
||||
this._autoReconnect = 0;
|
||||
this.clearTimer();
|
||||
}
|
||||
}
|
||||
11
assets/libs/network/NetNode.ts.meta
Normal file
11
assets/libs/network/NetNode.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "57f0f07d-d243-4150-9f7c-cb2bbe8f18f1",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
69
assets/libs/network/NetProtocolPako.ts
Normal file
69
assets/libs/network/NetProtocolPako.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-21 13:45:51
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-04-21 13:51:33
|
||||
*/
|
||||
import { IProtocolHelper, IRequestProtocol, IResponseProtocol, NetData } from "./NetInterface";
|
||||
|
||||
var unzip = function (str: string) {
|
||||
let charData = str.split('').map(function (x) {
|
||||
return x.charCodeAt(0);
|
||||
});
|
||||
let binData = new Uint8Array(charData);
|
||||
//@ts-ignore
|
||||
let data = pako.inflate(binData, { to: 'string' });
|
||||
return data;
|
||||
}
|
||||
|
||||
var zip = function (str: string) {
|
||||
//@ts-ignore
|
||||
let binaryString = pako.gzip(str, { to: 'string' });
|
||||
return binaryString;
|
||||
}
|
||||
|
||||
/** Pako.js 数据压缩协议 */
|
||||
export class NetProtocolPako implements IProtocolHelper {
|
||||
getHeadlen(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getHearbeat(): NetData {
|
||||
return "";
|
||||
}
|
||||
|
||||
getPackageLen(msg: NetData): number {
|
||||
return msg.toString().length;
|
||||
}
|
||||
|
||||
checkResponsePackage(respProtocol: IResponseProtocol): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
handlerResponsePackage(respProtocol: IResponseProtocol): boolean {
|
||||
if (respProtocol.code == 1) {
|
||||
if (respProtocol.isCompress) {
|
||||
respProtocol.data = unzip(respProtocol.data);
|
||||
}
|
||||
respProtocol.data = JSON.parse(respProtocol.data);
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
handlerRequestPackage(reqProtocol: IRequestProtocol): string {
|
||||
var rspCmd = reqProtocol.cmd;
|
||||
reqProtocol.callback = rspCmd;
|
||||
if (reqProtocol.isCompress) {
|
||||
reqProtocol.data = zip(reqProtocol.data);
|
||||
}
|
||||
return rspCmd;
|
||||
}
|
||||
|
||||
getPackageId(respProtocol: IResponseProtocol): string {
|
||||
return respProtocol.callback!;
|
||||
}
|
||||
}
|
||||
9
assets/libs/network/NetProtocolPako.ts.meta
Normal file
9
assets/libs/network/NetProtocolPako.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "88ae0948-8390-4559-bd4e-d44f3f12dc22",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
84
assets/libs/network/WebSock.ts
Normal file
84
assets/libs/network/WebSock.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-07-03 16:13:17
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 17:42:19
|
||||
*/
|
||||
import { Logger } from "../../core/common/log/Logger";
|
||||
import { ISocket, MessageFunc, NetData } from "./NetInterface";
|
||||
|
||||
type Connected = (event: any) => void;
|
||||
|
||||
/**
|
||||
* WebSocket 封装
|
||||
* 1. 连接/断开相关接口
|
||||
* 2. 网络异常回调
|
||||
* 3. 数据发送与接收
|
||||
*/
|
||||
export class WebSock implements ISocket {
|
||||
private _ws: WebSocket | null = null; // websocket对象
|
||||
|
||||
/** 网络连接成功事件 */
|
||||
onConnected: ((this: WebSocket, ev: Event) => any) | null = null;
|
||||
/** 接受到网络数据事件 */
|
||||
onMessage: MessageFunc | null = null;
|
||||
/** 网络错误事件 */
|
||||
onError: ((this: WebSocket, ev: Event) => any) | null = null;
|
||||
/** 网络断开事件 */
|
||||
onClosed: ((this: WebSocket, ev: CloseEvent) => any) | null = null;
|
||||
|
||||
/** 请求连接 */
|
||||
connect(options: any) {
|
||||
if (this._ws) {
|
||||
if (this._ws.readyState === WebSocket.CONNECTING) {
|
||||
Logger.logNet("websocket connecting, wait for a moment...")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let url = null;
|
||||
if (options.url) {
|
||||
url = options.url;
|
||||
}
|
||||
else {
|
||||
let ip = options.ip;
|
||||
let port = options.port;
|
||||
let protocol = options.protocol;
|
||||
url = `${protocol}://${ip}:${port}`;
|
||||
}
|
||||
|
||||
this._ws = new WebSocket(url);
|
||||
this._ws.binaryType = options.binaryType ? options.binaryType : "arraybuffer";
|
||||
this._ws.onmessage = (event) => {
|
||||
let onMessage: MessageFunc = this.onMessage!;
|
||||
onMessage(event.data);
|
||||
};
|
||||
this._ws.onopen = this.onConnected;
|
||||
this._ws.onerror = this.onError;
|
||||
this._ws.onclose = this.onClosed;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据
|
||||
* @param buffer 网络数据
|
||||
*/
|
||||
send(buffer: NetData): number {
|
||||
if (this._ws && this._ws.readyState == WebSocket.OPEN) {
|
||||
this._ws.send(buffer);
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络断开
|
||||
* @param code 关闭码
|
||||
* @param reason 关闭原因
|
||||
*/
|
||||
close(code?: number, reason?: string) {
|
||||
if (this._ws) {
|
||||
this._ws.close(code, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
assets/libs/network/WebSock.ts.meta
Normal file
11
assets/libs/network/WebSock.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "70df255b-214f-41eb-a16b-fa8a7c14a269",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
12
assets/libs/network/protocol.meta
Normal file
12
assets/libs/network/protocol.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "922e2501-4a6b-4f54-852c-28e24c3fc5b8",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"compressionType": {},
|
||||
"isRemoteBundle": {}
|
||||
}
|
||||
}
|
||||
3424
assets/libs/network/protocol/pako.min.js
vendored
Normal file
3424
assets/libs/network/protocol/pako.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
17
assets/libs/network/protocol/pako.min.js.meta
Normal file
17
assets/libs/network/protocol/pako.min.js.meta
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "javascript",
|
||||
"imported": true,
|
||||
"uuid": "60e799a8-4bb9-4e3d-8b8c-ab86909f9eb8",
|
||||
"files": [
|
||||
".js"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"isPlugin": true,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": false,
|
||||
"loadPluginInEditor": false,
|
||||
"loadPluginInMiniGame": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user