作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
凯文·布洛赫的头像

凯文·布洛赫

Kevin拥有超过20年的全栈、桌面和独立游戏开发经验. 他最近专门研究PostgreSQL、JavaScript、Perl和Haxe.

工作经验

8

Share

COVID-19的封锁使我们中的许多人被困在家里, 也许希望幽居病是我们将要经历的最糟糕的发烧. 我们中的许多人比以往任何时候都消费更多的视频内容. 而现在锻炼尤其重要, 有时, 有一种对奢侈品的怀念, 当笔记本电脑够不着的时候,老式的遥控器.

这就是这个项目的意义所在:有机会把任何智能手机——即使是旧的、因为缺乏更新而无用的智能手机——变成一个方便的遥控器,用于观看下一个网飞公司/YouTube/亚马逊Prime视频等. binge-watch. 它也是一个节点.js后端教程:学习使用表达框架和Pug(以前是Jade)模板引擎的后端JavaScript基础知识的机会.

如果这听起来令人生畏,那就完成了 Node.js project will be presented at the end; readers need only learn as much as they’re interested in learning, 在阅读过程中,会有相当数量的对一些基础知识的温和解释,更有经验的读者可以跳过.

为什么不...?

读者可能会想,“为什么要编码一个Node ?.Js后端?(当然,除了学习的机会.) "Isn't there already an app for that?"

当然有很多. 但有两个主要原因可能不可取:

  1. 对于那些试图重新利用旧手机的人来说,这可能是可行的 不再是你的选择窗户。 Phone 8也是如此.我想用的一个设备. (该应用商店于2019年底正式关闭.)
  2. 信任(或缺乏信任). 就像任何移动平台上的许多应用程序一样, 它们通常要求用户授予远远超过应用程序所需要的权限. 但即使这方面有适当的限制, 远程控制应用程序的本质意味着用户仍然必须相信应用程序开发人员不会滥用他们在桌面端的特权,包括间谍软件或其他恶意软件.

这些问题已经存在了很长时间,甚至是2014年在GitHub上发现的一个类似项目的动机. nvm 可以很容易地安装旧版本的Node.即使有一些依赖需要升级,Node . js也是如此.Js以向后兼容而闻名.

不幸的是,比特洛特赢了. 固执的方法和节点.js的后端兼容性无法与老版本Grunt中无尽的弃用和不可能的依赖循环相匹配, Bower, 以及其他几十个组件. 小时后, 很明显,从头开始要容易得多——这是笔者本人的反对意见 重新发明轮子 尽管.

旧发明的新发明:用一个节点把手机改造成遥控器.js后端

首先,请注意这个Node.js项目目前特定于Linux -在Linux Mint 19和Linux Mint 19上开发和测试.特别是3,但当然可以添加对其他平台的支持. It may 已经在Mac上运行了.

假设是现代版的 Node.js 安装, 命令提示符将在作为项目根目录的新目录中打开, 我们准备好开始使用表达了:

NPX express-generator -view=pug

注意:在这里, npx 是随附的方便工具吗 npm节点.Node . js包管理器.js. 我们用它来运行表达的应用程序骨架生成器. 在撰写本文时,生成器生成了表达/Node.Js项目, 默认情况下, 仍然使用一个名为Jade的模板引擎, 尽管Jade项目 改名为“帕格” 从版本2开始.0开始. 因此,为了跟上潮流,直接使用Pug,避免出现弃用警告,我们增加了一些内容 ——视图=哈巴狗的命令行选项 express-generator 正在运行的脚本 npx.

完成这些之后,我们需要从Node安装一些包.Js项目中新填充的依赖项列表 package.json. 传统的方法是跑步 npm i (i “安装”). 但有些人仍然喜欢的速度 Yarn,因此,如果您安装了该程序,只需运行即可 yarn 没有参数.

在这种情况下,应该可以安全地忽略(希望很快就会修复) 弃用警告 从帕格的一个子依赖项, 只要在本地网络上按需访问即可.

一个快速 纱线开始 or npm开始,其次是 导航到 localhost: 3000 在浏览器中显示了我们基本的基于express的Node.Js后端工作. 我们可以用 Ctrl+C.

Node.js后端教程,第2步:如何在主机上发送击键

remote 部分已经完成了一半,让我们把注意力转向 控制 part. 我们需要一些能够以编程方式控制运行Node的机器的东西.Js的后端,假装它在按键盘上的键.

为此,我们将安装 xdotool using 它的官方指示. 在终端中快速测试他们的示例命令:

xdotool搜索“Mozilla Firefox”窗口激活——同步键——clearmodifiers ctrl+l

……应该完全按照它所说的做,假设当时Mozilla Firefox是打开的. 这很好! 很容易得到我们的节点.Js项目调用命令行工具,如 xdotool,我们很快就会看到.

Node.js后端教程,步骤3:功能设计

这可能并不适用于所有人, 但是就个人而言, 我发现,许多现代实体遥控器的按键数量大约是我将要使用的按键数量的五倍. 对于这个项目, 我们看到的是一个全屏布局,有一个3乘3的网格, big, 容易到达按钮. 这九个按钮是什么取决于个人喜好.

事实证明,即使是最简单的功能,键盘快捷键也不尽相同 网飞公司, YouTube, and 亚马逊Prime视频. 这些服务也不能像本地音乐播放器应用程序那样使用通用媒体键. 此外,某些功能可能无法在所有服务中使用.

因此,我们需要做的是为每个服务定义不同的远程控制布局,并提供在它们之间切换的方法.

定义远程控制布局并将其映射到键盘快捷键

让我们用一些预设快速制作一个原型. 我们把它们放进去 常见/ preset_命令.js“通用”是因为我们会从多个文件中包含这些数据

module.出口= {
  //我们可以使用⏯️,但是一些旧的手机(例如.g.、Android 5.1.1)不会显示它,因此▶️代替
  “网飞公司”:{
    命令:{
      “-”:“逃跑 ',        '+': ' f ',             ' 🔊‘:’’,
      “⇤”:“XF86Back”、“▶️”:“回来了 ',        ' 🔉”:“下来”,
      “⏪”:“离开了 ',         ' ⏩‘:’ ',        ' 🔇”:“m”,
    },
  },
  “YouTube”:{
    命令:{
      “⇤”:“shift + p ',       ' ⇥‘:’shift + n ',       ' 🔊‘:’’,
      “CC”:“c ',            ' ▶️‘:’k ',             ' 🔉”:“下来”,
      “⏪”:“j ',            ' ⏩‘:’l ',            ' 🔇”:“m”,
    },
  },
  '亚马逊Prime视频': {
    window_name_override: 'Prime Video',
    命令:{
      “⇤”:“逃跑 ',        '+': ' f ',              ' 🔊‘:’’,
      “CC”:“c ',            ' ▶️‘:’空间 ',          ' 🔉”:“下来”,
      “⏪”:“离开了 ',         ' ⏩‘:’ ',         ' 🔇”:“m”,
    },
  },
  “通用/音乐播放器”:{
    window_name_override:”,
    命令:{
      '↑':'XF86AudioPrev', '↑':'XF86AudioNext', '🔊':'XF86AudioRaiseVolume',
      “🔀”:“r ',            ' ▶️‘:’XF86AudioPlay”、“🔉”:“XF86AudioLowerVolume”,
      “⏪”:“离开了 ',         ' ⏩‘:’ ',         ' 🔇”:“XF86AudioMute”,
    },
  },
};

keycode值可以是 发现使用 xev. (For me, 使用这种方法无法发现“音频静音”和“音频播放”, 所以我也咨询了 媒体键列表.)

读者可能会注意到两者的区别 space and Return-不管原因是什么,这个细节必须得到尊重 xdotool 要正常工作. 与此相关,我们有几个明确的定义.g., shift+p 尽管 P 为了让我们的意图清晰,也会起作用吗.

Node.js后端教程,第4步:我们的API的“关键”端点(原谅双关语)

我们需要一个端点 POST To,这反过来将模拟使用 xdotool. 因为我们可以发送不同的密钥组(每个服务一个), 我们称端点为一个特定的点 group. 我们将重新使用生成的 users 通过重命名端点 路线/用户.js to 路线/组.js,并在 app.js:

// ...

var indexRouter = require('./线路/指数”);
var groupRouter = require('./线路/集团”);

// ...

app.使用(' / ',indexRouter);
app.使用(/组,groupRouter);

// ...

The key 功能性就是使用 xdotool 通过系统shell调用 路线/组.js. 我们将硬编码 YouTube 作为目前的选择菜单,只是为了测试的目的.

Const express = require('express');
Const router = express.路由器();
Const debug = require('debug')('app');
Const cp = require('child_process');
preset_命令 = require('../共同/ preset_命令 ');

/* POST按键模拟*/
router.Post ('/', function(req, res, next) {

  Const keystroke_name = req.body.keystroke_name;
  const keystroke_code = preset_命令(“YouTube”).命令(keystroke_name);
  Const final_command = ' xdotool \
  搜索“YouTube”\
  Windowactivate——sync \
  ${keystroke_code} ';

  调试(“执行$ {final_command}”);
  cp.exec(final_command, (err, stdout, stderr) => {
    调试(“$ {keystroke_name}执行”);
    返回res.重定向(要求.originalUrl);
  });
});

module.Exports = router;

中获取所请求的键“name” POST 请求主体(req.body)下的参数 keystroke_name. 大概是 ▶️. 然后我们用它来查找对应的代码 preset_命令(“YouTube”)’s 命令 object.

最后一个命令在多行上,所以 \每行末尾的S将所有片段连接成单个命令:

  • 搜索“YouTube” 获取第一个标题为“YouTube”的窗口.
  • windowactivate——同步 激活获取的窗口并等待,直到它准备好接收击键.
  • ${keystroke_code} 发送击键, 确保暂时清除修改键,如Caps Lock,可能会干扰我们发送的内容.

此时此刻, 该代码假定我们向它提供了有效的输入—稍后我们将更加注意这一点.

为简单起见, 代码还会假设只有一个标题为“YouTube”的应用程序窗口打开——如果有多个匹配项, 不能保证我们会将击键发送到指定的窗口. 如果有问题的话, 除了要远程控制的窗口外,还可以通过切换所有窗口的浏览器选项卡来更改窗口标题,这可能会有所帮助.

准备好了, 我们可以重新启动服务器, 但是这次启用了调试,所以我们可以看到 debug calls. 要做到这一点,只需跑步 DEBUG=老式远程:* yarn启动 or DEBUG=old-fashioned-remote:* npm开始. 一旦它运行,在YouTube上播放视频,打开另一个终端窗口,并尝试cURL调用:

Curl——data "keystroke_name=▶️" http://localhost:3000/group

这会发送一个 POST 请求将请求的击键名称在其主体中发送到端口上的本地计算机 3000,我们的后端正在监听的端口. 运行该命令应该输出有关的注释 执行 and 执行 in the npm 窗口,更重要的是,打开浏览器并暂停其视频. 再次执行该命令应该会得到相同的输出并取消暂停.

Node.js后端教程,第5步:多个远程控制布局

我们的后端还没有完全完成. 我们还需要它能够:

  1. 生成远程控制布局的列表 preset_命令.
  2. 一旦我们选择了一个特定的远程控制布局,生成一个击键“名称”列表. (我们也可以用 常见/ preset_命令.js 直接在前端,因为它已经是JavaScript了,并在那里过滤. 这是Node的潜在优势之一.Js的后端,我们在这里不用它.)

这两个特性都是我们的Node.js后端教程与我们将要构建的基于pug的前端交叉.

使用Pug模板显示远程控制列表

方程的后端意味着修改 路线/索引.js 看起来像这样:

Const express = require('express');
Const router = express.路由器();
preset_命令 = require('../共同/ preset_命令 ');

/*获取主页. */
router.Get ('/', function(req, res, next) {
  const group_name =对象.键(preset_命令);
  res.呈现(“指数”,{
    标题:“哪个遥控器??',
    group_name,
    portrait_css:“.group_bar {
      高度:钙(100% / ${数学.group_name min(4日.长度)});
      行高:钙(100 vh / ${数学.group_name min(4日.长度)});
    }`,
    landscape_css:“.group_bar {
      高度:钙(100% / ${数学.group_name分钟(2.长度)});
      行高:钙(100 vh / ${数学.group_name分钟(2.长度)});
    }`,
  });
});

module.Exports = router;

在这里,我们获取远程控制布局名称(group_name)。 Object.keys on our preset_命令 file. 然后我们将它们和其他一些我们需要的数据发送给Pug模板引擎,该引擎会自动通过 res.呈现().

注意不要混淆…的意思 keys 拿着钥匙strokes 我们发送: Object.keys 给出一个数组(有序列表),其中包含所有的 keys of the 键值对 在JavaScript中组成一个对象:

const My_object = {
  “关键”: '其对应的值',
  另一个关键的: '它的单独对应值',
};

如果我们看一下 常见/ preset_命令.js,我们将看到上面的模式,以及我们的 keys (在宾语意义上)是我们组的名称: “网”, “YouTube”, etc. 它们对应的值不是简单的字符串 my_object 它们本身就是完整的物体,有自己的钥匙,对吗.e., 命令 并有可能 window_name_override.

不可否认,这里传递的自定义CSS有点粗糙. 我们需要它而不是使用现代的原因, 基于flexbox的解决方案是为了更好地兼容移动浏览器的美好世界,甚至是更美好的旧版本. 在这种情况下, 主要要注意的是,在横向模式下, we’re keeping buttons big by showing no more than two options per screenful; in portrait mode, four.

但它是在哪里被转换成HTML发送给浏览器的呢? 这就是 视图/索引.pug 进来,我们想让它看起来像这样:

延伸布局

块header_injection
  Style (media='(orientation: portrait)') #{portrait_css}
  Style (media='(orientation: landscape)') #{landscape}

块的内容
  group_name中的每个group_name
    跨度(class = " group_bar”)
      一群(href = ' / /?Group_name =' + Group_name ') #{Group_name}

第一行很重要: 延伸布局 意味着Pug将把这个文件放在 视图/布局.pug这是一种父模板,我们会在这里和另一个视图中重用. 的后面需要添加几行 link 行,使最终文件看起来像这样:

doctype html
html
  head
    标题=标题
    链接(rel =“样式表”,href = ' /样式表/风格.css')
    块header_injection
    元(name =“视窗”,内容=“user-scalable = no”)

  body
    块的内容

我们在这里不讨论HTML的基础知识, 但是对于不熟悉它们的读者, 这个Pug代码反映了随处可见的标准HTML代码. The 模板 首先是 标题=标题,它将HTML标题设置为与 title 我们让帕格经过的物体的钥匙 res.render.

我们可以在后面的两行中看到模板化的不同方面 block 我们命名 header_injection. 像这样的块是占位符,可以用扩展当前块的模板替换. (不相关, meta Line只是一个针对移动浏览器的快速解决方案, 所以当用户连续多次点击音量控制时, 手机不会放大或缩小.)

回到我们的 blocks:这就是原因 视图/索引.pug 定义自己的 blockS中有相同的名字 视图/布局.pug. 在这个例子中 header_injection,这让我们可以使用特定于手机纵向或横向方向的CSS.

内容 是我们放置网页的主要可见部分的地方,在这种情况下:

  1. 循环通过 group_name 数组,我们传递它,
  2. 创建一个 元素为每个CSS类 group_bar 应用于它,和
  3. 在每个中创建一个链接 基于 group_name.

CSS类 group_bar 我们可以在导入的文件中定义via 视图/布局.pug,即 公共/样式表/风格.css:

Html, body, form {
  填充:0;
  保证金:0;
  高度:100%;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

.group_bar, .group_bar, .remote_button {
  box-sizing: border-box;
  边框:1px纯白色;
  颜色:greenyellow;
  背景颜色:黑色;
}

.group_bar {
  宽度:100%;
  字体大小:6 vh;
  text-align:中心;
  显示:inline-block;
}

.Group_bar a {
  文字修饰:没有;
  显示:块;
}

此时,如果 npm开始 还在跑,要去吗 http://localhost:3000/ 在桌面浏览器中应该显示两个非常大的网飞公司和YouTube按钮, 其余的可通过向下滚动.

使用桌面浏览器测试远程控制布局选择器, 显示了两个非常大的网飞公司和YouTube按钮.

但如果我们在这里点击它们, 它们不会起作用, 因为我们还没有定义它们链接到的路由 GETting of /group.)

显示选择的远程控制布局

为此,我们将把这个添加到 路线/组.js 就在决赛之前 module.出口 line:

router.Get ('/', function(req, res, next) {
  Const group_name = req.query.Group_name || ";
  Const group = preset_命令[group_name];

  返回res.呈现(“集团”,{
    keystroke_names:对象.键(集团.命令),
    group_name,
    标题:“$ {group_name.匹配(/ ([a - z]) / g).加入(")}远程的
  });
});

这将使组名发送到端点(e.g.,通过 ?group_name = 网飞公司 在…的末尾 /组/),并使用它来获得的值 命令 从相应的组中. 值()group.命令)是一个对象,该对象的键是名称(keystroke_names),我们将显示在我们的遥控器布局上.

注意:没有经验的开发人员不需要了解它是如何工作的细节,但是它的价值 title 使用一点 正则表达式 将我们的组/布局名称转换为首字母缩写-例如, 我们的YouTube遥控器将有浏览器标题 YT-Remote. 这样,如果我们在手机上进行调试之前先在主机上进行调试,我们就不会有 xdotool 抓住远程控制浏览器窗口本身,而不是我们试图控制的窗口. 与此同时, 在手机上, 标题要简洁美观, 我们要不要把遥控器收藏起来.

就像我们之前遇到的 res.render,这个将发送数据与模板混合 视图/组.pug. 我们将创建该文件并填充如下内容:

延伸布局

块header_injection
  脚本(type = ' text / javascript ', src = ' / javascript /集团客户.js')

块的内容
  形式(action = " /组?Group_name =" + Group_name, method="post")
    keystroke_name中的每个keystroke_name
      输入(type="submit", name="keystroke_name", value= "keystroke_name", class="remote_button")

As with 视图/索引.pug,我们要覆盖两个博客 视图/布局.pug. 这一次, 我们在页眉里放的不是CSS, 还有一些客户端JavaScript, 我们很快就会讲到. (是的,在一时的挑剔中,我重命名了不正确的复数形式 javascript…)

主要的 内容 这是一个HTML表单,由一堆不同的提交按钮组成,每个按钮一个 keystroke_name. 每个按钮提交表单(生成一个 POST 请求)使用它显示的键击名称作为它与表单一起发送的值.

我们还需要在主样式表文件中添加更多的CSS:

.remote_button {
  浮:左;
  宽度:calc / 3 (100%);
  高度:钙(100% / 3);
  字体大小:12 vh;
}

前面,当我们设置端点时,我们完成了对请求的处理:

返回res.重定向(要求.originalUrl);

这实际上意味着,当浏览器提交表单时,Node.Js后端通过告诉浏览器返回到提交表单的页面来进行响应.e.,主要进行遥控器布局. It would be more elegant without switching pages; however, 我们想要最大限度地兼容陈旧的移动浏览器的怪异和奇妙的世界. 这样,即使没有任何前端JavaScript工作,我们的Node.Js后端项目 should 还是功能.

一点前端JavaScript

使用表单提交击键的缺点是浏览器必须等待, 然后执行额外的往返:然后必须从我们的Node请求页面及其依赖项.Js后端和交付. 然后,它们需要由浏览器再次呈现.

读者可能想知道这可能会产生多大的影响. 毕竟,页面很小,它的依赖非常小,而我们最终的Node.Js的项目将在本地wifi连接上运行. 应该是一个低延迟的设置,对吧?

事实证明,至少在运行窗户。 Phone 8的老式智能手机上测试是如此.1和Android 4.4.不幸的是,在快速敲击以提高或降低几个档的播放音量的常见情况下,效果相当明显. 这就是JavaScript可以提供帮助的地方,同时又不影响我们优雅的手工操作 POST通过HTML表单.

此时,我们的最终客户端JavaScript(要放入) 公共/ javascript /集团客户.js)需要兼容旧的,不再支持的移动浏览器. 但我们不需要太多。

(function () {
  函数form_submit(event) {
    var request = new XMLHttpRequest();
    request.(“文章”,窗口打开.位置.路径名+窗口.位置.搜索,真实);
    request.setRequestHeader(“内容类型”,“应用程序/ x-www-form-urlencoded”);
    request.send('keystroke_name=' + encodeURIComponent '.target.值));
    event.preventDefault ();
  }
  window.addEventListener("DOMContentLoaded", function() {
    Var输入=文档.querySelectorAll(“输入”);
    for (var i = 0; i < inputs.length; i++) {
      输入[我].addEventListener(“点击”,form_submit);
    }    
  });
})();

在这里, form_submit 函数只是通过异步调用发送数据, 最后一行阻止浏览器的正常发送行为, 根据服务器响应加载新页面. 这段代码的后半部分只是等待页面加载,然后连接每个提交按钮以供使用 form_submit. 整件事都被包裹在 一个IIFE.

最后

有很多 变化 到我们的Node的最终版本中的上述代码片段.Js后端教程代码,主要是为了更好地处理错误:

  • 的节点.Js的后端现在检查发送给它的组名和击键,以确保它们存在. 这段代码位于一个函数中,该函数在 GET and POST 的功能 路线/组.js.
  • 我们利用帕格 error 模板,如果没有的话.
  • 前端JavaScript和CSS现在使按钮在等待服务器响应时暂时呈现灰色轮廓, 绿灯一亮就亮了 xdotool 然后顺利返回,如果有什么不正常,就发红色.
  • 的节点.Js后端将打印堆栈跟踪,如果它死了,这将是不太可能的.

欢迎读者阅读(和/或克隆)完整的Node.js项目 GitHub上.

Node.js后端教程,第5步:真实世界的测试

现在是时候在一台与主机连接相同wifi网络的手机上进行测试了 npm开始 还有一个电影或音乐播放器. 这只需要将智能手机的web浏览器指向主机的本地IP地址(使用 :3000 它的后缀),这可能是最容易找到的运行 主机名-I | awk '{print $1}' 在主机的终端上.

窗户。 Phone 8有一个问题.用户可能会注意到的一个问题是,试图导航到类似于 192.168.2.5:3000 将给出一个错误弹出:

A screenshot of a 窗户。 Phone error message titled "Unsupported address," saying "Internet Explorer Mobile doesn't support this type of address and can't display this page.

值得庆幸的是,没有必要气馁:只需在前缀中加上 http:// 或者添加尾符 / 让它获取地址,不再抱怨.

遥控器布局选择界面.

在那里选择一个选项应该就能找到一个可以工作的遥控器.

“通用/音乐播放器”遥控器屏幕.

为了更方便, 用户可能希望调整路由器的DHCP设置,以便始终为主机分配相同的IP地址, 并收藏布局选择屏幕和/或任何喜欢的布局.

欢迎拉取请求

很可能不是每个人都喜欢这个项目的现状. 对于那些想要深入研究代码的人来说,这里有一些改进的想法:

  • 调整布局或为其他服务添加新布局应该很简单, 比如迪士尼Plus.
  • 也许有些人更喜欢“光模式”布局和选项之间的切换.
  • 退出网飞公司,因为它是不可逆转的,真的可以用一句“你确定吗??“某种形式的确认.
  • 该项目肯定会受益于 窗户。 support.
  • xdotool的文档中确实提到了osx——这个(或者这个)项目能在现代Mac上工作吗?
  • 高级休闲, 一种搜索和浏览电影的方式, 而不是在电脑上选择一部网飞公司/亚马逊Prime视频电影或创建一个YouTube播放列表.
  • 一个自动化的测试套件,以防任何建议的更改破坏了原始功能.

我希望你喜欢这个节点.Js后端教程和改进的媒体体验. 快乐的流媒体和编码!

了解基本知识

  • 是节点.Js用于后端?

    Yes. Node.js是一个运行JavaScript代码的命令行程序,它通常用于web主机上提供网页, 连接数据库, 等等......。.

  • 是节点.Js对于后端来说已经足够了?

    绝对. 架构正确的节点.Js的后端可以像任何技术一样扩展. 也就是说, 它通常与其他重要组件集成在一起, 比如访问应用程序的数据库层.

  • 什么是快递.js?

    表达是Node的一个模块.Js,减少了编写通用web服务器功能所需的样板代码的数量. 它有自己成熟的子生态系统. 大多数节点.. js web服务器使用表达.

  • 什么是帕格/杰德?

    Pug(以前的Jade)是一个与表达集成的模板引擎. 事实上, 多年来, 它一直是表达项目生成器在新项目中包含的默认模板引擎.

  • 什么是xdotool?

    命令行程序xdotool在它运行的计算机上模拟击键. 这个项目让手机通过网页在电脑上执行这些操作, 把它变成遥控器.

就这一主题咨询作者或专家.
预约电话
凯文·布洛赫的头像
凯文·布洛赫

位于 Bergerac、法国

成员自 2017年1月31日

作者简介

Kevin拥有超过20年的全栈、桌面和独立游戏开发经验. 他最近专门研究PostgreSQL、JavaScript、Perl和Haxe.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

工作经验

8

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.