!3 merge wei into main
到达 - 树状的节点 Created-by: qingss0 Commit-by: qing;shufeng2012 Merged-by: qingss0 Description: dnfs模块 准备dnfs模块 增加dnfs后续 dnfs_3 修改HTTP LIBRARY,增加OPTIONS支持 ... See merge request: qingss0/micro_servers!3
This commit is contained in:
16 files changed
+517
-125
No files matched your search
@@ -30,4 +30,5 @@ inst_module "libhttp"
|
||||
inst_module "libcrypt"
|
||||
inst_module "gateway"
|
||||
inst_module "static"
|
||||
inst_module "dnfs"
|
||||
inst_module "core" # core last
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
./nidus.out -i /etc/Nidus/init.txt --serve 5200
|
||||
./nidus.out -i init.txt --serve 5200
|
||||
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
bash /opt/micro_servers/gateway/_run.sh
|
||||
bash /opt/micro_servers/static/_run.sh
|
||||
bash /opt/micro_servers/dnfs/_run.sh
|
||||
bash /opt/micro_servers/gateway/_run.sh
|
||||
@@ -1,5 +1,10 @@
|
||||
#prjs
|
||||
*.db
|
||||
*.bin
|
||||
*.build
|
||||
*.dist
|
||||
*.onefile-build
|
||||
nuitka-crash-report.xml
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
+2
-1
@@ -2,4 +2,5 @@
|
||||
cd "$(dirname "$0")" || exit
|
||||
bash ./_setup.sh
|
||||
source ./.env/bin/activate
|
||||
exec python dnfs.py
|
||||
#exec ./dnfs.bin
|
||||
exec python3 dnfs.py
|
||||
@@ -4,3 +4,4 @@ python3 -m venv .env
|
||||
#sudo chmod +x .env/bin/activate
|
||||
source .env/bin/activate
|
||||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
nuitka --onefile --jobs=1 dnfs.py
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>树状笔记</title>
|
||||
|
||||
<!-- Font Awesome 6.0.0 是矢量图标库,提供 7,000+ 免费图标及 17,000+ 专业图标。 -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
|
||||
<!-- 外部CSS文件 -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><i class="fas fa-sitemap"></i> 树状笔记</h1>
|
||||
<div class="header-actions">
|
||||
<button onclick="resetForm()" class="btn btn-primary"><i class="fas fa-plus"></i> 新增节点</button>
|
||||
<button onclick="goBack()" class="btn btn-secondary"><i class="fas fa-arrow-left"></i> 返回上级</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 节点迁移模式提示栏 -->
|
||||
<div id="move-mode-bar" class="move-mode-bar" style="display: none;">
|
||||
<div class="move-mode-info">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
正在迁移节点:<strong id="moving-node-title"></strong>
|
||||
</div>
|
||||
<div class="move-mode-actions">
|
||||
<button onclick="confirmMoveHere()" class="btn btn-move-here"><i class="fas fa-arrow-down"></i> 移动到此</button>
|
||||
<button onclick="cancelMove()" class="btn btn-cancel-move"><i class="fas fa-times"></i> 取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="form-section">
|
||||
<!-- 表单标题 -->
|
||||
<h3 class="form-title"><i class="fas fa-pen"></i> <span id="form-mode-text">新增节点</span></h3>
|
||||
<input type="hidden" id="edit-id" value=""> <!-- 隐藏输入框,要编辑节点的id -->
|
||||
<!-- 输入节点标题 -->
|
||||
<div class="form-group">
|
||||
<label for="node-title"><i class="fas fa-heading"></i> 标题</label>
|
||||
<input type="text" id="node-title" class="form-control" placeholder="输入节点标题">
|
||||
</div>
|
||||
<!-- 输入节点内容 -->
|
||||
<div class="form-group">
|
||||
<label for="node-content"><i class="fas fa-file-alt"></i> 节点说明</label>
|
||||
<textarea id="node-content" class="form-control" placeholder="输入节点的文字说明"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 文件上传区域 -->
|
||||
<div class="form-group">
|
||||
<label><i class="fas fa-upload"></i> 文件上传</label>
|
||||
<div class="file-upload-area" onclick="document.getElementById('file-upload').click()">
|
||||
<i class="fas fa-cloud-upload-alt" style="font-size: 2em; color: #888; margin-bottom: 10px;"></i>
|
||||
<p>点击或拖拽文件到此处上传</p>
|
||||
<p style="font-size: 0.8em; color: #999;">文件将以编码形式保存,同一节点内数据不要超过1M</p>
|
||||
<input type="file" id="file-upload" style="display: none;" onchange="handleFileUpload(this.files)"> <!-- 隐藏输入框 -->
|
||||
</div>
|
||||
|
||||
<!-- 选择文件状态信息 -->
|
||||
<div class="file-info" id="file-info">未选择文件</div>
|
||||
|
||||
<!-- 文件元信息显示 -->
|
||||
<div id="file-meta-container" style="display: none;">
|
||||
<div id="file-meta-info" class="file-meta-info">
|
||||
<p><i class="fas fa-file"></i> 文件名:<span id="meta-filename"></span></p>
|
||||
<p><i class="fas fa-hdd"></i> 文件大小:<span id="meta-filesize"></span></p>
|
||||
<p><i class="fas fa-calendar-alt"></i> 上传时间:<span id="meta-uploadtime"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据修复提示 -->
|
||||
<div id="data-repair-hint" class="data-repair-hint" style="display: none;">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>检测到文件元数据损坏。请尝试重新上传文件以修复此问题。</span>
|
||||
</div>
|
||||
|
||||
<!-- 文件进度条 -->
|
||||
<div class="file-progress">
|
||||
<div class="file-progress-bar" id="file-progress-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件下载区域(仅在有文件时显示) -->
|
||||
<div class="form-group" id="download-section" style="display: none;">
|
||||
<label><i class="fas fa-download"></i> 文件操作</label>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button onclick="decodeAndDownload(null, null, true)" class="btn btn-success">
|
||||
<i class="fas fa-download"></i> 下载文件
|
||||
</button>
|
||||
<button onclick="removeFile()" class="btn btn-danger">
|
||||
<i class="fas fa-trash"></i> 移除文件
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的Base64存储字段 - 用户永远不会看到 -->
|
||||
<textarea id="hidden-base64" class="form-control hidden-field" placeholder="Base64编码内容(自动生成,隐藏显示)"></textarea>
|
||||
<input type="hidden" id="file-metadata" value="">
|
||||
|
||||
<!-- 动作表单 -->
|
||||
<div class="form-actions">
|
||||
<button onclick="saveNode()" class="btn btn-primary"><i class="fas fa-save"></i> 保存</button>
|
||||
<button onclick="resetForm()" class="btn btn-secondary"><i class="fas fa-undo"></i> 重置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 节点表单 -->
|
||||
<div id="node-list" class="node-list">
|
||||
<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 删除确认模态框 -->
|
||||
<div id="delete-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title"><i class="fas fa-exclamation-triangle"></i> 确认删除</h3>
|
||||
<button class="modal-close" onclick="closeDeleteModal()"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="delete-message">确定删除该节点吗?</p>
|
||||
<p id="delete-node-title-container" style="font-weight: bold; margin: 8px 0;"></p>
|
||||
<p id="delete-file-hint" style="font-size:.85em;color:#e74c3c;display:none;"><i class="fas fa-exclamation-circle"></i> 此节点包含文件,删除后文件将无法恢复</p>
|
||||
<p style="font-size:.85em;color:#777">此操作不可恢复</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="confirmDelete()" class="btn btn-danger"><i class="fas fa-trash"></i> 删除</button>
|
||||
<button onclick="closeDeleteModal()" class="btn btn-secondary"><i class="fas fa-times"></i> 取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
|
||||
<!-- 外部JS文件 -->
|
||||
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
nuitka
|
||||
flask
|
||||
flask-cors
|
||||
flask_sqlalchemy
|
||||
|
||||
+27
-14
@@ -273,6 +273,19 @@ void registerRoutes(HttpServer &server) {
|
||||
server.post("*", [](const auto& req, const auto& params) {
|
||||
return handleProxy(req, params, "post");
|
||||
});
|
||||
|
||||
server.options("*", [](const auto& req, const auto& params) {
|
||||
pmc::net::http::response<pmc::net::http::string_body> res;
|
||||
res.result(pmc::net::http::status::ok);
|
||||
res.set(pmc::net::http::field::access_control_allow_origin, "*");
|
||||
res.set(pmc::net::http::field::access_control_allow_methods, "GET, POST, OPTIONS");
|
||||
res.set(pmc::net::http::field::access_control_allow_headers, "Content-Type, Authorization");
|
||||
res.set(pmc::net::http::field::access_control_max_age, "86400");
|
||||
res.body() = "";
|
||||
res.content_length(0);
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
http::response<http::string_body> wrapperHttp(
|
||||
@@ -444,6 +457,7 @@ http::response<http::string_body> handleProxy(
|
||||
std::string t_url = "";
|
||||
std::string url = "";
|
||||
std::string auth = "";
|
||||
json::value jv_t; /* json value of the token */
|
||||
{
|
||||
auto flag = false;
|
||||
auto it = req.base().find("Target-Url");
|
||||
@@ -456,7 +470,7 @@ http::response<http::string_body> handleProxy(
|
||||
t_url = req["target-url"];
|
||||
}
|
||||
|
||||
if (url == "") {
|
||||
if (t_url == "") {
|
||||
t_url = _DEFT_URL;
|
||||
flag = true;
|
||||
}
|
||||
@@ -466,7 +480,7 @@ http::response<http::string_body> handleProxy(
|
||||
url += req.target();
|
||||
|
||||
it = req.base().find("Authorization");
|
||||
if (it == req.base().end() && !flag) {
|
||||
if (it == req.base().end() && !flag) { /* 不是默认代理地址且无令牌 */
|
||||
throw std::runtime_error("401");
|
||||
}
|
||||
std::string raw = req["Authorization"];
|
||||
@@ -484,21 +498,20 @@ http::response<http::string_body> handleProxy(
|
||||
}
|
||||
std::cout << "auth data: " << auth << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
json::value jv;
|
||||
/* 令牌是否过期 */
|
||||
{
|
||||
/* TODO: 放进try包裹 */
|
||||
jv = json::parse(auth);
|
||||
json::object& obj = jv.as_object();
|
||||
if (!flag) { /* 令牌是否过期 */
|
||||
|
||||
/* TODO: 放进try包裹 */
|
||||
jv_t = json::parse(auth);
|
||||
json::object& obj = jv_t.as_object();
|
||||
|
||||
auto t = json::value_to<std::string>(obj["created_at"]);
|
||||
bool r = LocalTime::isExpired(t);
|
||||
auto t = json::value_to<std::string>(obj["created_at"]);
|
||||
bool r = LocalTime::isExpired(t);
|
||||
|
||||
if (r) {
|
||||
THROW_EX_CUSTOM(HttpException, 2000, "Authorization Expired",
|
||||
401, "*");
|
||||
if (!r) {
|
||||
THROW_EX_CUSTOM(HttpException, 2000, "Authorization Expired",
|
||||
401, "*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ public:
|
||||
void post(const std::string& path, HttpRequestHandler handler);
|
||||
void put(const std::string& path, HttpRequestHandler handler);
|
||||
void del(const std::string& path, HttpRequestHandler handler);
|
||||
void options(const std::string& path, HttpRequestHandler handler); // 新增 OPTIONS
|
||||
void use(MiddlewareHandler middleware);
|
||||
void setStaticDirectory(const std::string& path);
|
||||
|
||||
@@ -102,6 +103,7 @@ private:
|
||||
std::vector<RouteEntry> post_handlers_;
|
||||
std::vector<RouteEntry> put_handlers_;
|
||||
std::vector<RouteEntry> delete_handlers_;
|
||||
std::vector<RouteEntry> options_handlers_; // 新增 OPTIONS 路由
|
||||
std::vector<MiddlewareHandler> middlewares_;
|
||||
std::string static_directory_;
|
||||
|
||||
@@ -113,6 +115,7 @@ private:
|
||||
bool matchPath(const std::string& path, const std::regex& pattern);
|
||||
http::response<http::string_body> handleRequest(const http::request<http::string_body>& req);
|
||||
HttpRequestHandler findRoute(const std::string& method, const std::string& path);
|
||||
bool routeExists(const std::string& method, const std::string& path); // 新增辅助方法
|
||||
|
||||
void addSession(const std::shared_ptr<Session>& session);
|
||||
void removeSession(const Session* session);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h> // SSL_CTX_set_cipher_list
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
@@ -85,7 +85,6 @@ private:
|
||||
<< remote_ip_ << ":" << remote_port_
|
||||
<< " - " << ec.message()
|
||||
<< " (code=" << ec.value() << ")" << std::endl;
|
||||
// 输出 OpenSSL 详细错误堆栈
|
||||
ERR_print_errors_fp(stderr);
|
||||
}
|
||||
}));
|
||||
@@ -168,7 +167,6 @@ private:
|
||||
HttpServer* server_;
|
||||
bool ssl_enabled_;
|
||||
|
||||
// 使用 any_io_executor 兼容多种执行器
|
||||
asio::strand<asio::any_io_executor> strand_;
|
||||
|
||||
std::string remote_ip_ = "unknown";
|
||||
@@ -272,7 +270,6 @@ void HttpServer::initSSLContext() {
|
||||
asio::ssl::context::no_sslv3 |
|
||||
asio::ssl::context::single_dh_use);
|
||||
|
||||
// 设置现代加密套件,确保兼容主流客户端
|
||||
SSL_CTX_set_cipher_list(ssl_ctx_->native_handle(),
|
||||
"ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS");
|
||||
|
||||
@@ -391,6 +388,17 @@ void HttpServer::del(const std::string& path, HttpRequestHandler handler) {
|
||||
delete_handlers_.push_back(entry);
|
||||
}
|
||||
|
||||
// ---- 新增 OPTIONS 路由注册 ----
|
||||
void HttpServer::options(const std::string& path, HttpRequestHandler handler) {
|
||||
RouteEntry entry;
|
||||
entry.path = path;
|
||||
entry.handler = handler;
|
||||
entry.is_wildcard = (path.find('*') != std::string::npos ||
|
||||
path.find('?') != std::string::npos);
|
||||
if (entry.is_wildcard) entry.pattern = wildcardToRegex(path);
|
||||
options_handlers_.push_back(entry);
|
||||
}
|
||||
|
||||
void HttpServer::use(MiddlewareHandler m) {
|
||||
middlewares_.push_back(m);
|
||||
}
|
||||
@@ -442,6 +450,7 @@ HttpRequestHandler HttpServer::findRoute(const std::string& method, const std::s
|
||||
else if (method == "POST") handlers = &post_handlers_;
|
||||
else if (method == "PUT") handlers = &put_handlers_;
|
||||
else if (method == "DELETE") handlers = &delete_handlers_;
|
||||
else if (method == "OPTIONS") handlers = &options_handlers_;
|
||||
else return nullptr;
|
||||
|
||||
// 精确匹配优先
|
||||
@@ -455,6 +464,11 @@ HttpRequestHandler HttpServer::findRoute(const std::string& method, const std::s
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---- 新增辅助方法:检查路由是否存在 ----
|
||||
bool HttpServer::routeExists(const std::string& method, const std::string& path) {
|
||||
return findRoute(method, path) != nullptr;
|
||||
}
|
||||
|
||||
// ---- 请求处理核心 ----
|
||||
http::response<http::string_body> HttpServer::handleRequest(const http::request<http::string_body>& req) {
|
||||
std::string target_path(req.target().data(), req.target().size());
|
||||
@@ -486,6 +500,68 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
}
|
||||
|
||||
std::string method = http::to_string(req.method());
|
||||
|
||||
// ========== 处理 OPTIONS 预检请求 ==========
|
||||
if (method == "OPTIONS") {
|
||||
// 检查是否有自定义 OPTIONS 路由
|
||||
auto handler = findRoute("OPTIONS", target_path);
|
||||
if (handler) {
|
||||
try {
|
||||
return handler(req, params);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[Handler] Exception: " << e.what() << std::endl;
|
||||
res.result(http::status::internal_server_error);
|
||||
res.body() = "500 Internal Server Error\n";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动生成 OPTIONS 响应
|
||||
auto origin = req.find(http::field::origin);
|
||||
if (origin != req.end()) {
|
||||
// 构建允许的方法列表
|
||||
std::vector<std::string> all_methods = {"GET", "POST", "PUT", "DELETE", "OPTIONS"};
|
||||
std::vector<std::string> allowed_methods;
|
||||
|
||||
// 检查每个方法是否有对应的路由
|
||||
for (const auto& m : all_methods) {
|
||||
if (m == "OPTIONS" || routeExists(m, target_path)) {
|
||||
allowed_methods.push_back(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed_methods.empty()) {
|
||||
std::string allow_methods;
|
||||
for (size_t i = 0; i < allowed_methods.size(); ++i) {
|
||||
if (i > 0) allow_methods += ", ";
|
||||
allow_methods += allowed_methods[i];
|
||||
}
|
||||
|
||||
res.result(http::status::ok);
|
||||
res.set(http::field::allow, allow_methods);
|
||||
res.set(http::field::access_control_allow_origin,
|
||||
std::string(origin->value()));
|
||||
res.set(http::field::access_control_allow_methods, allow_methods);
|
||||
res.set(http::field::access_control_allow_headers,
|
||||
"Content-Type, Authorization, X-Requested-With, Accept, Origin");
|
||||
res.set(http::field::access_control_max_age, "86400");
|
||||
res.body() = "";
|
||||
res.content_length(0);
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 没有 Origin 头或没有匹配的方法
|
||||
res.result(http::status::not_found);
|
||||
res.body() = "404 Not Found\n";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
// ========== OPTIONS 处理结束 ==========
|
||||
|
||||
// 查找普通路由
|
||||
auto handler = findRoute(method, target_path);
|
||||
|
||||
// 静态文件
|
||||
@@ -562,6 +638,9 @@ void HttpServer::showStatus() const {
|
||||
std::cout << " DELETE: " << delete_handlers_.size() << " handler(s)" << std::endl;
|
||||
for (const auto& entry : delete_handlers_)
|
||||
std::cout << " DELETE " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
|
||||
std::cout << " OPTIONS: " << options_handlers_.size() << " handler(s)" << std::endl; // 新增
|
||||
for (const auto& entry : options_handlers_)
|
||||
std::cout << " OPTIONS " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
|
||||
std::cout << "Middlewares: " << middlewares_.size() << std::endl;
|
||||
std::cout << "==========================" << std::endl;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- 移动端适配核心标签 -->
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<title>DNFS · 树状笔记</title>
|
||||
<style>
|
||||
code {
|
||||
color: red;
|
||||
background-color: rgb(254, 235, 238);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
pre > code {
|
||||
display: block;
|
||||
color: white;
|
||||
background-color: rgb(96, 96, 96);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
blockquote {
|
||||
margin-right: 0px;
|
||||
background-color: #cfcfcf;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<!-- 文章主标题 -->
|
||||
<h1><div id="article-title">title</div></h1>
|
||||
|
||||
<!-- 文章详情 -->
|
||||
<div>
|
||||
发布时间:<span id="publish-time">2026-1-1</span>
|
||||
</div>
|
||||
<div>
|
||||
浏览:<span id="visit-count">999</span>
|
||||
点赞:<span id="like-count">999</span>
|
||||
分享:<span id="share-count">999</span>
|
||||
</div>
|
||||
</center>
|
||||
<!-- 文章正文-->
|
||||
<div id="article-content">hello</div>
|
||||
<hr>
|
||||
<div class="other-recommend">
|
||||
<h3>其他推荐</h3>
|
||||
</div>
|
||||
<div class="recommend-list" id="recommend-list">
|
||||
<ul>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<br>
|
||||
<h3>节点操作</h3>
|
||||
<div>
|
||||
<label>新增节点</label><input id="input-note-title"></input><br>
|
||||
<label>节点内容</label><textarea id="input-note-content"></textarea><br>
|
||||
<button id="input-note-submit">提交</button>
|
||||
<button id="input-note-clear">重置</button>
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.0/markdown-it.min.js"></script>
|
||||
<script src="/js/validation.js"></script>
|
||||
<script src="/js/dnfs.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,167 @@
|
||||
async function getNode(token, id) {
|
||||
try {
|
||||
obj = { "pid": String(id) }
|
||||
jsonBody = JSON.stringify(obj)
|
||||
console.log(jsonBody)
|
||||
const res = await fetch('/get_notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // 告诉服务器内容为json
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Target-Url': 'http://127.0.0.1:8006'
|
||||
},
|
||||
body: jsonBody
|
||||
});
|
||||
|
||||
const responseText = await res.text();
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch {
|
||||
console.error(`获取笔记接口返回非 JSON 内容(HTTP ${res.status}):`, responseText);
|
||||
throw new Error('服务器响应格式异常,请稍后重试');
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(result.message || `获取节点失败(HTTP ${res.status})`);
|
||||
}
|
||||
|
||||
//if (result.success) {
|
||||
console.log(`获取节点成功`);
|
||||
return result
|
||||
//...
|
||||
|
||||
//} else {
|
||||
// alert(result.message || '获取节点失败');
|
||||
//}
|
||||
|
||||
} catch (err) {
|
||||
alert(err.message || '接口请求失败,请检查后端服务是否启动');
|
||||
console.error('获取节点请求异常:', err);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
async function addNode(token, pid, title, content) {
|
||||
try {
|
||||
obj = {
|
||||
"pid": String(pid),
|
||||
"title": title,
|
||||
"content": content
|
||||
}
|
||||
jsonBody = JSON.stringify(obj)
|
||||
console.log(jsonBody)
|
||||
const res = await fetch('/add_note', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // 告诉服务器内容为json
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Target-Url': 'http://127.0.0.1:8006'
|
||||
},
|
||||
body: jsonBody
|
||||
});
|
||||
|
||||
const responseText = await res.text();
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = JSON.parse(responseText);
|
||||
} catch {
|
||||
console.error(`添加笔记接口返回非 JSON 内容(HTTP ${res.status}):`, responseText);
|
||||
throw new Error('服务器响应格式异常,请稍后重试');
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(result.message || `添加节点失败(HTTP ${res.status})`);
|
||||
}
|
||||
|
||||
//if (result.success) {
|
||||
console.log(`添加节点成功`, result);
|
||||
return result
|
||||
//...
|
||||
|
||||
//} else {
|
||||
// alert(result.message || '获取节点失败');
|
||||
//}
|
||||
|
||||
} catch (err) {
|
||||
alert(err.message || '接口请求失败,请检查后端服务是否启动');
|
||||
console.error('获取节点请求异常:', err);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
function renderMarkdown(element, content) {
|
||||
/*
|
||||
Markdown渲染
|
||||
*/
|
||||
const md = window.markdownit();
|
||||
element.innerHTML = md.render(content);
|
||||
}
|
||||
const stack = []; /* 这个是笔记节点的栈 */
|
||||
var article_title = document.getElementById('article-title');
|
||||
var article_content = document.getElementById('article-content');
|
||||
var input_note_title = document.getElementById('input-note-title');
|
||||
var input_note_content = document.getElementById('input-note-content');
|
||||
var input_note_submit = document.getElementById('input-note-submit');
|
||||
var input_note_clear = document.getElementById('input-note-clear');
|
||||
var recommend_list = document.getElementById('recommend-list');
|
||||
|
||||
input_note_submit.addEventListener('click', function(event) {
|
||||
var title = input_note_title.value
|
||||
var content = input_note_content.value
|
||||
var pid = (stack.length == 0) ? 0 : stack[stack.length - 1];
|
||||
alert(stack.length)
|
||||
alert(pid)
|
||||
addNode(token, pid, title, content).then(result => {
|
||||
alert(result.message);
|
||||
})
|
||||
})
|
||||
|
||||
input_note_clear.addEventListener('click', function(event) {
|
||||
input_note_title.value = "";
|
||||
input_note_content.value = "";
|
||||
})
|
||||
|
||||
function load_notes(main_node) {
|
||||
article_title.textContent = "";
|
||||
article_content.textContent = "";
|
||||
|
||||
recommend_list.replaceChildren();
|
||||
//alert(stack.length)
|
||||
if (stack.length > 0) { /* 如果不在根节点 */
|
||||
var bak = document.createElement('a');
|
||||
//ttl.href = '/dnfs.html';
|
||||
bak.addEventListener('click', function (event) {
|
||||
stack.pop();
|
||||
var id = (stack.length > 1) ? stack[stack.length - 1] : 0;
|
||||
getNode(token, id).then(load_notes)
|
||||
});
|
||||
var bak1 = document.createElement('h1');
|
||||
bak1.textContent = "返回上一层";
|
||||
bak.appendChild(bak1)
|
||||
recommend_list.appendChild(bak)
|
||||
}
|
||||
for (i in main_node) {
|
||||
item = main_node[i]
|
||||
var ttl = document.createElement('a');
|
||||
//ttl.href ="/dnfs.html";
|
||||
ttl.addEventListener('click', function(event) {
|
||||
var id = Number(item.id)
|
||||
stack.push(id)
|
||||
getNode(token, id).then(load_notes)
|
||||
});
|
||||
var ttl1 =document.createElement('h1');
|
||||
ttl1.textContent = item.title
|
||||
ttl.appendChild(ttl1)
|
||||
recommend_list.appendChild(ttl)
|
||||
var ctt = document.createElement('p');
|
||||
renderMarkdown(ctt, item.content);
|
||||
recommend_list.appendChild(ctt)
|
||||
recommend_list.appendChild(document.createElement('br'))
|
||||
}
|
||||
};
|
||||
|
||||
var token = sessionStorage.getItem('q4_token')
|
||||
getNode(token, 0).then(load_notes);
|
||||
@@ -148,8 +148,8 @@ form.addEventListener('submit', async function(e) {
|
||||
|
||||
if (result.success) {
|
||||
alert(`登陆成功`);
|
||||
sessionStorage.setItem('token', result.data.token);
|
||||
location.href = 'main.html';
|
||||
sessionStorage.setItem('q4_token', result.data.token);
|
||||
location.href = 'dnfs.html';
|
||||
} else {
|
||||
alert(result.message || '登录失败');
|
||||
}
|
||||
|
||||
@@ -58,5 +58,13 @@
|
||||
};
|
||||
}
|
||||
|
||||
function note_title(value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function note_content(value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
window.FormValidators = { username, password, email };
|
||||
}());
|
||||
@@ -1,102 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- 移动端适配核心标签 -->
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||
<title>DNFS · 树状笔记</title>
|
||||
<style></style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 文章主标题 -->
|
||||
<div id="article-title">
|
||||
<h1>
|
||||
<p>Python</p>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- 文章详情 -->
|
||||
<div id="publish-time">
|
||||
<p>发布时间:2026-07-11</p>
|
||||
</div>
|
||||
<div>
|
||||
<span id="visit-count">浏览:126</span>
|
||||
<span id="like-count">点赞:28</span>
|
||||
<span id="share-count">分享:7</span>
|
||||
</div>
|
||||
|
||||
<!-- 文章正文-->
|
||||
<div id="article-content">
|
||||
<p>
|
||||
Python 的 <code>socket</code>
|
||||
模块是网络编程的核心,其函数可分为<strong>核心套接字操作</strong>、<strong>套接字选项设置</strong>、<strong>辅助工具函数</strong>三大类。本文将逐一拆解所有函数的语法、参数、用法和实战示例,覆盖从基础到高级的所有场景。
|
||||
</p>
|
||||
|
||||
<h2>一、模块初始化与核心对象</h2>
|
||||
<h3>1. socket.socket():创建套接字对象</h3>
|
||||
<p><strong>语法</strong>:</p>
|
||||
<pre><code>socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)</code></pre>
|
||||
<p><strong>作用</strong>:创建一个套接字对象,是所有网络操作的基础。</p>
|
||||
<p><strong>参数说明</strong>:</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>参数</th>
|
||||
<th>取值</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>family</code></td>
|
||||
<td><code>AF_INET</code>(默认)</td>
|
||||
<td>IPv4 协议族(最常用)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><code>AF_INET6</code></td>
|
||||
<td>IPv6 协议族</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><code>AF_UNIX</code></td>
|
||||
<td>Unix 域套接字(本地进程通信)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>type</code></td>
|
||||
<td><code>SOCK_STREAM</code>(默认)</td>
|
||||
<td>TCP 套接字(面向连接、可靠)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><code>SOCK_DGRAM</code></td>
|
||||
<td>UDP 套接字(无连接、数据报)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><code>SOCK_RAW</code></td>
|
||||
<td>原始套接字(可操作 IP 层,用于抓包)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>proto</code></td>
|
||||
<td>0(默认)</td>
|
||||
<td>协议编号,默认自动匹配 type(TCP=6,UDP=17)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fileno</code></td>
|
||||
<td>None(默认)</td>
|
||||
<td>复用已有文件描述符,无需手动指定</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="other-recommend">
|
||||
<h3>其他推荐</h3>
|
||||
</div>
|
||||
<div class="recommend-list" id="recommend-list">
|
||||
<ul>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
<li><a href="https://www.baidu.com">百度推荐</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user