前端node.js上传文件到服务器_从零开始: nodejs 搭建文件索引服务器(Part 1)

2023-11-15

在考虑要用nodejs搭建文件索引服务器来替代我原来那个笨重的Apache服务器(采用省心的bitnami lampstack搭建,然后配了背景图片和图标)之前,说实话我连javascript是什么都不清楚。所以,从语言上讲,确实是从零开始。

但另一方面,我大致知道“菜鸟教程”上有各种语言、环境的入门教程,我还学过C语言,用C语言写过五子棋(课程大作业),用C语言写过单片机外设的驱动,用verilog写过硬件逻辑,用Matlab写过简单的数字信号处理程序、语音合成、图像分析等。没错,都是电子信息工程的必修课程。这些任务大多数需要边做任务边学习(复习),特别是Matlab作业,需要在完全不知道相关概念的情况下自行查阅网上资料现学现用,因此极大锻炼了我编程处理问题的能力,同时也让我有勇气在完全不知道javascript和nodejs特性的情况下、仅凭网上“用nodejs搭建简单的文件服务器”的帖子就敢开始这个项目。

废话就说到这,下面我们开始。

0 下载nodejs

我手头的环境是wsl1, 这年头不会一点linux的估计也不会看这篇文档的吧。相较于windows,将服务器部署在linux显然是更为方便的,但在轻薄本上测试。。。好吧,wsl上。下载安装包我们选择官网http://nodejs.cn/download/,选择linux x86_64下载即可。下好后解压,运行bin/node即可。当然,也可以选择用aptyumpacman安装。打开node之后,就可以看到node解释终端,和python解释终端别无二致。

1 用nodejs搭建简单的文件服务器

就像网上的帖子,在这里,你只需要复制粘贴代码,初步感受一下简单的nodejs服务器是什么样的。

代码:

var http = require('http');
http.createServer(function (request, response) {
 response.writeHead(200, {'Content-Type': 'text/plain'});
 response.end('Hello Worldn');
}).listen(8888);
console.log('Server running at http://127.0.0.1:8888/');

好了,现在将其保存为“hello.js”, 然后用nodejs运行它: node hello.js, 打开浏览器,输入localhost:8888, 你就可以看见“Hello World”的字样了。所以,挺神奇的吧,原来要好大的lampstack才能实现的东西,原来在nodejs里几行代码就能初步实现。而且,代码是可见的,这意味着DIY起来将会很方便,也不需要看很多特定的文档,就像当初手动配置LAMP环境一样,除了安装linux, 没有一点是容易的。

下面,我们来逐句分析一下代码:

var http = require('http'); //载入http模块,具体查阅 nodejs 载入模块 相关内容
http.createServer(...) // 调用http属性createServer, 创建服务器
.listen(8888) // 其实应该写作http.listen(8888), 至于为啥省略了http, 是因为creatServer方法返回值就是http. 刚入门时对对象、方法、属性很难理解,查看了“Node.js模块系统”有关内容会好很多。
console.log('Server running at localhost:8888); // 这句最好理解:终端打印日志...

上面确实是逐句分析的,当然参数没有分析进去。注意到createServer函数的参数有点特别,是一个“无名函数”:

function (request, response) {
 response.writeHead(200, {'Content-Type': 'text/plain'});
 response.end('Hello Worldn');
}

这个函数需要两个参数,一个是request 一个是response, 之后在正式着手写服务器时会经常见到它们。这个函数做了两件事,一个是response.writeHead(), 调用response的writeHead方法写个http响应头, 还有一个是.end(), 调用end方法写点数据,并结个尾。

我们习惯上吧没有名字的函数称为匿名函数,匿名函数还有一种写法就是

(request, response) => {}

看着就像 给什么“()”, 然后“=>”,做什么“{}”,这种感觉。两种写法具体区别自行查阅。

2 目标

我们的目标肯定不是写一个hello world, 而是要写一个像apache文件索引服务那样的东西。具体来说,肯定需要这些功能:

A 请求啥文件,发送啥文件

B 请求是个目录,目录下没有index.html, 上传目录结构

C 请求是个目录,目录下有index.html,自行判断要不要跳转到目录下的index.html

另外,好不容易能DIY,应该也需要写一些额外的功能:

D 利用HTML5 audio标签 和已知的目录文件列表(B中实现)实现简单的音乐播放器

E 上传文件 重命名文件 新建文件夹等

F 系统监视器(利用child_process中的exec调用shell命令,或者用fs模块读取/proc内文件并显示)

3 发送文件

首先我们要解决的当然是发送Index.html的问题。最简单的方法:

var htmlFile = “../*这里写入你的html文本*/...”
...
Response.write(htmlFile);
Response.end();
...

简单易懂,但要是换一个文档呢?或者这个文档很大呢?或者这个文档压根就不是文本文档而是二进制文档呢?显然,这种方法是行不通的。

如果是C语言,我们便可以使用fread等文件读写相关接口来读写硬盘文件。因此我们猜想,nodejs也一定存在这样的模块或方法来实现这个功能。通过查阅资料我们可以找到这个十分有用的模块fs, 同时还找到了它诸多的属性和方法。假设我们现在已经学会了fs读取文件的操作(暂时称为fs.read(path)),那我们就可以这么写:

var htmlFile = fs.read(“./index.html”);
...
Response.write(htmlFile);
Response.end();
...

仔细查阅相关资料,我们会发现更过有用的工具,例如pipe, fs.createReadStream, fs.readdir, fs.rename, fs.mkdir, fs.stat 等一系列与file system相关的东西。有了这个重要的模块,好像我们离目标不远了。使用了fs后,文件读取变成了下面的模样:

function sendFile_0(realPath, Request, Response, callback) {
    try {
        var stats = fs.statSync(realPath);
        if (stats.isDirectory()) {
            callback("error: isDirectory");
            return;
        }
        Response.statusCode = 200;
        Response.setHeader("Content-Length", stats.size);
        fs.createReadStream(realPath).pipe(Response);
    } catch (err) {
        callback(err);
    }
}
 
var http = require("http");
var fs = require("fs");
http.createServer((Request, Response) => {
    sendFile_0("/mnt/d/test/01._Take_Me_Away.mp3", Request, Response, console.log);
}).listen(7777);

现在开始,可以不妨享受以下自己做出的成果了,来听一段音乐吧。

3.1 parseRange

可是,这音乐怎么没法跳转进度呢?这和Apache服务器上不一样啊。查找资料,我们找到了http Header 中Range这一选项。查看浏览器发出的header, 果然有着一条:

GET / HTTP/1.1
...
Range: bytes=0-

但我们发出的header中确没有对其做回应——甚至我们压根忽视了它的存在。查阅资料可以发现Range的格式一般有以下几种,同时也涉及到HTTP状态码。其中206 Particial Content 便是我们需要的状态。几种例子在下面都已经展现,这里参考的内容为csdn的博客https://blog.csdn.net/thewindkee/article/details/80189434。

// Examples: 1.Range: bytes=1-499 (1-499 Bytes) 2.Range: bytes=-500 (last 500 Bytes)
// 3. Range: bytes=500- (500-end Bytes) 4.Range: bytes=500-600,601-999
// Res: Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity length]
// Examples: Content-Range: bytes 1-499/22400
// HTTP/1.1 200 Ok (No using resume from break point) 
// HTTP/1.1 206 Partial Content (Using resume from break point)

现在,我们已经知道请求头和响应头中Range相关的内容。想要给出响应头及对应的响应内容,自然的,首先需要解析请求头中的Range属性。通过Request.headers["range"]可以得到Range属性值,即诸如“bytes=500-1000”的内容,接下来要做的就是对这个内容做分析。

首先,想到找到“=”的位置,取得字符串“500-1000”,然后利用split(“-”)对字符串分割得到500 和 1000, 这两个就是请求范围的start 和 end。很简单吧? 可要是其它情况呢?

全盘考虑所有情况,首先我们应当排除情况4“500-600,601-999”,这个情况的特点是多个Range,Range之间使用“,”隔开。因此我们利用“,”做分隔便可得到一个表示请求范围的字符串数组。逐个处理即可。

接着我们处理单个字符串,其情况有三种“1-499”“-500” “500-”,利用“-”分割后,共有三种情况[1,499] [NaN,500] [500,NaN],记为[start,end] 因此 ,通过isNaN函数分别判断start和end便可得到正确的Range信息。

上述过程代码如下:

function parseRange(rangeStr, filesize) {
    if (rangeStr.indexOf("=") == -1 || filesize <= 0) return;
    var rangeStr = rangeStr.substring(rangeStr.indexOf("=") + 1, rangeStr.length);
    var rangeList = rangeStr.split(",");
    var results = [];
    for (var i in rangeList) {
        var range = rangeList[i].split("-");
        var coRange = { "start": parseInt(range[0], 10), "end": parseInt(range[1], 10) };
        if (isNaN(coRange.start)) {
            coRange.start = filesize - coRange.end;
            coRange.end = filesize - 1;
        } else if (isNaN(coRange.end)) {
            coRange.end = filesize - 1;
        }
        if (!isNaN(coRange.start) && !isNaN(coRange.end) && coRange.start <= coRange.end)
            results.push(coRange);
    }
    return results;
}

3.2 其它HTTP头

附带地,我们还学会在浏览器中摁F12调出调试界面查看浏览器发送的头。为确定我们搭建的简易服务器和普通的服务器在Header中到底差了啥, 我们打开某网页,摁下F12,

HTTP/1.1 200 OK 
Server: nginx/1.15.12 
Date: Sat, 22 Feb 2020 11:12:02 GMT 
Content-Type: text/html 
Content-Length: 6204 
Last-Modified: Sun, 16 Feb 2020 16:08:00 GMT 
Connection: keep-alive 
ETag: "5e4968e0-183c" 
Expires: Sat, 22 Feb 2020 23:12:02 GMT 
Cache-Control: max-age=43200 
Accept-Ranges: bytes

在其中找到Etag Last-Motified Cache-Control Content-Length Accept-Ranges Content-Type 等信息,查阅资料可以知道各自的含义。例如Etag和Last-Motified记录文件更新信息,如果文件未更新,可以发送304状态码以减少不必要的文件传输。Cache-Control可以控制是否缓存,何种方式缓存,最长缓存期限等. Content-Type 记录文件属性,一般可根据后缀名获取。加入这些信息,最终,我们的简单文件服务器如下:

var MimeSet = {
    "css": "text/css", "gif": "image/gif", "html": "text/html",
    "php": "text/html", "ico": "image/x-icon", "jpeg": "image/jpeg",
    "jpg": "image/jpeg", "js": "text/javascript", "json": "application/json",
    "pdf": "application/pdf", "png": "image/png", "svg": "image/svg+xml",
    "swf": "application/x-shockwave-flash", "tiff": "image/tiff", "txt": "text/plain",
    "wav": "audio/x-wav", "wma": "audio/x-ms-wma", "wmv": "video/x-ms-wmv",
    "xml": "text/xml", "mp3": "audio/mpeg", "mp4": "video/mp4"
};
 
function TypeChoose(realPath) {
    var suffix = realPath.match(/(.[^.]+|)$/)[0];
    suffix = suffix.slice(1);
    if (typeof MimeSet[suffix] == "undefined") return "application/octet-stream";
    else return MimeSet[suffix];
}
 
function parseRange(rangeStr, filesize) {...}
 
function sendFile(realPath, Request, Response, callback) {
    try {
        var stats = fs.statSync(realPath);
        if (stats.isDirectory()) {
            callback("error: isDirectory");
            return;
        }
        var LastModified = stats.mtime.toUTCString();
        var Etag = 'W/"' + stats.size.toString(16) + '-' + stats.mtime.getTime().toString(16) + '"';
 
        if (Request.headers["if-none-match"] == Etag) {
            Response.statusCode = 304;
            Response.end();
            return;
        }
 
        Response.setHeader("Accpet-Ranges", "bytes");
        Response.setHeader("Cache-Control", "public, max-age=0");
        Response.setHeader("Content-type", TypeChoose(realPath));
        Response.setHeader("Last-Modified", LastModified);
        Response.setHeader("Etag", Etag);
 
        if (Request.headers["range"]) {
            var range = parseRange(Request.headers["range"], stats.size);
            if (range.length) { // has ranges
                range = range[0]; // only trans the first
                Response.statusCode = 206;
                Response.setHeader("Content-Length", (range.end - range.start + 1));
                Response.setHeader("Content-Range",
                    "bytes " + range.start + "-" + range.end + "/" + stats.size);
                fs.createReadStream(realPath, {
                    "start": range.start, "end": range.end
                }).pipe(Response);
            } else {
                Response.statusCode = 416;
                Response.end();
            }
        } else {
            Response.statusCode = 200;
            Response.setHeader("Content-Length", stats.size);
            fs.createReadStream(realPath).pipe(Response);
        }
 
    } catch (err) {
        callback(err);
    }
}
 
var http = require("http");
var fs = require("fs");
 
http.createServer((Request, Response) => {
    sendFile("/mnt/d/test/01._Take_Me_Away.mp3", Request, Response, console.log);
}).listen(7777);

现在,我们的音乐终于可以想跳哪就跳哪了。文件读取的部分也全部完成。此时,我们的响应头是这样的:

HTTP/1.1 206 Partial Content
Accpet-Ranges: bytes
Cache-Control: public, max-age=0
Content-type: audio/mpeg
Last-Modified: Sat, 02 Nov 2019 13:27:24 GMT
Etag: W/"6ccdec-16e2c4c8e26"
Content-Range: bytes 4554752-7130603/7130604
Date: Sat, 22 Feb 2020 12:55:07 GMT
Connection: keep-alive
Content-Length: 7130604

4 404页面和重定向

404页面非常简单,写就完事儿了。重定向主要靠发送location头和修改状态码(为301或302)来实现。代码如下:

function send404(Response) {
	Response.statusCode = 404;
       	Response.setHeader('Content-Type', 'text/html; charset=utf-8');
       	Response.write('<div style="text-align:center;font-weight:bold;font-size:12vw;');
       	Response.write('top:50%;left:50%;transform:translate(-50%,-50%);position:fixed;">');
       	Response.end('404 Not Found</div>');
}
 
function reDirect(statusCode, url, Request, Response) {
       	Response.statusCode = statusCode;
       	if (url == "back")
          	Response.setHeader('Location', Request.headers["referer"] || "/");
       	else
            	Response.setHeader('Location', url);
        Response.end();
}

未完待续...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

前端node.js上传文件到服务器_从零开始: nodejs 搭建文件索引服务器(Part 1) 的相关文章

  • macm1环境下jdk版本切换

    macm1环境下jdk版本切换 本文目录 macm1环境下jdk版本切换 下载jdk 安装 动态切换jdk 终端生效 全局生效 参考 下载jdk oracle官方源下载地址 https www oracle com java technol
  • 一个新的微型JSON开源框架

    Snack3 一个新的微型JSON框架 一个作品 一般表达作者的一个想法 因为大家想法不同 所有作品会有区别 就做技术而言 因为有很多有区别的框架 所以大家可以选择的框架很丰富 snack3 基于jdk8 60kb 无其它依赖 非常小巧 强
  • OperationalError: (2005, "Unknown MySQL server host 'localhost' (11001)")

    在调试Django时突然报OperationalError 2005 Unknown MySQL server host localhost 11001 这个错误 根据提示信息判断是说mysql server 无法识别 localhost
  • $nextTick的作用和使用场景

    nextTick的作用和使用场景 vue中的nextTick主要用于处理数据动态变化后 DOM还未及时更新的问题 用nextTick就可以获取数据更新后最新DOM的变化 适用场景 第一种 有时需要根据数据动态的为页面某些dom元素添加事件
  • 解决延迟有 Wi-Fi 6 就够了!

    最近二狗子家里的路由器坏了 而家里的数据网络信号又非常差 失去了路由器基本上就等于和世界隔离 所以二狗子打算去附近商城随便买一个新的路由器 结果售货员张口就问 买 Wi Fi 6 的路由器吗 Wi Fi 6 这直接把二狗子问懵了 Wi Fi
  • Serverless Kubernetes 应用部署及扩缩容

    作者 邓青琳 轻零 阿里云技术专家 导读 本文分为三个部分 首先给大家演示 Serverless Kubernetes 集群的创建和业务应用的部署 其次介绍 Serverless Kubernetes 的常用功能 最后对应用扩缩容的操作进行
  • 医学图像分割研究思路

    医学图像分割的主流方法之一是基于水平集 Level Set 的分割方法 目前针对主流的分割方法 我们主体研究思路如下图 在模型凸化以及形状先验两个方面 未开展相关工作 参考文献 部分演示代码 参数随图像需要调整 7 Xiaomeng Xin
  • 重新生成一堆rpm目录的repo库步骤

    createrepo c repo2module s stable modules yaml modifyrepo c mdtype modules modules yaml repodata
  • IntelliJ IDEA插件搜索下载缓慢

    我用的版本是2019 2 1 搜索插件特别慢 有时候加载不出来 看到别人说是用 Setting Appearance Behavior Syetem Setting Updates 将Use secure connection 的勾选去掉
  • java----面向对象和面向过程

    1 面向对象思想 面向对象四星思想就是把关注点放在一件事或一个活动中设计的人或事物上的思想 2 面向过程思想 面向过程思想就是把关注点放在一件事或一个活动中设计的人或事物所涉及的步骤上的思想 3 面向过程关键字 步骤 过程 4 面向对象关键
  • 一周 8k Star 的 Notion 开源替代品 AppFlowy 诞生

    近日 Notion 的开源替代品 AppFlowy 正式发布了 一经发布 在短短一周就获得了近 8k Star 这个成绩对于一个开源项目来说是非常不错的 那么为什么有了 Notion AppFlowy 团队却要从头开始开发一个类似的产品呢
  • 框架--SpringWeb

    文章目录 一 springweb 1 概述 2 springWeb层搭建 3 请求中的地址如何定义 4 如何接收请求中的数据 5 直接使用对象接收 6 post请求中文乱码处理 7 Ajax 返回 JSON 8 跨域问题 9 拦截器 10
  • string头文件常用方法(C++)

    string 定义字符串 如果未赋初值 则默认是 即空字符串 结尾也没有结束标志 0 include
  • 排序算法-希尔排序

    属性 1 希尔排序是对直接插入排序的优化 2 当gap gt 1时都是预排序 目的是让数组更接近于有序 当gap 1时 数组已经接近有序的了 这样就会很 快 这样整体而言 可以达到优化的效果 我们实现后可以进行性能测试的对比 3 希尔排序的
  • C++ 异常处理 入门

    C 异常处理 入门 异常 程序执行期间 可检测到的不正常情况 例如 0作除数 数组下标越界 打开不存在的文件 远程机器连接超时 malloc失败等等 程序的两种状态 正常状态和异常状态 发生不正常情况后 进入异常状态 从当前函数开始 按调用
  • Flex和Bison协同工作(下)

    Flex和Bison协同工作 下 上一篇文章我们写了一个稍微复杂一点点的词法解析器 这篇我们开始搞定语法分析器 文法与语法分析 语法分析器的任务其实就是找出输入记号之间的关系 通常使用语法分析树 parse tree 例如 算术表达式12
  • [4G&5G专题-62]:架构 - 开放的网络自动化平台ONAP(Open Network Automation Platform)

    目录 第1章 什么是开放的网络自动化平台ONAP 1 1 什么是ONAP 1 2 什么是的网络自动化平台 1 3 ONAP的动机与背景 1 4 ONAP的底层支撑技术 1 5 ONAP的前世 1 6 5G与ONAP 第2章 5G RAN的自
  • 你真的会用read()读【普通文件】吗?

    原型如下 include
  • 如何将json字符串写入table表格中

    JSON JavaScript Object Notation 是一种轻量级的数据交换格式 可以方便的将后台复杂的数据带回到前台进行展示 那么如何把json字符串写入到table表格之中呢 json简单说就是javascript中的对象和数

随机推荐

  • 美通企业日报

    今日看点 2019时尚育儿
  • java 常见错误合集

    java lang NullPointerException 这个异常都是因为调用null对象的方法 就是一个对象还没有没有正常初始化 就先调用它的方法比如 Object obj null obj toString 这就会抛出 这个异常
  • java中strictfp么意思_java中的strictfp的作用

    自Java2以来 Java语言增加了一个关键字strictfp 虽然这个关键字在大多数场合比较少用 但是还是有必要了解一下 strictfp 的意思是FP strict 也就是说精确浮点的意思 在Java虚拟机进行浮点运算时 如果没有指定s
  • 基于STM32控制的数字BUCK电路及程序编写

    本文芯片采用STM32G474CBT6 采用STM32cubeMX进行程序生成 BUCK电路拓扑结构 如图所示 BUCK变换器主要由电源VDC 场效应管MOSFET 续流二极管VD 电感L以及滤波电容C和负载RES组成 当MOSFET开通时
  • linux pxe安装视频,Linux利用PXE安装虚拟机的方法

    之前和友人聊天 知道可以利用PXE安装虚拟机 相信很多用户都和小编一样还不是很清楚 在这小编就把学习到的方法分享给大家 方法如下 1 1 dnsmasq apt get install dnsmasq vim etc dnsmasq con
  • elasticsearch sort script实现字段值等于某值排名靠前

    什么是script语言 script语言是es提供的一种支持自定义编程的用于复杂查询的脚本语言 主要类型有painless expressions等 需求描述 实际业务场景需要将某字段等于某值的排在前面 其他数据靠后 比如 雇员属性 nam
  • 阿里跟腾讯又㕛叒打起来了,这次是在东南亚

    腾讯与阿里的先头部队 已经在东南亚开始新的战争 在这之中 电商的争夺尤其激烈 神经浪游者 作者威廉 吉布森说 未来已来 只是分布得不太均匀 互联网的分布尤其如此 先是欧美 之后中国 如今 投资人和从业者纷纷押注 昔日的好时光将在东南亚将重现
  • sqlite3 提示 not found

    在android开发中使用adb shell下的sqlite3命令来查看SQLite数据库时 出现了 sqlite3 not found 错误 在网上搜索了一下问题的原因 原来是模拟器或真机中的 system xbin 目录下少了sqlit
  • 华为OD机试 - 工作安排 - 动态规划(Java 2023Q1 100分)

    目录 一 题目描述 二 输入描述 三 输出描述 四 动态规划 五 解题思路 六 Java算法源码 七 效果展示 1 输入 2 输出 3 说明 华为OD机试 2023B卷题库疯狂收录中 刷题点这里 一 题目描述 小明每周上班都会拿着自己的工作
  • vue设置全局过滤器

    src目录下新建文件夹filters 新建文件index js内容为 const timefilters getdate data console log data getdatetime data console log data exp
  • 十大Web网站漏洞扫描工具

    1 Nikto 这是一个开源的Web服务器扫描程序 它可以对Web服务器的多种项目 包括3500个潜在的危险文件 CGI 以及超过900个服务器版本 还有250 多个服务器上的版本特定问题 进行全面的测试 其扫描项目和插件经常更新并且可以自
  • 信号槽的返回值(QMetaObject::invokeMethod的用法)——Qt

    前言 之前从未想过信号槽是可以有返回值的 因为虽然信号发出去了 但是它在事件循环中 什么时候执行还不一定 这个想法固然是对的 但是这也不是不能实现的 我查了网上的一些资料 发现差不多有一下三种方法 1 信号槽里加指针或引用 这个不推荐 隐患
  • 并发处理

    1 并发活动 进程的引入 操作系统的特性之一是并发与共享 即在系统中 内存 同时存在几个相互独立的程序 这些程序在系统中既交叉地运行 又要共享系统中的资源 这就会引起一系列的问题 包括 对资源的竞争 运行程序之间的通信 程序之间的合作与协同
  • 分布式锁的实现

    目录 分布式锁 分布式锁的引出 单体锁存在的问题 共享数据不安全 超卖现象 锁的理解 拓展 分流 拓展 分流Nginx简单理解 分布式锁的引出 分布式锁的设计思路 分布式锁的常见应用场景 分布式锁方案 数据库的分布式锁如何实现 Redis分
  • 《手把手教你》系列练习篇之4-python+ selenium自动化测试(详细教程)

    1 简介 今天我们继续前边的练习 学习和练习一下 如何使用webdriver方法获取当前测试页面的URL 如何获取当前页面的title 如何打开浏览器的一个新建页面 如何操作单选按钮等等 这些小练习 来巩固基础 2 webdriver方法获
  • WebGL 实践篇(二)—— 屏幕坐标与裁剪坐标,片段着色器中的颜色定义

    一 裁剪坐标系 canvas坐标系以及屏幕坐标系 裁剪坐标 WebGL坐标系 的范围 1 1 原点在中间 x正值向右 y正值向上 屏幕坐标 原点在左上角 x正值向右 y正值向下 canvas坐标 与屏幕坐标相比 原点向右向下偏移 x y正值
  • 如何安装Jenkins并配置插件(清华源)

    Linux启动jenkins 将 jenkins war 放在 usr local jenkins 目录下 执行命令启动 Jenkins 1 nohup java jar jenkins war httpPort 8000 安装Jenkin
  • Gateway服务的搭建

    1 Gateway的作用 网关的核心功能特性 请求路由 权限控制 限流 架构图 权限控制 网关作为微服务入口 需要校验用户是是否有请求资格 如果没有则进行拦截 路由和负载均衡 一切请求都必须先经过gateway 但网关不处理业务 而是根据某
  • 微信公众号第三方平台开发,零基础入门。想学我教你啊

    在学习微信第三方平台开发之前你应该会的 1 常用接口测试工具的使用 postman 2 学会看第三方平台文档 这个很关键 不过很多还是喜欢百度 白嫖别人的操作文档 3 学习排查问题 分析问题 4 第三方平台的限制 和公众号 订阅号这些关系要
  • 前端node.js上传文件到服务器_从零开始: nodejs 搭建文件索引服务器(Part 1)

    在考虑要用nodejs搭建文件索引服务器来替代我原来那个笨重的Apache服务器 采用省心的bitnami lampstack搭建 然后配了背景图片和图标 之前 说实话我连javascript是什么都不清楚 所以 从语言上讲 确实是从零开始