深爱多年的,今天终于放下.
书接上回: https://www.v2ex.com/t/1218755#reply0
首先跟兄弟们道个歉,因为发现还是感情贴热度高,没法,蹭蹭热度.感谢,祝大家早日财务自由!
前两天闲来无事做了一个项目,初衷和目的上一章都有,可能就是觉得不甘心吧.期间也认识了一些新的人接触一些新的知识.
还是觉得 ai 是用来更好的服务人民的,我这个如果开源了,旅游业会发展跟快一点.起码不是跟我左右到处碰壁.万一有人真成了,街头店家都可以用,收外国的钱.我感觉这个意义更好.希望如果有人做起来也挺好的.也不知道行不行.
以下是代码的一些内容,有用的自取,感谢.
架构
Vercel Fluid Compute 单函数入口
│
▼
api/index.py → app/init.py (Flask app factory)
│
├── Session: Flask session cookie, HTTPOnly, SameSite=Lax
├── 存储: Upstash Redis REST API → 内存 dict fallback
├── AI: DeepSeek API (deepseek-chat)
├── 前端: Jinja2 模板 + Tailwind CDN + Vanilla JS
│
└── 8 个 Blueprint:
translate /api/translate, /api/export-*
auth /api/auth/*, /login, /logout
merchant /api/merchant/*
gmb /api/gmb/* (Google OAuth)
payment /api/payment/* (支付诊断)
psb /api/psb/* (MRZ + 机构查询)
beta /api/health, /api/admin/*
guide /api/guide/checklist
源代码
api/index.py — Vercel 入口
from app import create_app
app = create_app()
app/init.py — Flask 工厂
import os from flask import Flask from dotenv import load_dotenv
load_dotenv(“.env.local”, override=False) load_dotenv(“.env”, override=False)
def create_app():
app = Flask(__name__, template_folder="templates")
app.secret_key = os.getenv("SESSION_SECRET", os.urandom(24).hex())
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
from app.translate import translate_bp
from app.auth import auth_bp
from app.merchant import merchant_bp
from app.gmb import gmb_bp
from app.payment import payment_bp
from app.psb import psb_bp
from app.beta import beta_bp
from app.guide import guide_bp
app.register_blueprint(translate_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(merchant_bp)
app.register_blueprint(gmb_bp)
app.register_blueprint(payment_bp)
app.register_blueprint(psb_bp)
app.register_blueprint(beta_bp)
app.register_blueprint(guide_bp)
@app.route("/")
def index():
from flask import render_template
return render_template("index.html")
@app.route("/dashboard")
def dashboard():
from flask import render_template
return render_template("dashboard.html")
@app.route("/tripadvisor")
def tripadvisor_guide():
from flask import render_template
return render_template("tripadvisor_guide.html")
@app.route("/psb")
def psb_guide():
from flask import render_template
return render_template("psb_guide.html")
@app.route("/gmb/guide")
def gmb_guide():
from flask import render_template
return render_template("gmb_guide.html")
@app.route("/health")
def health():
from flask import redirect
return redirect("/api/health")
return app
app/kv_client.py — 存储抽象层
import os, json, time, threading from urllib.request import Request, urlopen
def _strip_env(val):
v = val.strip()
if v and ord(v[0]) == 0xFEFF: # 去除 Upstash URL 的 BOM 前缀
v = v[1:]
return v
_REST_URL = _strip_env(os.getenv(“UPSTASH_REDIS_REST_URL”, “”)) _REST_TOKEN = _strip_env(os.getenv(“UPSTASH_REDIS_REST_TOKEN”, “”))
_REDIS_OK = None _MEMORY_STORE = {} _MEMORY_LOCK = threading.Lock()
def _redis_cmd(*args):
if not (_REST_URL and _REST_TOKEN):
return None
try:
body = json.dumps(args).encode("utf-8")
req = Request(_REST_URL, data=body, headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {_REST_TOKEN}",
})
with urlopen(req, timeout=5) as r:
resp = json.loads(r.read().decode("utf-8"))
_REDIS_OK = True # FIXME: global mutable
return resp.get("result")
except Exception:
_REDIS_OK = False
return None
公开 API — Redis 优先,内存 fallback
def redis_get(key):
if _REST_URL and _REST_TOKEN:
result = _redis_cmd("GET", key)
if result is not None or _REDIS_OK:
return result
with _MEMORY_LOCK:
entry = _MEMORY_STORE.get(key)
if entry and entry.get("exp") and time.time() > ent
del _MEMORY_STORE[key]
return None
return entry.get("val") if entry else None
def redis_set(key, value, ex=None):
if _REST_URL and _REST_TOKEN:
if ex:
result = _redis_cmd("SET", key, str(value), "EX", str(ex))
else:
result = _redis_cmd("SET", key, str(value))
if result is not None or _REDIS_OK:
return result
with _MEMORY_LOCK:
entry = {"val": str(value)}
if ex:
entry["exp"] = time.time() + int(ex)
_MEMORY_STORE[key] = entry
return True
def redis_incr(key):
if _REST_URL and _REST_TOKEN:
result = _redis_cmd("INCR", key)
if result is not None:
return result
with _MEMORY_LOCK:
val = redis_get(key)
new_val = (int(val) + 1) if val is not None else 1
_MEMORY_STORE[key] = {"val": str(new_val)}
return new_val
def redis_del(key):
if _REST_URL and _REST_TOKEN:
_redis_cmd("DEL", key)
with _MEMORY_LOCK:
_MEMORY_STORE.pop(key, None)
def redis_keys(pattern):
if _REST_URL and _REST_TOKEN:
keys, cursor = [], "0"
while True:
result = _redis_cmd("SCAN", cursor, "MATCH", pattern, "COUNT", "200")
if not isinstance(result, list) or len(result) < 2:
return []
cursor = str(result[0]) if result[0] is not None else "0"
keys.extend(result[1] if isinstance(result[1], list) else [])
if cursor == "0":
break
return keys
import re
escaped = re.escape(pattern).replace(r"\*", ".*")
compiled = re.compile("^" + escaped + "$")
with _MEMORY_LOCK:
return [k for k in _MEMORY_STORE if compiled.match(k)]
def is_redis_available():
if _REST_URL and _REST_TOKEN:
return _redis_cmd("PING") == "PONG"
return False
app/translate.py — AI 翻译(核心)
import os, json, sys from urllib.request import Request, urlopen from urllib.error import URLError from flask import Blueprint, request, jsonify, session
from app.rate_limit import rate_limit from app.auth import login_required from app.constants import HISTORY_MAX_ENTRIES, HISTORY_TTL
translate_bp = Blueprint(“translate”, name) PROMPTS = {
"menu": {
"en": "You are a professional restaurant menu translator. Translate Chinese menu items into natural, appetizing English...",
"ja": "あなたはプロの料理メニュー翻訳者です...",
"ko": "당신은 전문 레스토랑 메뉴 번역가입니다...",
"ru": "Вы профессиональный переводчик меню ресторанов...",
},
"checkin_guide": { ... },
"room_card": { ... },
"reg_card": { ... },
"emergency_card": { ... },
}
LANGUAGES = [
{"code": "en", "label": "English", "flag": "🇬🇧"},
{"code": "ja", "label": "日本語", "flag": "🇯🇵"},
{"code": "ko", "label": "한국어", "flag": "🇰🇷"},
{"code": "ru", "label": "Русский", "flag": "🇷🇺"},
]
@translate_bp.route(“/api/translate”, methods=[“POST”]) @rate_limit(“translate”, identifier_fn=lambda: session.get(“user_id”, “anon”)) def translate():
data = request.get_json()
source_text = data.get("sourceText", "")
material_type = data.get("materialType", "")
target_lang = data.get("targetLang", "")
api_key = os.getenv("DEEPSEEK_API_KEY", "").strip()
system_prompt = PROMPTS.get(material_type, {}).get(target_lang, "")
req_body = json.dumps({
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": source_text},
],
"temperature": 0.3,
"max_tokens": 4096,
}).encode("utf-8")
req = Request("https://api.deepseek.com/v1/chat/completions",
data=req_body,
headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"})
with urlopen(req, timeout=30) as r:
body = json.loads(r.read().decode("utf-8"))
translated = body["choices"][0]["message"]["content"]
app/payment.py — 支付诊断引擎(节选核心算法)
3 个方案各有评分规则矩阵
SOLUTIONS = {
"airwallex": {
"name": "Airwallex 空中云汇",
"score_rules": {
"monthly_volume": {"low": 1, "medium": 2, "high": 3},
"customer_type": {"mixed": 2, "foreign": 3},
"tech_level": {"basic": -1, "intermediate": 1, "advanced": 2},
...
},
},
"lianlian": { ... },
"bank_pos": { ... },
}
def diagnose_payment(answers):
"""对 3 个方案各自打分,按分数降序排列,第一名标记 recommended"""
results = []
for slug, sol in SOLUTIONS.items():
score = 0
reasons = []
for qid, answer in answers.items():
pts = sol["score_rules"].get(qid, {}).get(answer, 0)
score += pts
reason_key = f"{qid}={answer}"
if reason_key in REASON_TEMPLATES.get(slug, {}):
reasons.append(REASON_TEMPLATES[slug][reason_key])
results.append({"slug": slug, "name": sol["name"], "score
results[0]["recommended"] = True
return results
app/psb.py — MRZ 护照解析(节选)
def parse_mrz_td3(line1, line2):
"""解析标准护照 MRZ (2×44 字符), 符合 ICAO 9303"""
issuing_state = line1[2:5].replace("<", "").strip()
nationality = line2[10:13].replace("<", "").strip()
doc_number = line2[0:9].replace("<", "").strip()
dob_raw = line2[13:19]
sex = line2[20]
expiry_raw = line2[21:27]
# 解析姓名
name_part = line1[5:44]
surnames, given_names = [], []
if "<<" in name_part:
surname_part, given_part = name_part.split("<<", 1)
surnames = [s for s in surname_part.split("<") if s]
given_names = [s for s in given_part.split("<") if s]
def fmt_date(raw):
yy = int(raw[:2])
century = "19" if yy >= 70 else "20" # ICAO 标准: YY≥70→19xx
return f"{century}{raw[:2]}-{raw[2:4]}-{raw[4:6]}"
"passport_number": doc_number,
"nationality": COUNTRY_MAP.get(nationality, nationality),
"dob": fmt_date(dob_raw),
"sex": "男" if sex == "M" else "女" if sex == "F" else sex,
"expiry": fmt_date(expiry_raw),
}, None
def parse_mrz(line1, line2):
line1, line2 = line1.strip().upper(), line2.strip().upper()
if len(line1) == 44 and len(line2) == 44:
return parse_mrz_td3(line1, line2) # 标准护照
if len(line1) == 36 and len(line2) == 36:
return parse_mrz_td2(line1, line2) # 护照卡
return None, f"不支持 MRZ 格式 (行 1={len(line1)}, 行 2={len(line2)})"
app/gmb.py — Google 商家 OAuth (节选)
@gmb_bp.route(“/gmb/connect”) @login_required def gmb_connect():
"""生成 Google OAuth URL, state 存在 Redis (10 分钟 TTL)"""
state = os.urandom(16).hex()
redis_set(f"oauth_state:{state}", session["user_id"], ex=600)
params = urlencode({
"client_id": GMB_CLIENT_ID,
"redirect_uri": GMB_REDIRECT_URI,
"response_type": "code",
"scope": "https://www.googleapis.com/auth/business.manage",
"access_type": "offline",
"prompt": "consent",
"state": state,
})
return redirect(f"https://accounts.google.com/o/oauth2/v2/auth?{params}")
@gmb_bp.route(“/gmb/callback”) def gmb_callback():
"""Google 回调: 验证 state → 换 token → 发现/创建 GMB 账号 → 存 Redis"""
code = request.args.get("code")
state = request.args.get("state")
stored_user = redis_get(f"oauth_state:{state}")
redis_del(f"oauth_state:{state}")
if not stored_user:
return render_template("gmb_status.html", error="授权会话已过期")
# 用 code 换 access_token + refresh_token
body = urlencode({ "code": code, "client_id": ..., "grant_type": "authorization_code" })
# ... HTTP POST 到 https://oauth2.googleapis.com/token ...
# 存储 token
redis_set(f"gmb_token:{user_id}", json.dumps({
"access_token": access_token,
"refresh_token": refresh_token,
"expires_at": time.time() + expires_in - 60,
"account_id": account_id,
}))
return redirect("/gmb/form")
app/image_export.py — 卡片图片渲染
from PIL import Image, ImageDraw, ImageFont
CARD_W = 800 _FONT_CACHE = {}
def generate_card_image(text, lang, material_type, lang_label):
"""用 PIL 渲染翻译卡片为 PNG, 蓝顶栏 + 语言标签 + 正文 + 灰底页脚"""
f_body = _font(20)
# 计算正文高度 → 确定卡片总高度
body_text_height = _text_height(text, f_body, CARD_W - 80)
card_h = 56 + 24 + 20 + body_text_height + 24 + 32
img = Image.new("RGB", (CARD_W, int(card_h)), "#ffffff")
draw = ImageDraw.Draw(img)
# 顶部蓝条 + 物料类型标题
draw.rectangle([(0, 0), (CARD_W, 56)], fill="#2563eb")
# 语言标签 (右上角蓝色圆角徽章)
# 正文区域 (自动换行)
# 底部灰条 + "国际宾客支持 · 多语种物料生成"
buf = io.BytesIO()
img.save(buf, format="PNG", optimize=True)
buf.seek(0)
return buf
def generate_zip_of_cards(results, material_type, lang_label_map):
"""多语种结果打包为一个 ZIP"""
zip_buf = io.BytesIO()
with zipfile.ZipFile(zip_buf, "w", zipfile.ZIP_DEFLATED) as zf:
for r in results:
img_buf = generate_card_image(r["text"], r["lang"], material_type, ...)
zf.writestr(f"{r['lang']}.png", img_buf.read())
zip_buf.seek(0)
return zip_buf
app/merchant.py — 三档订阅套餐
SUBSCRIPTION_PLANS = {
"free": {"name": "免费版", "price": 0, "translations_per_day": 20, "languages": ["en","ja","ko","ru"]},
"pro": {"name": "专业版", "price": 29, "translations_per_day": 200, "languages": ["en","ja","ko","ru","fr","de","es","ar"]},
"business": {"name": "企业版", "price": 99, "translations_per_day": 1000, "languages": ["en","ja","ko","ru","fr","de","es","ar","th","vi","it","pt"]},
}
app/constants.py
DEFAULT_DAILY_LIMIT = 20 RATE_LIMIT_TTL = 86400 PROFILE_TTL = 7776000 # 90 天 FEEDBACK_TTL = 7776000 HISTORY_TTL = 7776000 PAY_ORDER_TTL = 2592000 # 30 天 OAUTH_STATE_TTL = 600 # 10 分钟 HISTORY_MAX_ENTRIES = 200 MAX_ERROR_LOG = 100 PBKDF2_ITERATIONS = 600000 MIN_PASSWORD_LENGTH = 8
app/static/app.js — 共享前端工具
function msg(type, text) { /* 浮动 toast 提示, 3.5 秒自动消失 / } function escapeHtml(str) { / XSS 防护 / } function requireAuth(callback) { / 检查 /api/auth/me, 未登录跳转 /login, 防重复跳转 */ }
关键设计决策
- INCR-first 速率限制:先原子递增再检查,避免 TOCTOU 竞态
- IP 提取:Vercel 上信任 X-Real-IP ,其次取 X-Forwarded-For 最后一跳
- BOM 前缀:Upstash URL 在 Vercel 环境变量面板粘贴时可能带 ( U+FEFF ),kv_client.py 自动剥离
- 密码哈希:PBKDF2-SHA256 60 万次迭代;旧 SHA-256 格式的账户登录时自动升级
- load_dotenv 顺序:先 .env.local 再 .env ,均为 override=False (前者优先)
- 无 JS 框架:前端纯 Vanilla JS ,所有模板内联
loveshuyuan · 2026-06-10 16:43
什么鬼玩意儿 @Livid
minibear2021 · 2026-06-10 17:08这是走火入魔了?
darksword21 · 2026-06-10 17:08让 AI 服务你妈的尸体,傻逼东西
记录翻译历史
app/auth.py — 认证
import re, hashlib, secrets, json from datetime import datetime, timezone from functools import wraps from flask import Blueprint, request, jsonify, session, render_template from app.kv_client import redis_get, redis_set, redis_incr from app.constants import PBKDF2_ITERATIONS, MIN_PASSWORD_LENGTH
auth_bp = Blueprint(“auth”, name)
PBKDF2_PREFIX = “pbkdf2:sha256:”
def _hash_password(password, salt=None):
def _verify_password(stored, password):
def login_required(f):
@auth_bp.route(“/login”) def login_page():
@auth_bp.route(“/api/auth/register”, methods=[“POST”]) def register():
@auth_bp.route(“/api/auth/login”, methods=[“POST”]) @rate_limit(“login”) def login_api():
app/rate_limit.py — 限流
import time, functools from flask import request, jsonify from app.kv_client import redis_incr, redis_set
LIMITS = {
def _get_ip():
def rate_limit(prefix, identifier_fn=None):