refactor: 重构项目目录结构并优化前后端代码

将项目结构调整为前后端分离的清晰目录布局,优化了代码组织和可维护性。主要变更包括:
- 将前端代码移至frontend目录
- 将后端代码移至backend目录
- 更新README.md以反映新的目录结构
- 优化.gitignore文件
- 修复部分文件路径问题
This commit is contained in:
2026-04-29 18:25:06 +08:00
parent d43d732c03
commit 1db57cd18c
25 changed files with 31 additions and 20 deletions

View File

@@ -0,0 +1,24 @@
# License
[The MIT License (MIT)](http://opensource.org/licenses/MIT)
Copyright (c) 2009-2013 Jeff Mott
Copyright (c) 2013-2016 Evan Vosberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,20 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,37 @@
var signer = require('./signer');
var https = require("https");
var sig = new signer.Signer();
//Set the AK/SK to sign and authenticate the request.
// 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
// 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
sig.Key = process.env.HUAWEICLOUD_SDK_AK;
sig.Secret = process.env.HUAWEICLOUD_SDK_SK;
//The following example shows how to set the request URL and parameters to query a VPC list.
//Specify a request method, such as GET, PUT, POST, DELETE, HEAD, and PATCH.
//Set request host.
//Set request URI.
//Set parameters for the request URL.
var r = new signer.HttpRequest("GET", "endpoint.example.com/v1/77b6a44cba5143ab91d13ab9a8ff44fd/vpcs?limie=1");
//Add header parameters, for example, x-domain-id for invoking a global service and x-project-id for invoking a project-level service.
r.headers = {"Content-Type": "application/json"};
//Add a body if you have specified the PUT or POST method. Special characters, such as the double quotation mark ("), contained in the body must be escaped.
r.body = '';
var opt = sig.Sign(r);
console.log(opt.headers["X-Sdk-Date"]);
console.log(opt.headers["Authorization"]);
var req = https.request(opt, function (res) {
console.log(res.statusCode);
console.log('headers:', JSON.stringify(res.headers));
res.on("data", function (chunk) {
console.log(chunk.toString())
})
});
req.on("error", function (err) {
console.log(err.message)
});
req.write(r.body);
req.end();

View File

@@ -0,0 +1,374 @@
// HWS API Gateway Signature
(function (root, factory) {
"use strict";
/*global define*/
if (typeof define === 'function' && define.amd) {
// AMD
define(['CryptoJS'], function (CryptoJS) {
var crypto_wrapper = {
hmacsha256: function (keyByte, message) {
return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex)
},
HexEncodeSHA256Hash: function (body) {
return CryptoJS.SHA256(body)
}
};
return factory(crypto_wrapper)
});
} else if (typeof wx === 'object') {
// wechat
var CryptoJS = require('./js/hmac-sha256.js');
var crypto_wrapper = {
hmacsha256: function (keyByte, message) {
return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex)
},
HexEncodeSHA256Hash: function (body) {
return CryptoJS.SHA256(body)
}
};
module.exports = factory(crypto_wrapper);
} else if (typeof module === 'object' && module.exports) {
// Node
var crypto = require('crypto');
var crypto_wrapper = {
hmacsha256: function (keyByte, message) {
return crypto.createHmac('SHA256', keyByte).update(message).digest().toString('hex')
},
HexEncodeSHA256Hash: function (body) {
return crypto.createHash('SHA256').update(body).digest().toString('hex')
}
};
module.exports = factory(crypto_wrapper);
} else {
// Browser
var CryptoJS = root.CryptoJS
var crypto_wrapper = {
hmacsha256: function (keyByte, message) {
return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex)
},
HexEncodeSHA256Hash: function (body) {
return CryptoJS.SHA256(body)
}
};
root.signer = factory(crypto_wrapper);
}
}(this, function (crypto_wrapper) {
'use strict';
var Algorithm = "SDK-HMAC-SHA256";
var HeaderXDate = "X-Sdk-Date";
var HeaderAuthorization = "Authorization";
var HeaderContentSha256 = "x-sdk-content-sha256";
const hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
const noEscape = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0 // 112 - 127
];
// function urlEncode is based on https://github.com/nodejs/node/blob/master/lib/querystring.js
// Copyright Joyent, Inc. and other Node contributors.
function urlEncode(str) {
if (typeof str !== 'string') {
if (typeof str === 'object')
str = String(str);
else
str += '';
}
var out = '';
var lastPos = 0;
for (var i = 0; i < str.length; ++i) {
var c = str.charCodeAt(i);
// ASCII
if (c < 0x80) {
if (noEscape[c] === 1)
continue;
if (lastPos < i)
out += str.slice(lastPos, i);
lastPos = i + 1;
out += hexTable[c];
continue;
}
if (lastPos < i)
out += str.slice(lastPos, i);
// Multi-byte characters ...
if (c < 0x800) {
lastPos = i + 1;
out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
continue;
}
if (c < 0xD800 || c >= 0xE000) {
lastPos = i + 1;
out += hexTable[0xE0 | (c >> 12)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
continue;
}
// Surrogate pair
++i;
if (i >= str.length)
throw new errors.URIError('ERR_INVALID_URI');
var c2 = str.charCodeAt(i) & 0x3FF;
lastPos = i + 1;
c = 0x10000 + (((c & 0x3FF) << 10) | c2);
out += hexTable[0xF0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3F)] +
hexTable[0x80 | ((c >> 6) & 0x3F)] +
hexTable[0x80 | (c & 0x3F)];
}
if (lastPos === 0)
return str;
if (lastPos < str.length)
return out + str.slice(lastPos);
return out;
}
function HttpRequest(method, url, headers, body) {
if (method === undefined) {
this.method = "";
} else {
this.method = method;
}
if (url === undefined) {
this.host = "";
this.uri = "";
this.query = {};
} else {
this.query = {};
var host, path;
var i = url.indexOf("://");
if (i !== -1) {
url = url.substr(i + 3)
}
var i = url.indexOf("?");
if (i !== -1) {
var query_str = url.substr(i + 1);
url = url.substr(0, i);
var spl = query_str.split("&");
for (var i in spl) {
var kv = spl[i];
var index = kv.indexOf('=');
var key, value;
if (index >= 0) {
key = kv.substr(0, index);
value = kv.substr(index + 1);
} else {
key = kv;
value = "";
}
if (key !== "") {
key = decodeURI(key);
value = decodeURI(value);
if (this.query[key] === undefined) {
this.query[key] = [value]
} else {
this.query[key].push(value)
}
}
}
}
var i = url.indexOf("/");
if (i === -1) {
host = url;
path = "/";
} else {
host = url.substr(0, i);
path = url.substr(i);
}
this.host = host;
this.uri = decodeURI(path);
}
if (headers === undefined) {
this.headers = {};
} else {
this.headers = headers;
}
if (body === undefined) {
this.body = "";
} else {
this.body = body;
}
}
function findHeader(r, header) {
for (var k in r.headers) {
if (k.toLowerCase() === header.toLowerCase()) {
return r.headers[k]
}
}
return null;
}
// Build a CanonicalRequest from a regular request string
//
// CanonicalRequest =
// HTTPRequestMethod + '\n' +
// CanonicalURI + '\n' +
// CanonicalQueryString + '\n' +
// CanonicalHeaders + '\n' +
// SignedHeaders + '\n' +
// HexEncode(Hash(RequestPayload))
function CanonicalRequest(r, signedHeaders) {
var hexencode = findHeader(r, HeaderContentSha256);
if (hexencode === null) {
var data = RequestPayload(r);
hexencode = crypto_wrapper.HexEncodeSHA256Hash(data);
}
return r.method + "\n" + CanonicalURI(r) + "\n" + CanonicalQueryString(r) + "\n" + CanonicalHeaders(r, signedHeaders) + "\n" + signedHeaders.join(';') + "\n" + hexencode
}
function CanonicalURI(r) {
var pattens = r.uri.split('/');
var uri = [];
for (var k in pattens) {
var v = pattens[k];
uri.push(urlEncode(v))
}
var urlpath = uri.join('/');
if (urlpath[urlpath.length - 1] !== '/') {
urlpath = urlpath + '/'
}
//r.uri = urlpath
return urlpath;
}
function CanonicalQueryString(r) {
var keys = [];
for (var key in r.query) {
keys.push(key)
}
keys.sort();
var a = [];
for (var i in keys) {
var key = urlEncode(keys[i]);
var value = r.query[keys[i]];
if (Array.isArray(value)) {
value.sort();
for (var iv in value) {
a.push(key + '=' + urlEncode(value[iv]))
}
} else {
a.push(key + '=' + urlEncode(value))
}
}
return a.join('&');
}
function CanonicalHeaders(r, signedHeaders) {
var headers = {};
for (var key in r.headers) {
headers[key.toLowerCase()] = r.headers[key];
}
var a = [];
for (var i in signedHeaders) {
var value = headers[signedHeaders[i]];
a.push(signedHeaders[i] + ':' + value.trim())
}
return a.join('\n') + "\n"
}
function SignedHeaders(r) {
var a = [];
for (var key in r.headers) {
a.push(key.toLowerCase())
}
a.sort();
return a
}
function RequestPayload(r) {
return r.body
}
// Create a "String to Sign".
function StringToSign(canonicalRequest, t) {
var bytes = crypto_wrapper.HexEncodeSHA256Hash(canonicalRequest);
return Algorithm + "\n" + t + "\n" + bytes
}
// Create the HWS Signature.
function SignStringToSign(stringToSign, signingKey) {
return crypto_wrapper.hmacsha256(signingKey, stringToSign)
}
// Get the finalized value for the "Authorization" header. The signature
// parameter is the output from SignStringToSign
function AuthHeaderValue(signature, Key, signedHeaders) {
return Algorithm + " Access=" + Key + ", SignedHeaders=" + signedHeaders.join(';') + ", Signature=" + signature
}
function twoChar(s) {
if (s >= 10) {
return "" + s
} else {
return "0" + s
}
}
function getTime() {
var date = new Date();
return "" + date.getUTCFullYear() + twoChar(date.getUTCMonth() + 1) + twoChar(date.getUTCDate()) + "T" +
twoChar(date.getUTCHours()) + twoChar(date.getUTCMinutes()) + twoChar(date.getUTCSeconds()) + "Z"
}
function Signer() {
this.Key = "";
this.Secret = "";
}
Signer.prototype.Sign = function (r) {
var headerTime = findHeader(r, HeaderXDate);
if (headerTime === null) {
headerTime = getTime();
r.headers[HeaderXDate] = headerTime
}
if (r.method !== "PUT" && r.method !== "PATCH" && r.method !== "POST") {
r.body = ""
}
var queryString = CanonicalQueryString(r);
if (queryString !== "") {
queryString = "?" + queryString
}
var options = {
hostname: r.host,
path: encodeURI(r.uri) + queryString,
method: r.method,
headers: r.headers
};
if (findHeader(r, 'host') === null) {
r.headers.host = r.host;
}
var signedHeaders = SignedHeaders(r);
var canonicalRequest = CanonicalRequest(r, signedHeaders);
var stringToSign = StringToSign(canonicalRequest, headerTime);
var signature = SignStringToSign(stringToSign, this.Secret);
options.headers[HeaderAuthorization] = AuthHeaderValue(signature, this.Key, signedHeaders);
return options
};
return {
HttpRequest: HttpRequest,
Signer: Signer,
urlEncode: urlEncode,
findHeader: findHeader,
SignedHeaders: SignedHeaders,
CanonicalRequest: CanonicalRequest,
StringToSign: StringToSign,
}
}));

View File

@@ -0,0 +1,10 @@
var assert = require("assert");
var signer = require('./signer')
var s = ""
for (i = 0; i < 0x80; i++) {
s = s + signer.urlEncode(String.fromCharCode(i))
}
console.log(s)
assert.equal(s, "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F")