From 0e64b3ca26c0dedeeb6225d015d2f8714c02fc25 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Sun, 26 Jul 2026 14:41:38 +0800 Subject: [PATCH 1/7] refix --- gateway/src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway/src/main.cpp b/gateway/src/main.cpp index c649987..bd9218a 100644 --- a/gateway/src/main.cpp +++ b/gateway/src/main.cpp @@ -522,8 +522,8 @@ void registerRoutes(HttpServer &server) { }); server.get("*", [](const auto& req, const auto& params) -> http::response { - return wapperHttp(req, params, - [](const auto& req, const auto& params) { return handleProxy(req, params, "get"); }) + return wrapperHttp(req, params, + [](const auto& req, const auto& params) { return handleProxy(req, params, "get"); }); }); server.post("*", [](const auto& req, const auto& params) { @@ -943,7 +943,7 @@ http::response handleProxy( catch (const boost::wrapexcept& e) { THROW_EX_CUSTOM(HttpException, 2302, "Bad token", 400, "*"); } - if (!jv_r.is_object()) THROW_EX_CUSTOM(HttpException, 2302, "Bad token", 400, "*"); + if (!jv_t.is_object()) THROW_EX_CUSTOM(HttpException, 2302, "Bad token", 400, "*"); json::object& obj = jv_t.as_object(); auto t = json::value_to(obj["created_at"]); From 6067030ae241986cb70e30932fb51f0d47d29a52 Mon Sep 17 00:00:00 2001 From: shufeng2012 <277665039@qq.com> Date: Sun, 26 Jul 2026 19:37:20 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20=E6=B7=BB=E5=8A=A0smtp=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E9=AA=8C=E8=AF=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smtp/.gitignore | 2 + smtp/README.md | 16 +++++ smtp/log_client.py | 156 +++++++++++++++++++++++++++++++++++++++++++++ smtp/smtp.py | 96 +++++++++++++++++----------- 4 files changed, 233 insertions(+), 37 deletions(-) create mode 100644 smtp/.gitignore create mode 100644 smtp/README.md create mode 100644 smtp/log_client.py diff --git a/smtp/.gitignore b/smtp/.gitignore new file mode 100644 index 0000000..ae52178 --- /dev/null +++ b/smtp/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.env/ \ No newline at end of file diff --git a/smtp/README.md b/smtp/README.md new file mode 100644 index 0000000..a5e9f2a --- /dev/null +++ b/smtp/README.md @@ -0,0 +1,16 @@ +# SMTP API 手册 +## `/send_email` +```json +{ + "email": "接收验证码的邮箱" +} +``` + +## `/verify_email` +```json +{ + "email": "刚才接收验证码的邮箱", + "input_captcha": "输入的验证码" +} + +``` \ No newline at end of file diff --git a/smtp/log_client.py b/smtp/log_client.py new file mode 100644 index 0000000..5bcf8c4 --- /dev/null +++ b/smtp/log_client.py @@ -0,0 +1,156 @@ +""" +统一日志客户端 — 各服务共用。 + +设计要点: + - 使用 urllib.request(标准库,不引入 requests) + - 异步非阻塞:内存队列 + 后台线程批量推送,不拖慢业务 + - fail-safe:db 服务不可用时静默丢弃,绝不影响业务 + - 缓冲批量:队列满 50 条或超时 2 秒时自动 flush + +用法: + from log_client import logs + + logs.info("dnfs", "节点新增成功 id=42") + logs.error("dnfs", "数据库连接失败", extra={"errno": 2002}) + logs.push("gateway", "WARNING", "登录失败次数超限", + trace_id="req-abc", extra={"ip": "1.2.3.4"}) +""" + +import json +import threading +import queue +import time +import urllib.request +import urllib.error +import atexit + +# ── 配置 ────────────────────────────────────────────────────────────── + +DB_LOG_URL = 'http://127.0.0.1:8005/log/ingest' + +_BATCH_SIZE = 50 # 队列满 50 条时 flush +_FLUSH_INTERVAL = 2.0 # 超时 2 秒强制 flush +_TIMEOUT = 5 # HTTP 请求超时(秒) + + +class LogClient: + """异步批量日志推送客户端。""" + + def __init__(self, url=DB_LOG_URL, batch_size=_BATCH_SIZE, + flush_interval=_FLUSH_INTERVAL): + self._url = url + self._batch_size = batch_size + self._flush_interval = flush_interval + self._queue = queue.Queue() + self._stop = threading.Event() + self._thread = threading.Thread(target=self._flush_loop, + daemon=True, name='log-client') + self._thread.start() + atexit.register(self._shutdown) + + # ── 公开接口 ──────────────────────────────────────────────────────── + + def push(self, service, level, message, trace_id=None, extra=None): + """推送一条日志(非阻塞,立即返回)。""" + self._queue.put({ + 'service': service, + 'level': level, + 'message': message, + 'trace_id': trace_id, + 'extra': extra, + }) + + def debug(self, service, message, **kw): + self.push(service, 'DEBUG', message, **kw) + + def info(self, service, message, **kw): + self.push(service, 'INFO', message, **kw) + + def warning(self, service, message, **kw): + self.push(service, 'WARNING', message, **kw) + + def error(self, service, message, **kw): + self.push(service, 'ERROR', message, **kw) + + def critical(self, service, message, **kw): + self.push(service, 'CRITICAL', message, **kw) + + def flush(self): + """手动 flush 队列中剩余的日志。""" + batch = [] + while True: + try: + batch.append(self._queue.get_nowait()) + if len(batch) >= self._batch_size: + self._send(batch) + batch = [] + except queue.Empty: + break + if batch: + self._send(batch) + + # ── 内部实现 ──────────────────────────────────────────────────────── + + def _flush_loop(self): + """后台线程:积攒到 batch_size 或超时 flush_interval 时发送。""" + batch = [] + last_flush = time.monotonic() + + while not self._stop.is_set(): + timeout = max(0, self._flush_interval - (time.monotonic() - last_flush)) + try: + item = self._queue.get(timeout=timeout) + batch.append(item) + # 继续非阻塞地取,直到满 batch_size + while len(batch) < self._batch_size: + try: + batch.append(self._queue.get_nowait()) + except queue.Empty: + break + except queue.Empty: + # 【核心修复 1】:如果没有新日志,必须重置上次刷新时间。 + # 否则下次 timeout 会变成 0,导致疯狂空转。 + last_flush = time.monotonic() + # 【核心修复 2】:跳出本次循环,避免重复执行下面的 `if should_flush` 判断 + continue + + # 满了或超时了就发 + should_flush = ( + len(batch) >= self._batch_size or + (batch and time.monotonic() - last_flush >= self._flush_interval) + ) + if should_flush: + self._send(batch) + batch = [] + last_flush = time.monotonic() + + # 退出前 flush 剩余 + if batch: + self._send(batch) + + def _send(self, batch): + """发送一批日志到 db 服务。失败时静默丢弃。""" + data = json.dumps({'logs': batch}).encode('utf-8') + req = urllib.request.Request( + self._url, data=data, + headers={'Content-Type': 'application/json'}, + method='POST' + ) + try: + with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp: + resp.read() # 消费响应体 + except (urllib.error.URLError, urllib.error.HTTPError, OSError): + # fail-safe:日志服务不可用,静默丢弃,不影响业务 + pass + except Exception: + pass + + def _shutdown(self): + """进程退出时 flush 剩余日志。""" + self._stop.set() + self.flush() + + +# ── 全局单例 ────────────────────────────────────────────────────────── + +logs = LogClient() diff --git a/smtp/smtp.py b/smtp/smtp.py index c32697b..816accc 100644 --- a/smtp/smtp.py +++ b/smtp/smtp.py @@ -1,62 +1,84 @@ -from flask import Flask,app -from urllib import request +#!/usr/bin/env python3 + +from flask import Flask, request, jsonify from email.mime.text import MIMEText -from email.header import Header +from email import header +from log_client import logs +import waitress import smtplib import random -from waitress import serve +import time -verification_code = 0 +app = Flask(__name__) +captchas: dict[str, list[str, str]] = {} -@app.route('/send_email', methods=["POST"]) +@app.route("/send_email", methods=["POST"]) def send_email(): + def build_captcha() -> str: + captcha: str = "" + for i in range(0,4): + digit: int = random.randint(0,9) + captcha += str(digit) + return captcha + + SMTP_SERVER: str = "" + SMTP_PORT: str = 0 + SMTP_USER: str = "" + __PASSWORD: str = "" + data = request.get_json() - reciver_email = data.get('email') - SMTP_SERVER = '' - SMTP_PORT = 587 - SENDER_EMAIL = '' - __PASS = '' + recipient: str = data.get("email") + subject: str = "DNFS 邮箱验证" + captcha: str = build_captcha() + content: str = f"DNFS: 您的邮箱验证码为 {captcha},一分钟内有效。" - verification_code = random.randint(1000,9999) + captchas[recipient] = ["", ""] + captchas[recipient][0] = captcha + captchas[recipient][1] = str(int(time.time())+60) - subject = "DNFS 验证码" - content = f"DNFS: 您的验证码为{str(verification_code)}" - - email_message = MIMEText(content, 'plain', 'utf-8') - email_message['From'] = Header(SENDER_EMAIL, 'utf-8') - email_message['To'] = Header(reciver_email, 'utf-8') - email_message['Subject'] = Header(subject) + message = MIMEText(content, "plain", "UTF-8") + message["From"] = "dnfs@qsont.me" + message["To"] = recipient + message["Subject"] = subject try: - server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) - server.starttls() - server.login(SENDER_EMAIL, __PASS) - server.sendmail(SENDER_EMAIL, [reciver_email], email_message.as_string()) - return 200 + server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, timeout=10) + server.login(SMTP_USER, __PASSWORD) + server.sendmail(SMTP_USER, [recipient], message.as_string()) + logs.info("smtp", f"{recipient}: 验证码发送成功({captcha})") + return jsonify({"success": "验证码发送成功"}), 200 except Exception as err: - raise err - return 400 + logs.error("smtp", f"{recipient}: 验证码发送失败({err})") + return jsonify({"error": f"验证码发送失败:{err}"}), 400 finally: server.quit() -@app.route('/get_verification_code', method=["POST"]) -def get_verification_code(): - data=request.get_json() +@app.route("/verify_email", methods=["POST"]) - input_verification_code = data.get('verification_code') +def verify_email(): + data = request.get_json() - if verification_code == input_verification_code: - return 200 + for i in list(captchas.keys()): + if int(captchas[i][1]) < int(time.time()): + del captchas[i] + + email: str = data.get("email") + input_captcha: str = data.get("input_captcha") + + if captchas[email][0] == input_captcha: + logs.info("smtp", f"{email}: 验证通过") + return jsonify({"success": "验证通过"}), 200 else: - return 400 + logs.info("smtp", f"{email}: 验证失败") + return jsonify({"error": "验证失败"}), 400 + -ADDR = '127.0.0.1' -PORT = 8007 if __name__ == "__main__": - print(f'Listen at http://{ADDR}:{PORT}') - serve(app, host=ADDR, port=PORT) \ No newline at end of file + logs.info("smtp", "开始监听http://127.0.0.1:1234") + print("Listen at http://127.0.0.1:1234") + waitress.serve(app, host="127.0.0.1", port=1234) \ No newline at end of file From fb57c87d17be7de969e73f4ead70055e1bbbcee4 Mon Sep 17 00:00:00 2001 From: shufeng2012 <277665039@qq.com> Date: Sun, 26 Jul 2026 20:14:40 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=94=A5=20=E5=A5=BD=E5=83=8F=E8=BF=99?= =?UTF-8?q?=E4=B8=AAAPI=E6=96=87=E6=A1=A3=E6=B2=A1=E5=95=A5=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smtp/README.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 smtp/README.md diff --git a/smtp/README.md b/smtp/README.md deleted file mode 100644 index a5e9f2a..0000000 --- a/smtp/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# SMTP API 手册 -## `/send_email` -```json -{ - "email": "接收验证码的邮箱" -} -``` - -## `/verify_email` -```json -{ - "email": "刚才接收验证码的邮箱", - "input_captcha": "输入的验证码" -} - -``` \ No newline at end of file From 52f4429f6f63048c728ec34f37bdbe97610f1cf0 Mon Sep 17 00:00:00 2001 From: shufeng2012 <277665039@qq.com> Date: Sun, 26 Jul 2026 20:22:31 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smtp/smtp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smtp/smtp.py b/smtp/smtp.py index 816accc..e45fd61 100644 --- a/smtp/smtp.py +++ b/smtp/smtp.py @@ -79,6 +79,6 @@ def verify_email(): if __name__ == "__main__": - logs.info("smtp", "开始监听http://127.0.0.1:1234") - print("Listen at http://127.0.0.1:1234") - waitress.serve(app, host="127.0.0.1", port=1234) \ No newline at end of file + logs.info("smtp", "开始监听http://127.0.0.1:8008") + print("Listen at http://127.0.0.1:8008") + waitress.serve(app, host="127.0.0.1", port=8008) \ No newline at end of file From 957981c2c31792df1af7553ba1d35e763277d664 Mon Sep 17 00:00:00 2001 From: shufeng2012 <277665039@qq.com> Date: Sun, 26 Jul 2026 20:57:04 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=8D=B1=20=E6=9B=B4=E6=96=B0=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _inst.sh | 1 + _setup.rhel.sh | 0 core/init.txt | 1 + smtp/_set.sh | 4 ++-- smtp/requirements.txt | 3 ++- 5 files changed, 6 insertions(+), 3 deletions(-) mode change 100644 => 100755 _setup.rhel.sh diff --git a/_inst.sh b/_inst.sh index a701068..03c5f4d 100755 --- a/_inst.sh +++ b/_inst.sh @@ -34,4 +34,5 @@ inst_module "waf" # waf 在 gateway 之前编译,部署时由 core 先拉起 w inst_module "static" inst_module "dnfs" inst_module "db" +inst_module "smtp" inst_module "core" # core last diff --git a/_setup.rhel.sh b/_setup.rhel.sh old mode 100644 new mode 100755 diff --git a/core/init.txt b/core/init.txt index 23998a1..12b510e 100644 --- a/core/init.txt +++ b/core/init.txt @@ -1,6 +1,7 @@ bash /opt/micro_servers/db/_run.sh bash /opt/micro_servers/ai_review/_run.sh bash /opt/micro_servers/dnfs/_run.sh +bash /opt/micro_servers/smtp/_run.sh bash /opt/micro_servers/static/_run.sh bash /opt/micro_servers/waf/_run.sh bash /opt/micro_servers/gateway/_run.sh diff --git a/smtp/_set.sh b/smtp/_set.sh index e50af4f..5c79af3 100755 --- a/smtp/_set.sh +++ b/smtp/_set.sh @@ -1,7 +1,7 @@ #!/bin/bash cd "$(dirname "$0")" || exit -python3 -m venv .env +python3 -m venv venv #sudo chmod +x .env/bin/activate -source .env/bin/activate +source venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple #nuitka --standalone smtp.py diff --git a/smtp/requirements.txt b/smtp/requirements.txt index c383cd6..5680c64 100644 --- a/smtp/requirements.txt +++ b/smtp/requirements.txt @@ -3,4 +3,5 @@ flask flask-cors flask_sqlalchemy #gunicorn -waitress \ No newline at end of file +waitress +python-dotenv \ No newline at end of file From 747c2ebd0a11db85de16c25f471fe08a7057837e Mon Sep 17 00:00:00 2001 From: shufeng2012 <277665039@qq.com> Date: Sun, 26 Jul 2026 20:57:16 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=99=88=20=E6=9B=B4=E6=96=B0smtp/.giti?= =?UTF-8?q?gnore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smtp/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smtp/.gitignore b/smtp/.gitignore index ae52178..dcf118f 100644 --- a/smtp/.gitignore +++ b/smtp/.gitignore @@ -1,2 +1,3 @@ __pycache__/ -.env/ \ No newline at end of file +.env +venv/ \ No newline at end of file From c1642dbc0c25f3de58c190345c9ad2a9c44aa457 Mon Sep 17 00:00:00 2001 From: shufeng2012 <277665039@qq.com> Date: Sun, 26 Jul 2026 20:57:54 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9E=95=20=E6=B7=BB=E5=8A=A0.env=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smtp/smtp.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/smtp/smtp.py b/smtp/smtp.py index e45fd61..de970b2 100644 --- a/smtp/smtp.py +++ b/smtp/smtp.py @@ -4,10 +4,15 @@ from flask import Flask, request, jsonify from email.mime.text import MIMEText from email import header from log_client import logs +from dotenv import load_dotenv import waitress import smtplib import random import time +import sys, os + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")) app = Flask(__name__) captchas: dict[str, list[str, str]] = {} @@ -22,11 +27,6 @@ def send_email(): captcha += str(digit) return captcha - SMTP_SERVER: str = "" - SMTP_PORT: str = 0 - SMTP_USER: str = "" - __PASSWORD: str = "" - data = request.get_json() recipient: str = data.get("email")