当前位置: 首页 > news >正文

【联机对战】微信小程序联机游戏开发流程详解

现有一个微信小程序叫中国象棋项目,棋盘类的单机游戏看着有缺少了什么,现在给补上了,加个联机对战的功能,增加了可玩性,对新手来说,实现联机游戏还是有难度的,那要怎么实现的呢,接下来给大家讲一下。

考虑到搭建联机游戏的服务器成本不小,第一个想法是用小程序的蓝牙功能实现游戏联机的,但是其API接口提供的蓝牙硬件支持兼容问题不少,暂时不去折腾了,现在采用UDP通信就很容易实现,可以在WIFI局域网内让两个以上小程序实现通信。

UDP通信

先来了解一下 UDP通信 的工作原理,这是一个面向无连接的传输协议,是UDP通信,与之对应的是 面向可连接的传输协议,是 TCP通信

client客户端server服务端发送报文收到client客户端server服务端

从上图看出来,UDP通信的方式很简单,可以想象它们能充当其中一个角色,

  • client客户端: 只负责发送报文
  • server服务端:只负责接收报文

小程序实现UDP通信,要创建client客户端和服务端server,各占一个socket端口,
对初学者来说,第一次接触不好理解,端口,可比喻成线路一端的接口。

TCP通信的面向连接是比UDP通信最可靠的,那为什么不优先采用TCP通信呢

小程序的TCP通信实现过程中,需要绑定到wifi,这一点获取wifi信息的处理有遇到问题,对新手来说是比较麻烦的,暂且避之,能正常获取到wifi信息再来考虑

client客户端

直接在一个模块文件中实现,这个在项目中的文件是lan.js,可以理解它为局域网工具模块,

负责发送

要向server服务端发送报文(消息),就写一个方法sendMessage(e)来调用,传入服务端的remoteInfo,实现代码如下

import Util from './util';function sendMessage(e){//需要传入的参数const { message, port, remoteInfo, fail, success, autoClose } = e;let udp = wx.createUDPSocket();udp.onError(err=>{//...这里处理初始化udp的错误});udp.onMessage(res=>{const { remoteInfo, localInfo } = res;if(autoClose) udp.close();//默认自动关闭udp//消息res.message是ArrayBuffer对象,要转换为json object对象才好处理let message = Util.arrayBufferToString({data:res.message});message = Util.parseJSON(message);//返回服务端响应的数据success(message,remoteInfo,localInfo);});//绑定端口udp.bind(port);//发送消息message 到服务端 `address(IP)`和`port(端口)`udp.send({address:remoteInfo.address,port:remoteInfo.port,message:toStringMesssage(message)});return udp;
}

客户端向服务端发出消息,没必要加请求超时的处理,后面有个逻辑是处理连接的,用它代替连接超时处理的判断

连接服务端

客户端连接到服务端方法是connectServer(remoteInfo,e),实现代码如下,加了定时连接请求,如果请求超时了,就会提示用户连接超时(连接断开)

const Timeout = 6000;//超时6sfunction connectServer(remoteInfo,e){const { config, onReceive, onError, onConnect, onDisconnect } = e;let connectInfo;let timer,timer2;//关闭定时器const closeTimer = function(){if(timer) {clearTimeout(timer);timer=undefined;}if(timer2) {clearTimeout(timer2);timer2=undefined;}};const clientUdp = wx.createUDPSocket();clientUdp.onClose(function(){closeTimer();});clientUdp.onError(function(err){closeTimer();//...这里处理udp抛出的错误,回调onErroronError(err);});//默认不传port,就绑定一个随机的port(端口号)clientUdp.bind();let time;let sendSign = function(){//定时发送timer = setTimeout(function(){let message = {intent:'keep_connect',ntime:Date.now(),};if(!connectInfo){message.intent='create_connect';message.data=config;}//定时向服务端发送连接信息clientUdp.send({address:remoteInfo.address,port:remoteInfo.port,message:JSON.stringify(message)});//加个定时器,用于超时判断timer2 = setTimeout(function(){    connectInfo = null;onDisconnect({ errMsg:'the request timeout' });},Timeout);},config.time || 3000);};clientUdp.onMessage(function(res){//防抖处理closeTimer();const { localInfo,remoteInfo } = res;let message = Util.arrayBufferToString({data:res.message});message = Util.parseJSON(message);if(message.intent=='create_connect'){connectInfo = remoteInfo;//回调连接事件onConnect({message:message.data,localInfo,remoteInfo});}else{if(time && message.otime==undefined) message.otime = Date.now() - time;//回调接收事件onReceive({message,localInfo,remoteInfo});time = Date.now(); }sendSign();});//开始发送sendSign();return clientUdp;
}

方法sendSign()就是发送信号的意思,可以这样认为,在网络上冒个泡,可以让对方知道你在线,主动找你沟通,如果超过时间还不吐泡泡,就认为你潜水了(隐身)

请添加图片描述

server服务端

负责接收

接下来,写一个叫服务端server的创建方法createServer(),用于监听客户端发来的连接请求,还要处理其它的请求,稍微复杂一点,实现代码如下

import Util from './util';function createServer(e){const { config, onReceive, onError, onConnect, onDisconnect } = e;let udp = wx.createUDPSocket();udp.onError(function(err){//...这里处理udp抛出错误,回调onErroronError(err);});udp.onClose(function(){closeTimer();});udp.onMessage(function(res){const { localInfo, remoteInfo } = res;let message = Util.arrayBufferToString({data:res.message});message = Util.parseJSON(message);let response;switch(message?.intent){case 'create_connect'://...处理创建连接请求break;case 'keep_connect'://...处理保持连接请求break;default://如果没处理,就交给回调onReceive处理response = onReceive({ localInfo, remoteInfo, message });}//如果还没处理,就不需要响应了(不理睬)if(!response) return;//服务端响应数据发给客户端udp.send({address:remoteInfo.address,port:remoteInfo.port,message:toStringMesssage(response)});});//绑定一个服务器端口let port = udp.bind(config.port);return {getPort(){return port;},close(){udp.close();}};
}

有没有觉得,服务端的处理逻辑很像web的服务器处理请求,处理响应来自客户端(浏览器)的请求

管理客户端连接

再具体一点,处理创建和保持连接请求的方法,将上面的代码改一下,添加后的代码如下

const Timeout = 6000;//超时6s
//...let connectInfo;let timer;//保持连接(定时连接检查)const keepConnectInfo = function(){timer = setTimeout(function(){if(connectInfo){let otime = Date.now()-connectInfo.utime;//未超时if(otime<Timeout){keepConnectInfo();onReceive({ //...回调接收事件,返回连接状态});return;}}connectInfo=null;//若连接超时了,就回调断开连接的方法onDisconnect({ errMsg:'wait update is timeout'});},Timeout);};//关闭定时器const closeTimer = function(){//...};switch(message?.intent){case 'create_connect'://创建连接请求{let data = message.data;connectInfo = {//...记录连接信息};//回调连接事件onConnect({//...传连接数据});response = {intent:message.intent,//...返回连接后获取的初始化数据};//防抖处理closeTimer();keepConnectInfo();break;}case 'keep_connect'://保持连接请求{if(connectInfo) {connectInfo.utime = Date.now();response = {intent:message.intent,//...};}else{response = {intent:'create_connect',//...返回配置数据data:config,};}//记录时间差if(message.ntime) response.utime = response.time - message.ntime;break;}default://如果没处理,就交给回调onReceive处理response = onReceive({ localInfo, remoteInfo, message });}//...

可以看出来,这里连接的逻辑是定时检查客户端连接更新的状态,如果超过时间不更新,就判断为连接超时

局域网广播

两个小程序之间是怎么知道对方的IP和端口呢,这就要借助广播IP地址了,

发送广播IP地址,就可以在局域网发现在线的设备,然后请求连接,广播过程是这样的

发送广播
转发广播
转发广播
转发广播
客户端A
中央路由器
客户端B
客户端C
其余客户端...

来打个比喻:

客户端A(你)发送广播消息(点餐订单),交给路由器(平台)处理,路由器会转发消息,附带了你的IP(家)地址和端口(门号),到其余的客户端(抢单),
如果有客户端(抢到单的外卖服务员)对方想回应你,就会给你发消息:你的外卖到了…(票据上有写了对方的IP(店铺)地址和端口(门牌号))

请添加图片描述

发送广播

客户端怎样发送广播呢,这个方法是sendBroadcast(),很容易实现,代码如下

function sendBroadcast(e){const { port, fail, success, showLoading } = e;let udp = wx.createUDPSocket();let timer;let list = [];//记录接收的列表function complete(callback){//...udp.close();//处理完要关闭callback();}if(fail instanceof Function){udp.onError(function(err){complete(function(){fail(err);});});}udp.onMessage(function(res){//将接收的消息转换成json对象res.message = toDataJSON(res.message);list.push(res);});udp.bind();//发送广播消息,其中port是指定小程序的服务端接收端口udp.send({address:'255.255.255.255',port: port,message: JSON.stringify({ intent: 'scan' })});//加上定时timer = setTimeout(function(){//到时结束complete(function(){if(success instanceof Function) success({ list });})}, e.timeout || 3000);//...
}

广播IP是255.255.255.255,就是发给局域网内的路由器,需要注意的是,不是所有的路由器都是支持广播IP的,要确保支持它,需要登录路由器的控制页面,找看有没有其中的隔离AP项,取消勾选即可

发送的广播消息是{ intent: 'scan' },需要从另一个小程序的服务端负责接收那方法onReceive()中处理这个广播消息,返回响应数据

游戏联机

实现游戏的联机方式分两种,上面开始讲过,用其中的一个角色:服务端或者客户端

主动加入

一种是主动加入游戏,就用客户端发送问候消息方法sendMessage(e)去请求服务端,服务端会处理响应请求

Hello World
...
客户端
服务端

加入前,客户端需要先知道对方的IP地址和端口,从上面讲得发送广播方法sendBroadcast(e)来扫描一下,找到对方后,然后发送加入请求
在对方的服务端返回同意消息时,附带了游戏入口消息,告诉客户端加入游戏的途径

主动等待

另一种,是主动创建好游戏地盘,创建好服务端,用一个接收数据的绑定的端口,去负责监听客户端发来的请求,然后处理响应数据

快到碗里来
...
服务端
客户端

这里注意不要搞错,当另一个小程序客户端发来加入游戏的请求时,要留点心,别把客户端的端口号当作服务端的端口号(接收数据)

关于项目

好了,UDP通信的方法就讲到这里,这样有了大致的方向,

如需要看项目源码的请在 下载列表点这里 找联机游戏相关的项目,那些联机游戏项目里都有应用,有对应的介绍,请放心下载,多谢支持,愿学有所获!

打开项目源码,如果遇到微信开发工具提示Error: 登录用户不是该小程序的开发者 ,需要替换项目的测试号替换为自己的,点击开发工具右边的详情,里面有AppID,可修改替换,

这里有一个中国象棋-单机游戏开发流程详解文章,需要的同学可以先看看,

这里是在原单机游戏项目的基础上增加了联机功能,联机游戏运行的动图效果如下
请添加图片描述

联机测试

项目还没有自己的测试号,就前往申请一个测试号,申请成功后,登录如下图,其中AppID的就是
在这里插入图片描述

  • 申请测试号 🔗传送门

开发工具上扫码预览出来的小程序是开发版,只能在自己的微信上体验,想测试联机游戏就这样做,选择里面的真机调试项,这样就可以模拟两个用户来体验了,一个在开发工具上模拟器上,另一个就是自己的真机微信上

如果有两个手机微信就这样试试,用两个手机微信分别登录开发工具弄一个开发版小程序来,这样两个手机微信上就能测试游戏联机,

上面操作有点麻烦,就用自己申请好的一个小程序来测试,发布一个体验版小程序就可以让很多人参与测试了。

在这里插入图片描述

相关文章:

【联机对战】微信小程序联机游戏开发流程详解

现有一个微信小程序叫中国象棋项目&#xff0c;棋盘类的单机游戏看着有缺少了什么&#xff0c;现在给补上了&#xff0c;加个联机对战的功能&#xff0c;增加了可玩性&#xff0c;对新手来说&#xff0c;实现联机游戏还是有难度的&#xff0c;那要怎么实现的呢&#xff0c;接下…...

优化基于axios接口管理的骚操作

优化基于axios接口管理的骚操作&#xff01; 本文针对中大型的后台项目的接口模块优化&#xff0c;在不影响项目正常运行的前提下&#xff0c;增量更新。 强化功能 1.接口文件写法简化&#xff08;接口模块半自动化生成&#xff09; 2.任务调度、Loading调度&#xff08;接口层…...

【Django功能开发】如何正确使用定时任务(启动、停止)

系列文章目录 【Django开发入门】ORM的增删改查和批量操作 【Django功能开发】编写自定义manage命令 文章目录系列文章目录前言一、django定时任务二、django-apscheduler基本使用1.安装django-apscheduler2.配置settings.py的INSTALLED_APPS3.通过命令生成定时记录表3.如何创…...

7个好用到爆的音频、配乐素材网站,BGM都在这里了

现在只要有一部手机&#xff0c;人人都能成为视频创作者。一个好的视频不能缺少的就是内容、配乐&#xff0c;越来越注重版权的当下&#xff0c;音效素材使用不当造成侵权的案例层出不穷。为了避免侵权&#xff0c;找素材让很多创作者很头疼。 今天我就整理了7个可以免费下载&…...

JUC(二)

1.可重入锁–ReentrantLock原理 1.1.非公平锁的实现原理 1.1.1.加锁解锁流程 1>.先从构造器开始看,默认为非公平锁,可以在构造函数中设置参数指定公平锁 public ReentrantLock() {sync = new NonfairSync(); }public ReentrantLock...

ATS认证教学

我用的版本是ATS7.11、系统版本是用最新的ios13.2.1 定义 ATS旨在分析通过UART、USB和蓝牙传输传输的iAP流量、通过USB和无线&#xff08;蓝牙和Wi-Fi&#xff09;传输的CarPlay流量、通过Wi-Fi传输的AirPlay 2流量以及闪电音频流量。 ATS是Apple’s Accessory Test System的…...

【操作系统】进程管理

进程与线程 1. 进程 进程是资源分配的基本单位 进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态&#xff0c;所谓的创建进程和撤销进程&#xff0c;都是指对 PCB 的操作。 下图显示了 4 个程序创建了 4 个进程&#xff0c;这 4 个进程可以并发地执行…...

一分钟掌握技术术语:API(接口)

很多产品经理在项目开发过程中经常听到&#xff1a;你调我这个接口就好了&#xff1b;这个功能你写个接口给我&#xff1b;有什么不懂的就看下API接口文档。 开发经常说的接口是什么意思呢&#xff1f;术语解释&#xff1a;API&#xff08;Application Programming Interface&…...

RabbitMQ之交换机

交换机 在上一节中,我们创建了一个工作队列。我们假设的是工作队列背后,每个任务都恰好交付给一个消费者(工作进程)。在这一部分中,我们将做一些完全不同的事情-我们将消息传达给多个消费者。这种模式称为“发布/订阅”. 为了说明这种模式,我们将构建一个简单的日志系统。它…...

Tensorflow深度学习对遥感图像分类,内存不够怎么办?

问题描述在使用Tensorflow-cpu对图像分类的时候&#xff0c;在预读数据过程中&#xff0c;由于数据量过大&#xff0c;内存不足&#xff0c;导致计算失败。使用环境&#xff1a;win10系统 Pycharm tensorflow-cpu2.5.0 CPU: i7 8700 内存64G图1 CPU配置图图2 内存信息图使用数据…...

基础存贮模型介绍

基础存贮模型 这里主要讨论在需求量稳定的情况下&#xff0c;贮存量需要多少的问题。当贮存量过大时&#xff0c;会提高库存成本&#xff0c;也会造成积压资金&#xff1b;当贮存量过小时&#xff0c;会导致一次性订购费用增加&#xff0c;或者不能及时满足需求。 下面讨论不允…...

JNDIExploit使用方法

JNDIExploit 一款用于 JNDI注入 利用的工具&#xff0c;大量参考/引用了 Rogue JNDI 项目的代码&#xff0c;支持直接植入内存shell&#xff0c;并集成了常见的bypass 高版本JDK的方式&#xff0c;适用于与自动化工具配合使用。 对 feihong-cs 大佬的项目https://github.com/fe…...

建议一般人不要全职做副业

欢迎关注勤于奋每天12点准时更新国外LEAD相关技术全职做国外LEAD&#xff0c;听起来不错&#xff0c;但是效果不一定好&#xff0c;没有自控力来全职做&#xff0c;基本要废了自己&#xff0c;最好抽时间来做。我现在就是全职做国外LEAD&#xff0c;外加其他一些项目&#xff0…...

pytorch入门6--数据分析(pandas)

pandas是基于Numpy构建的&#xff0c;提供了众多比NumPy更高级、更直观的数据处理功能&#xff0c;尤其是它的DataFrame数据结构&#xff0c;可以用处理数据库或电子表格的方式来处理分析数据。 使用Pandas前&#xff0c;需导入以下内容&#xff1a; import numpy as np from …...

淘宝API接口开发系列,详情接口参数说明

onebound.taobao.item_get 公共参数 名称类型必须描述keyString是 调用key&#xff08;必须以GET方式拼接在URL中&#xff09; 注册Key和secret&#xff1a; https://o0b.cn/anzexi secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff0…...

keep-alive

keep-alive 是 Vue 的内置组件&#xff0c;当它包裹动态组件时&#xff0c;会缓存不活动的组件实例&#xff0c;而不是销毁它们。 keep-alive 包裹动态组件时&#xff0c;会缓存不活动的组件实例&#xff0c;而不是销毁它们 使用场景 使用原则&#xff1a;当我们在某些场景下…...

Maven的生命周期及常用命令

文章目录1、Maven生命周期1.1、clean生命周期1.2、default生命周期1.3、site生命周期2、Maven常用命令1、Maven生命周期 Maven有三套生命周期系统&#xff1a; 1、clean生命周期 2、default生命周期 3、site生命周期 三套生命周期之间相互独立&#xff0c;每套生命周期包含一…...

【Java开发】JUC基础 03:线程五大状态和主要方法

1 概念介绍&#x1f4cc; 五大状态&#xff1a;new&#xff1a;Thread t new Thread(); 线程对象一旦被创建就进入到了新生状态&#xff1b;就绪状态&#xff1a;当调用start()方法&#xff0c;线程立即进入就绪状态&#xff0c;但不意味着立即调度执行&#xff1b;运行状态&a…...

docker打包容器 在另一个机器上运行

1&#xff1a;将运行中的容器变为镜像docker commit 容器id 镜像名&#xff08;docker commit 89e81386d35e aabbcc&#xff09;2:将容器打包成tar包docker save -o xxx.tar 镜像名 &#xff08;当前路径下会生成一个tar的文件&#xff09;3&#xff1a;将tar包压缩为gz包tar -…...

2023年全国最新保安员精选真题及答案9

百分百题库提供保安员考试试题、保安职业资格考试预测题、保安员考试真题、保安职业资格证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 91.护卫对象在公共场所参加活动前&#xff0c;保安员需要事先&#xff08;&#xff0…...

arduino-sentry2之卡片篇

欧克,今天在学生的强烈要求下 我又重启arduino的sentry2调试篇 目前实验结果,可以检测到10张交通卡片 也就是如图所示十张 具体视频如下: https://live.csdn.net/v/279170 具体代码如下: #include <Arduino.h> #include <...

七、JUC并发工具

文章目录JUC并发工具CountDownLatch应用&源码分析CountDownLatch介绍CountDownLatch应用CountDownLatch源码分析有参构造await方法countDown方法CyclicBarrier应用&源码分析CyclicBarrier介绍CyclicBarrier应用CyclicBarrier源码分析CyclicBarrier的核心属性CyclicBarr…...

C++ string类(二)及深浅拷贝

一、string类方法使用举例1.迭代器迭代器本质&#xff1a;指针&#xff08;理解&#xff09;迭代器&#xff1a;正向迭代器&#xff1a; begin() | end() 反向迭代器&#xff1a; rbegin() | rend()2.find使用//找到s中某个字符 void TestString3() {string s("AAADEFNUIE…...

「TCG 规范解读」TCG 软件栈 TSS (上)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…...

(二)Markdown编辑器的使用效果 | 以CSDN自带MD编辑器为例

Markdown编辑器使用指南 &#xff08;一&#xff09;Markdown编辑器的使用示例 | 以CSDN自带MD编辑器为例&#xff08;二&#xff09;Markdown编辑器的使用效果 | 以CSDN自带MD编辑器为例 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xf…...

WebSocket网络通信执行流程

目录WebSocket网络通信执行流程相关概念执行流程WebSocket网络通信执行流程 WebSocket协议&#xff1a;通过单个TCP连接在客户端和服务器之间建立全双工双向通信通道。 WebSocket 对象&#xff1a;提供了用于创建和管理 WebSocket 连接&#xff0c;以及可以通过该连接发送和接…...

【Shell学习笔记】4.Shell 基本运算符

前言 本章介绍Shell的基本运算符。 Shell 基本运算符 Shell 和其他编程语言一样&#xff0c;支持多种运算符&#xff0c;包括&#xff1a; 算数运算符关系运算符布尔运算符字符串运算符文件测试运算符 原生bash不支持简单的数学运算&#xff0c;但是可以通过其他命令来实现…...

无代码资讯 | 《低代码开发平台能力要求》发布;CADP列入Gartner《2022-2024 中型企业技术采用路线图》

栏目导读&#xff1a;无代码资讯栏目从全球视角出发&#xff0c;带您了解无代码相关最新资讯‍。TOP3 大事件1、《低代码开发平台能力要求》团体标准正式发布近日&#xff0c;中国电子工业标准化协会发布公告&#xff08;中电标【2022】037 号&#xff09;&#xff0c;由中国电…...

智能家居Homekit系列一智能插座

WiFi智能插座对于新手接触智能家居产品更加友好&#xff0c;不需要额外购买网关设备 很多智能小配件也给我们得生活带来极大的便捷&#xff0c;智能插座就是其中之一&#xff0c;比如外出忘记关空调&#xff0c;可以拿起手机远程关闭。 简单说就是&#xff1a;插座可以连接wi…...

React(三):脚手架、组件化、生命周期、父子组件通信、插槽

React&#xff08;三&#xff09;一、脚手架安装和创建1.安装脚手架2.创建脚手架3.看看脚手架目录4.运行脚手架二、脚手架下从0开始写代码三、组件化1.类组件2.函数组件四、React的生命周期1.认识生命周期2.图解生命周期&#xff08;1&#xff09;Constructor&#xff08;2&…...

我赢网seo优化网站/十大暗网搜索引擎

郁闷&#xff01;今天淘宝被差评 郁闷&#xff01;今天淘宝被差评。经历了淘宝近5个月的掌柜后&#xff08;全职1个半月&#xff09;&#xff0c;淘宝创业的心酸苦辣也是尝了不少&#xff0c;知道淘宝掌柜最怕啥吗&#xff1f;我总结了3大怕5小怕&#xff0c;今天来说一大怕吧-…...

电子元器件外贸网站建设/网页设计首页制作

转载于:https://blog.51cto.com/dl5174887/1586335...

150m网站空间流量大吗/高权重友情链接

前言 在企业级项目开发过程中&#xff0c;上传文件是最常用到的功能。SpringBoot集成了SpringMVC&#xff0c;当然上传文件的方式跟SpringMVC没有什么出入。 本章目标 使用SpringBoot项目完成单个、多个文件的上传处理&#xff0c;并将上传的文件保存到指定目录下。这里我们会使…...

做虚假网站判多少年/扬州百度seo公司

1.2.2 审慎、合宜与慈善三种学派的分类与核心思想 亚当•斯密在《道德情操论》中论述了三种美德&#xff0c;分别为审慎&#xff08;利己&#xff09;&#xff0c;合宜&#xff08;克己&#xff09;和慈善&#xff08;利他&#xff09;&#xff0c;而三种美德分别为心理的自爱&…...

推荐几个高端大气上档次网站/怎么做市场营销和推广

项目框架的JS库集成了jQuery&#xff0c;Layout页面&#xff08;模板页面&#xff0c;类似ASP.NET的母版页&#xff09;中引用了这些JS&#xff0c;后来使用图表插件&#xff08;图表插件是基于jQuery的&#xff09;的时候&#xff0c;项目框架中的JS和图表插件有冲突&#xff…...

wordpress分权限浏览器/seo工具优化软件

“现在大部分的矿池都是在用我们当初开源的那套代码&#xff0c;包括前10名里也有不少。所以这个决定是很好的。只是后来给我们自己创业造成了一些麻烦&#xff0c;过多的竞争对手&#xff08;笑&#xff09;。”文 | 黄雪姣 运营 | 盖遥 编辑 | Mandy王梦蝶出品 | Odaily星球…...