diff --git a/Luban/FileServer/.gitignore b/Luban/FileServer/.gitignore
new file mode 100644
index 00000000..a52d3c3c
--- /dev/null
+++ b/Luban/FileServer/.gitignore
@@ -0,0 +1,8 @@
+node_modules
+*.swap
+.idea
+.DS_Store
+*.log
+.vscode
+*-lock.json
+AssetsRoot
\ No newline at end of file
diff --git a/Luban/FileServer/FileSys.js b/Luban/FileServer/FileSys.js
deleted file mode 100644
index c48b7354..00000000
--- a/Luban/FileServer/FileSys.js
+++ /dev/null
@@ -1,98 +0,0 @@
-//-----------------------------------------------------------------------
-// Copyright (c) TEngine. All rights reserved.
-// Author: TangXiao
-// Date: 2022/5/14 16:29:13
-//-----------------------------------------------------------------------
-const http = require("http");
-const url = require("url");
-const fs = require("fs");
-const path = require("path");
-const mime = {
- css: "text/css",
- gif: "image/gif",
- html: "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",
-};
-const port = 8088;
-
-const httpServer = http.createServer((request, response) => {
- const requestUrl = request.url;
- let pathName = url.parse(requestUrl).pathname;
-
- // 对路径解码,防止中文乱码
- pathName = decodeURI(pathName);
-
- // 绝对路径
- const filePath = path.resolve(__dirname + pathName);
-
- // 扩展名
- let ext = path.extname(pathName);
- ext = ext ? ext.slice(1) : "unknown";
-
- // 未知的类型一律用"text/plain"类型
- const contentType = mime[ext] || "text/plain";
-
- // fs.stat()方法用于判断给定的路径是否存在
- fs.stat(filePath, (err, stats) => {
- // 路径不存在,则返回404
- if (err) {
- response.writeHead(404, { "content-type": "text/html" });
- response.end("
404 Not Found
");
- }
- // 如果是文件
- if (!err && stats.isFile()) {
- response.writeHead(200, { "content-type": contentType });
- // 建立流对象,读文件
- const stream = fs.createReadStream(filePath);
- // 错误处理
- stream.on("error", function() {
- response.writeHead(500, { "content-type": contentType });
-
- response.end("500 Server Error
");
- });
- // 读取文件
- stream.pipe(response);
- //response.end(); // 这个地方有坑,加了会关闭对话,看不到内容了
- }
- // 如果是路径
- if (!err && stats.isDirectory()) {
- let html = " ";
- // 读取该路径下文件
- fs.readdir(filePath, (err, files) => {
- if (err) {
- response.writeHead(500, { "content-type": contentType });
- response.end("路径读取失败!
");
- } else {
- for (const file of files) {
- if (file === "index.html") {
- response.writeHead(200, { "content-type": "text/html" });
- response.end(file);
- break;
- }
- html += ``;
- }
- response.writeHead(200, { "content-type": "text/html" });
- response.end(html);
- }
- });
- }
- });
-});
-
-httpServer.listen(port, function() {
- console.log(`File Service: ${port}`);
-});
\ No newline at end of file
diff --git a/Luban/FileServer/README.md b/Luban/FileServer/README.md
new file mode 100644
index 00000000..4f955671
--- /dev/null
+++ b/Luban/FileServer/README.md
@@ -0,0 +1,42 @@
+## 使用node搭建静态资源服务器
+
+### 安装
+
+```bash
+npm install yumu-static-server -g
+```
+
+### 使用
+
+```bash
+server # 会在当前目录下启动一个静态资源服务器,默认端口为8080
+
+server -p[port] 3000 # 会在当前目录下启动一个静态资源服务器,端口为3000
+
+server -i[index] index.html # 设置文件夹在默认加载的文件
+
+server -c[charset] UTF-8 # 设置文件默认加载的字符编码
+
+server -cors # 开启文件跨域
+
+server -h[https] # 开启https服务
+
+server --openindex # 是否打开默认页面
+
+server --no-openbrowser # 关闭自动打开浏览器
+```
+
+### 基本功能
+
+1. 启动静态资源服务器
+2. 端口可配置
+3. 字符编码可配置
+4. 文件夹下默认加载文件可配置
+5. 是否跨域可配置
+6. 开启https服务
+
+### TODO
+
+- [x] 引入handlerbars编译模板
+- [x] 支持文件是否跨域
+- [x] 支持https服务
diff --git a/Luban/FileServer/bin/app.js b/Luban/FileServer/bin/app.js
new file mode 100644
index 00000000..bc2a45d5
--- /dev/null
+++ b/Luban/FileServer/bin/app.js
@@ -0,0 +1,26 @@
+const StaticServer = require('../src/static-server');
+
+const options = require('yargs')
+ .option('p', { alias: 'port', describe: '设置服务启动的端口号', type: 'number' })
+ .option('i', { alias: 'index', describe: '设置默认打开的主页', type: 'string' })
+ .option('c', { alias: 'charset', describe: '设置文件的默认字符集', type: 'string' })
+ .option('o', { alias: 'openindex', describe: '是否打开默认页面', type: 'boolean' })
+ .option('h', { alias: 'https', describe: '是否启用https服务', type: 'boolean' })
+ .option('cors', { describe: '是否开启文件跨域', type: 'boolean' })
+ .option('openbrowser', { describe: '是否默认打开浏览器', type: 'boolean' })
+
+ // 默认参数
+ .default('openbrowser', true)
+ // .default('https', true)
+ .default('port', 8080)
+ .default('index', 'index.html')
+ .default('openindex', 'index.html')
+ .default('charset', 'UTF-8')
+
+ .help()
+ .alias('?', 'help')
+
+ .argv;
+
+const server = new StaticServer(options);
+server.start();
\ No newline at end of file
diff --git a/Luban/FileServer/index.js b/Luban/FileServer/index.js
new file mode 100644
index 00000000..f113fe8b
--- /dev/null
+++ b/Luban/FileServer/index.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+module.exports = require('./bin/app.js');
diff --git a/Luban/FileServer/instal.bat b/Luban/FileServer/instal.bat
new file mode 100644
index 00000000..ad36db1c
--- /dev/null
+++ b/Luban/FileServer/instal.bat
@@ -0,0 +1 @@
+npm install yumu-static-server -g
\ No newline at end of file
diff --git a/Luban/FileServer/package.json b/Luban/FileServer/package.json
new file mode 100644
index 00000000..73cdb9f0
--- /dev/null
+++ b/Luban/FileServer/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "static-server",
+ "version": "0.0.1",
+ "description": "使用node搭建静态资源服务器",
+ "main": "index.js",
+ "scripts": {
+ "dev": "supervisor bin/app.js",
+ "start": "npm run dev"
+ },
+ "bin": {
+ "server": "index.js"
+ },
+ "author": "Alex",
+ "license": "ISC",
+ "keywords": [
+ "static-server",
+ "server"
+ ],
+ "dependencies": {
+ "chalk": "^2.3.2",
+ "handlebars": "^4.0.11",
+ "mime": "^2.2.0",
+ "open": "^7.1.0",
+ "pem": "^1.12.5",
+ "yargs": "^6.6.0"
+ },
+ "devDependencies": {
+ "supervisor": "^0.12.0"
+ }
+}
\ No newline at end of file
diff --git a/Luban/FileServer/src/mime.js b/Luban/FileServer/src/mime.js
new file mode 100644
index 00000000..967897d5
--- /dev/null
+++ b/Luban/FileServer/src/mime.js
@@ -0,0 +1,12 @@
+const path = require('path');
+const mime = require('mime');
+
+const lookup = (pathName) => {
+ let ext = path.extname(pathName);
+ ext = ext.split('.').pop();
+ return mime.getType(ext) || mime.getType('txt');
+}
+
+module.exports = {
+ lookup
+};
diff --git a/Luban/FileServer/src/static-server.js b/Luban/FileServer/src/static-server.js
new file mode 100644
index 00000000..ca2c5113
--- /dev/null
+++ b/Luban/FileServer/src/static-server.js
@@ -0,0 +1,334 @@
+const http = require('http');
+const https = require('https');
+const path = require('path');
+const fs = require('fs');
+const url = require('url');
+const zlib = require('zlib');
+const chalk = require('chalk');
+const os = require('os');
+const open = require("open");
+const Handlebars = require('handlebars');
+const pem = require('pem');
+const mime = require('./mime');
+const Template = require('./templates');
+
+const _defaultTemplate = Handlebars.compile(Template.page_dafault);
+const _404TempLate = Handlebars.compile(Template.page_404);
+
+const hasTrailingSlash = url => url[url.length - 1] === '/';
+
+const ifaces = os.networkInterfaces();
+
+class StaticServer {
+ constructor(options) {
+ this.port = options.port;
+ this.indexPage = options.index;
+ this.openIndexPage = options.openindex;
+ this.openBrowser = options.openbrowser;
+ this.charset = options.charset;
+ this.cors = options.cors;
+ this.protocal = options.https ? 'https' : 'http';
+ this.zipMatch = '^\\.(css|js|html)$';
+ }
+
+ /**
+ * 响应错误
+ *
+ * @param {*} err
+ * @param {*} res
+ * @returns
+ * @memberof StaticServer
+ */
+ respondError(err, res) {
+ res.writeHead(500);
+ return res.end(err);
+ }
+
+ /**
+ * 响应404
+ *
+ * @param {*} req
+ * @param {*} res
+ * @memberof StaticServer
+ */
+ respondNotFound(req, res) {
+ res.writeHead(404, {
+ 'Content-Type': 'text/html'
+ });
+ const html = _404TempLate();
+ res.end(html);
+ }
+
+ respond(pathName, req, res) {
+ fs.stat(pathName, (err, stat) => {
+ if (err) return respondError(err, res);
+ this.responseFile(stat, pathName, req, res);
+ });
+ }
+
+ /**
+ * 判断是否需要解压
+ *
+ * @param {*} pathName
+ * @returns
+ * @memberof StaticServer
+ */
+ shouldCompress(pathName) {
+ return path.extname(pathName).match(this.zipMatch);
+ }
+
+ /**
+ * 解压文件
+ *
+ * @param {*} readStream
+ * @param {*} req
+ * @param {*} res
+ * @returns
+ * @memberof StaticServer
+ */
+ compressHandler(readStream, req, res) {
+ const acceptEncoding = req.headers['accept-encoding'];
+ if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
+ return readStream;
+ } else if (acceptEncoding.match(/\bgzip\b/)) {
+ res.setHeader('Content-Encoding', 'gzip');
+ return readStream.pipe(zlib.createGzip());
+ }
+ }
+
+ /**
+ * 响应文件路径
+ *
+ * @param {*} stat
+ * @param {*} pathName
+ * @param {*} req
+ * @param {*} res
+ * @memberof StaticServer
+ */
+ responseFile(stat, pathName, req, res) {
+ // 设置响应头
+ res.setHeader('Content-Type', `${mime.lookup(pathName)}; charset=${this.charset}`);
+ res.setHeader('Accept-Ranges', 'bytes');
+
+ // 添加跨域
+ if (this.cors) res.setHeader('Access-Control-Allow-Origin', '*');
+
+ let readStream;
+ readStream = fs.createReadStream(pathName);
+ if (this.shouldCompress(pathName)) { // 判断是否需要解压
+ readStream = this.compressHandler(readStream, req, res);
+ }
+ readStream.pipe(res);
+ }
+
+ /**
+ * 响应重定向
+ *
+ * @param {*} req
+ * @param {*} res
+ * @memberof StaticServer
+ */
+ respondRedirect(req, res) {
+ const location = req.url + '/';
+ res.writeHead(301, {
+ 'Location': location,
+ 'Content-Type': 'text/html'
+ });
+ const html = _defaultTemplate({
+ htmlStr: `Redirecting to ${location}`,
+ showFileList: false
+ })
+ res.end(html);
+ }
+
+ /**
+ * 响应文件夹路径
+ *
+ * @param {*} pathName
+ * @param {*} req
+ * @param {*} res
+ * @memberof StaticServer
+ */
+ respondDirectory(pathName, req, res) {
+ const indexPagePath = path.join(pathName, this.indexPage);
+ // 如果文件夹下存在index.html,则默认打开
+ if (this.openIndexPage && fs.existsSync(indexPagePath)) {
+ this.respond(indexPagePath, req, res);
+ } else {
+ fs.readdir(pathName, (err, files) => {
+ if (err) {
+ respondError(err, res);
+ }
+ const requestPath = url.parse(req.url).pathname;
+ const fileList = [];
+ files.forEach(fileName => {
+ let itemLink = path.join(requestPath, fileName);
+ let isDirectory = false;
+ const stat = fs.statSync(path.join(pathName, fileName));
+ if (stat && stat.isDirectory()) {
+ itemLink = path.join(itemLink, '/');
+ isDirectory = true;
+ }
+ fileList.push({
+ link: itemLink,
+ name: fileName,
+ isDirectory
+ });
+ });
+ // 排序,目录在前,文件在后
+ fileList.sort((prev, next) => {
+ if (prev.isDirectory && !next.isDirectory) {
+ return -1;
+ }
+ return 1;
+ });
+ res.writeHead(200, {
+ 'Content-Type': 'text/html'
+ });
+ const html = _defaultTemplate({
+ requestPath,
+ fileList,
+ showFileList: true
+ })
+ res.end(html);
+ });
+ }
+ }
+
+ /**
+ * 路由处理
+ *
+ * @param {*} pathName
+ * @param {*} req
+ * @param {*} res
+ * @memberof StaticServer
+ */
+ routeHandler(pathName, req, res) {
+ const realPathName = pathName.split('?')[0];
+ fs.stat(realPathName, (err, stat) => {
+ this.logGetInfo(err, pathName);
+ if (!err) {
+ const requestedPath = url.parse(req.url).pathname;
+ // 检查url
+ // 如果末尾有'/',且是文件夹,则读取文件夹
+ // 如果是文件夹,但末尾没'/',则重定向至'xxx/'
+ // 如果是文件,则判断是否是压缩文件,是则解压,不是则读取文件
+ if (hasTrailingSlash(requestedPath) && stat.isDirectory()) {
+ this.respondDirectory(realPathName, req, res);
+ } else if (stat.isDirectory()) {
+ this.respondRedirect(req, res);
+ } else {
+ this.respond(realPathName, req, res);
+ }
+ } else {
+ this.respondNotFound(req, res);
+ }
+ });
+ }
+
+ /**
+ * 打印ip地址
+ *
+ * @memberof StaticServer
+ */
+ logUsingPort() {
+ const me = this;
+ console.log(`${chalk.yellow(`Starting up your server\nAvailable on:`)}`);
+ Object.keys(ifaces).forEach(function (dev) {
+ ifaces[dev].forEach(function (details) {
+ if (details.family === 'IPv4') {
+ console.log(` ${me.protocal}://${details.address}:${chalk.green(me.port)}`);
+ }
+ });
+ });
+ console.log(`${chalk.cyan(Array(50).fill('-').join(''))}`);
+ }
+
+ /**
+ * 打印占用端口
+ *
+ * @param {*} oldPort
+ * @param {*} port
+ * @memberof StaticServer
+ */
+ logUsedPort(oldPort, port) {
+ const me = this;
+ console.log(`${chalk.red(`The port ${oldPort} is being used, change to port `)}${chalk.green(me.port)} `);
+ }
+
+ /**
+ * 打印https证书友好提示
+ *
+ * @memberof StaticServer
+ */
+ logHttpsTrusted() {
+ console.log(chalk.green('Currently is using HTTPS certificate (Manually trust it if necessary)'));
+ }
+
+
+ /**
+ * 打印路由路径输出
+ *
+ * @param {*} isError
+ * @param {*} pathName
+ * @memberof StaticServer
+ */
+ logGetInfo(isError, pathName) {
+ if (isError) {
+ console.log(chalk.red(`404 ${pathName}`));
+ } else {
+ console.log(chalk.cyan(`200 ${pathName}`));
+ }
+ }
+
+ startServer(keys) {
+ const me = this;
+ let isPostBeUsed = false;
+ const oldPort = me.port;
+ const protocal = me.protocal === 'https' ? https : http;
+ const options = me.protocal === 'https' ? { key: keys.serviceKey, cert: keys.certificate } : null;
+ const callback = (req, res) => {
+ const pathName = path.join(process.cwd(), path.normalize(decodeURI(req.url)));
+ me.routeHandler(pathName, req, res);
+ };
+ const params = [callback];
+ if (me.protocal === 'https') params.unshift(options);
+ const server = protocal.createServer(...params).listen(me.port);
+ server.on('listening', function () { // 执行这块代码说明端口未被占用
+ if (isPostBeUsed) {
+ me.logUsedPort(oldPort, me.port);
+ }
+ me.logUsingPort();
+ if (me.openBrowser) {
+ open(`${me.protocal}://127.0.0.1:${me.port}`);
+ }
+ });
+
+ server.on('error', function (err) {
+ if (err.code === 'EADDRINUSE') { // 端口已经被使用
+ isPostBeUsed = true;
+ me.port = parseInt(me.port) + 1;
+ server.listen(me.port);
+ } else {
+ console.log(err);
+ }
+ })
+ }
+
+ start() {
+ const me = this;
+ if (this.protocal === 'https') {
+ pem.createCertificate({ days: 1, selfSigned: true }, function (err, keys) {
+ if (err) {
+ throw err
+ }
+ me.logHttpsTrusted();
+ me.startServer(keys);
+ })
+ } else {
+ me.startServer();
+ }
+ }
+}
+
+module.exports = StaticServer;
\ No newline at end of file
diff --git a/Luban/FileServer/src/templates/404.js b/Luban/FileServer/src/templates/404.js
new file mode 100644
index 00000000..62d60971
--- /dev/null
+++ b/Luban/FileServer/src/templates/404.js
@@ -0,0 +1,44 @@
+module.exports = `
+
+
+
+
+
+
+ node静态服务器
+
+
+
+
+

+
+
抱歉,你访问的路径不存在
+
您要找的页面没有找到,请返回首页继续浏览
+
+
+
+
+`
\ No newline at end of file
diff --git a/Luban/FileServer/src/templates/default.js b/Luban/FileServer/src/templates/default.js
new file mode 100644
index 00000000..4f389f6b
--- /dev/null
+++ b/Luban/FileServer/src/templates/default.js
@@ -0,0 +1,93 @@
+module.exports = `
+
+
+
+
+
+
+ node静态服务器
+
+
+
+
+
当前目录:{{requestPath}}
+ {{#if showFileList}}
+
+ {{else}}
+ {{htmlStr}}
+ {{/if}}
+
+
+
+
+`;
\ No newline at end of file
diff --git a/Luban/FileServer/src/templates/images/404.png b/Luban/FileServer/src/templates/images/404.png
new file mode 100644
index 00000000..93cd1bb8
Binary files /dev/null and b/Luban/FileServer/src/templates/images/404.png differ
diff --git a/Luban/FileServer/src/templates/index.js b/Luban/FileServer/src/templates/index.js
new file mode 100644
index 00000000..4b51b700
--- /dev/null
+++ b/Luban/FileServer/src/templates/index.js
@@ -0,0 +1,7 @@
+const page_dafault = require('./default');
+const page_404 = require('./404');
+
+module.exports = {
+ page_dafault,
+ page_404
+};
\ No newline at end of file
diff --git a/Luban/FileServer/start.bat b/Luban/FileServer/start.bat
index babde2c4..13cd1fa7 100644
--- a/Luban/FileServer/start.bat
+++ b/Luban/FileServer/start.bat
@@ -1 +1 @@
-node FileSys.js
\ No newline at end of file
+server
\ No newline at end of file
diff --git a/Luban/FileServer/start.sh b/Luban/FileServer/start.sh
deleted file mode 100644
index babde2c4..00000000
--- a/Luban/FileServer/start.sh
+++ /dev/null
@@ -1 +0,0 @@
-node FileSys.js
\ No newline at end of file