From f894e55602237cc88d1273be6643db049926ba40 Mon Sep 17 00:00:00 2001 From: Falck Date: Mon, 6 Apr 2026 12:40:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E5=86=97=E4=BD=99=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=BF=AE=E5=A4=8D=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E6=A0=87=E9=A2=98=E4=B8=8E=E6=A8=A1=E6=9D=BF=E5=AE=89?= =?UTF-8?q?=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 简化 http-api/http-tcp/web-toolkit 的 router.py,抽取重复代码 - 修复 ws-api middleware 中间件返回值传递问题 - 修复 web-toolkit template.py 安全漏洞 (eval → AST 验证) - 将首页标题从 "OSS Runtime" 改为 "Future OSS" - 更新 README.md 与 static/banner.svg - 新增 i18n 国际化插件 (骨架) - 新增 oss/shared/ 共享模块 --- README.md | 18 +- oss/shared/__init__.py | 4 + oss/shared/__pycache__/router.cpython-313.pyc | Bin 0 -> 5604 bytes oss/shared/router.py | 136 +++++++++++ static/banner.svg | 20 +- .../__pycache__/router.cpython-313.pyc | Bin 3599 -> 1124 bytes .../__pycache__/template.cpython-313.pyc | Bin 8539 -> 9155 bytes store/@{Falck}/web-toolkit/router.py | 60 +---- store/@{Falck}/web-toolkit/template.py | 39 +++- .../__pycache__/router.cpython-313.pyc | Bin 3868 -> 1204 bytes store/@{FutureOSS}/http-api/router.py | 72 +----- .../__pycache__/router.cpython-313.pyc | Bin 3633 -> 1175 bytes .../__pycache__/server.cpython-313.pyc | Bin 9849 -> 10801 bytes store/@{FutureOSS}/http-tcp/router.py | 60 +---- store/@{FutureOSS}/http-tcp/server.py | 29 ++- store/@{FutureOSS}/i18n/__init__.py | 1 + .../i18n/__pycache__/i18n.cpython-313.pyc | Bin 0 -> 7380 bytes .../i18n/__pycache__/main.cpython-313.pyc | Bin 0 -> 8642 bytes .../__pycache__/middleware.cpython-313.pyc | Bin 0 -> 3994 bytes store/@{FutureOSS}/i18n/i18n.py | 156 +++++++++++++ store/@{FutureOSS}/i18n/locales/en-US.json | 51 +++++ store/@{FutureOSS}/i18n/locales/zh-CN.json | 51 +++++ store/@{FutureOSS}/i18n/locales/zh-TW.json | 51 +++++ store/@{FutureOSS}/i18n/main.py | 215 ++++++++++++++++++ store/@{FutureOSS}/i18n/manifest.json | 23 ++ store/@{FutureOSS}/i18n/middleware.py | 90 ++++++++ .../__pycache__/middleware.cpython-313.pyc | Bin 2611 -> 2674 bytes store/@{FutureOSS}/ws-api/middleware.py | 11 +- website/index.html | 4 +- 29 files changed, 890 insertions(+), 201 deletions(-) create mode 100644 oss/shared/__init__.py create mode 100644 oss/shared/__pycache__/router.cpython-313.pyc create mode 100644 oss/shared/router.py create mode 100644 store/@{FutureOSS}/i18n/__init__.py create mode 100644 store/@{FutureOSS}/i18n/__pycache__/i18n.cpython-313.pyc create mode 100644 store/@{FutureOSS}/i18n/__pycache__/main.cpython-313.pyc create mode 100644 store/@{FutureOSS}/i18n/__pycache__/middleware.cpython-313.pyc create mode 100644 store/@{FutureOSS}/i18n/i18n.py create mode 100644 store/@{FutureOSS}/i18n/locales/en-US.json create mode 100644 store/@{FutureOSS}/i18n/locales/zh-CN.json create mode 100644 store/@{FutureOSS}/i18n/locales/zh-TW.json create mode 100644 store/@{FutureOSS}/i18n/main.py create mode 100644 store/@{FutureOSS}/i18n/manifest.json create mode 100644 store/@{FutureOSS}/i18n/middleware.py diff --git a/README.md b/README.md index bd6423b..86b64d1 100644 --- a/README.md +++ b/README.md @@ -36,18 +36,18 @@ FutureOSS/ -所有文档都在本地 `dock/` 目录中: +完整开发者文档请查阅 [项目 Wiki](https://gitee.com/starlight-apk/feature-oss/wikis): | 📘 页面 | 📝 内容 | |:---:|:---| -| [🎯 项目介绍](./dock/00-项目介绍/) | 什么是 FutureOSS、架构设计、核心概念 | -| [🚀 快速开始](./dock/01-快速开始/) | 安装、配置、第一次运行 | -| [🔌 插件开发](./dock/02-插件开发/) | 编写你的第一个插件、事件系统 | -| [📄 插件文档](./dock/03-插件文档/) | http-api、ws-api、file 插件详解 | -| [📦 包管理](./dock/04-包管理/) | 安装/卸载/搜索/发布插件 | -| [⚙️ 配置参考](./dock/05-配置参考/) | 配置参数详解 | -| [🚢 部署运维](./dock/06-部署运维/) | 本地运行、Docker、生产环境 | -| [🌟 社区与贡献](./dock/07-社区与贡献/) | 贡献指南、行为准则 | +| [🎯 项目介绍](https://gitee.com/starlight-apk/feature-oss/wikis/项目介绍) | 什么是 FutureOSS、架构设计、核心概念 | +| [🚀 快速开始](https://gitee.com/starlight-apk/feature-oss/wikis/快速开始) | 安装、配置、第一次运行 | +| [🔌 插件开发](https://gitee.com/starlight-apk/feature-oss/wikis/插件开发) | 编写你的第一个插件、事件系统 | +| [📄 插件文档](https://gitee.com/starlight-apk/feature-oss/wikis/插件文档) | http-api、ws-api、file 插件详解 | +| [📦 包管理](https://gitee.com/starlight-apk/feature-oss/wikis/包管理) | 安装/卸载/搜索/发布插件 | +| [⚙️ 配置参考](https://gitee.com/starlight-apk/feature-oss/wikis/配置参考) | 配置参数详解 | +| [🚢 部署运维](https://gitee.com/starlight-apk/feature-oss/wikis/部署运维) | 本地运行、Docker、生产环境 | +| [🌟 社区与贡献](https://gitee.com/starlight-apk/feature-oss/wikis/社区与贡献) | 贡献指南、行为准则 |
diff --git a/oss/shared/__init__.py b/oss/shared/__init__.py new file mode 100644 index 0000000..f2c7d55 --- /dev/null +++ b/oss/shared/__init__.py @@ -0,0 +1,4 @@ +"""共享工具模块""" +from .router import BaseRoute, BaseRouter, match_path, extract_path_params + +__all__ = ["BaseRoute", "BaseRouter", "match_path", "extract_path_params"] diff --git a/oss/shared/__pycache__/router.cpython-313.pyc b/oss/shared/__pycache__/router.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f20d836e386ed4a44993c88b6e92af3daea007df GIT binary patch literal 5604 zcmc&&Z*UvM72ngHq_ZSPvLz>u<2Z_KV#|b>1T%n=KvO3nV`2loC}Uz%^&%;@7+F&H zB>okqE)Z%n20IWF5i*VV03(+`obn+CLWYm+H@li(<)$;uOf4CIs)I9=Z*AYZlTNaU z2beO`ow>KWZ{O~|eS7=c_jb9w+(aNfwJ9AuYbE5LSSchvUs!$-3KK*iln9KVdyMJO zQC$a1*$$3!PEt+kiNMwnfxBO)_Mmz}-)9gE@EQAzZ3cm_AFX5K;L2H{%4;`Sr zOldbMBbi_%vo?~s-{LBdSA$sg!&kY9sYRUA#maoU z7z&BULjjq8JQ@o|!eU5f_k~~ZVS6DN@`dFcP?#Wngfg%!oxo65&{0lc`}6__AE*y% z85FgQf|2t5ysJzu`=uxax+C#eU=bCMR|3f^^y%68&quQ7zgW2RrEC}s#0DaMnTv|C z0ogbphW()c^>O*RF+)SK@GUn3nIJv35aep=afTSJd~YakQbPL4B-7<$X&E$9+%#$i zGReuD6bPMI#g=d~CnVVsk)&2>K%@bGD@E1mj_3d~^yhPpQ`%fJL%D>D~o>;lukfMIbk00Wx5-oc1J9>TK4>-|k!4CPy_ zUhjz@m13b_I1r9Nv(fAIM|@CVr5L4X4~o;uRbHVd*ryp9 zL~u}Ldv*aAwuf=|5F*01^uzz}A-kcZ)L0Mc);0_JdVV#HhpZc>N zouEf$!<>if#=@#3=n9(PkA_L~5z>ohP?Co&w3qO-KJF_Z{}{;QKc~v;1cRXGNTHXV z9C{EA0$3?})T1vMLmO8a1KORWpY<4iq>dop2`9A2=TTPYevS}7@?b6;$BdLInN8EU zgjdu6daJwDizh}&Jq-nfffY37SJ3~H^xF*j^=q2J7)8xo#`r^w0u9-#uVqI+Up)Dy z$&@>NF*o{qx9J||!k@1$eDJBtcRO3fXs|Ud1*qiQySJnUg4wZG0N}na*y;}YohrMe zL+j36_d%z{p|ddG!iA4k^m8K?-MLR|ltxYjLjjX%AMKaiCO{2m{)D)lD!k=VA7rnL zIh(T|y;3?MsNpJz54fG#Pye|1-pOw-Ud~;51^U6-zt;m)beT-u%8AQQl}lXAj^~xl z4UcEvIBz;4Gwy>j)4KQ{NL?<2tOtAuMj?xaf-%_y;6P*2b3s5?HWUcUWr1*fFhIpv zKxSVIMqNCugH8xyeQym{$uiQ8Na(Z`Xy18@Su{klAYEN~~)ZP1yW9P{J;e8pa?R0y>^v>gH zYs2*owHdqP%$`$wlI)zlF>P;5RZLe*4qkKZhNhckq-xiaiP#*s41_li?*)y|3CWuG zy=miiP<`HJAMgCewqt}FW?S961=eb} zND2NHQSCIKnhWOV3-b%QZVlfGdP6&Rr``g454aGhcVS)OvYxk9{c)FEhWHeWG5_UT z>Or2L{v5RNSx!o{v${r(lc%IymtCAh;Vo=>`x05C>Uj8#-7-kE-GF zpNtjPjWvm#iCu}R;f~DK`f+#KQg_pcC)o1Ckkc?dn06md zHy_SaZB1@X*3VSkHEg=!*l_01sY8ilfJ*>J?&&9In;)KOeq?6zFVc>E!)2Mx+lF79 zigUY|?yXy7)AR#6=+a z0N}lp>ixq-KQ=yp^?kU87fz=Z&c2qvK|f1nUmwlBJ*wWyaLaxB>R11|GMfEl972Zd z*q;{94Z;1n@Ya-5jTf3-&fL47=RTV-`S4dRBtTIWVKMfYAgzF>#n7RjP`sKZ8E|UR z)t6ihg`kc|ID&eYsSdWixDd_j2fvU==;(ph-{czR-ZZN&GqQ^D#)>Egfl`6{E+|YO zv_rl+-3gFOK)4cIP#gnN0gBMMbLrw1VC61w4~86r1$%b0n@Jjd6-UwZDLs)Lc!QjyYNAlj2%tE)s!T ztvx!m z-8QJzH5D`X1EB!8sud;nQ(4c#SgR~pooKtpyA*XPcpGcz`~|eEb^aj0c?<$X*28uq zc&thuCA3wXnJRETH0KDU3vVSQJv_RC%e2A`+O@)M`GKBcuBWX{C+VD|QE`)$vpxco zJ+my$4KRm9P(;>mX0FbvR? zC=1|r_+4jLBpfhxx%3Lqs4pDo@T;d4Z*FKpL@hpk$a?iv1#CbhG#{!^h#O7>!+x*Q zE?1T8c9D%QYZ7b^9Bps}*4LcrJ>5He0Q_*p@qYH2^{xvoQ=ZGmE*<;YG1qc1-E#2n z8@H!C7mrOIn|7o(K0I#7Y^WW7`ERyHxYXBg1-IPhP=6LT4BF+gQwQV}wwBQKNH!qB z=QQOapc}D-$3Vd*dH_ouND$E}!a3!D$og133U62l_%i1Y`eO7bw$&g(R8{9xUS=M^ zDiXXQrH_ETPHw`p4F8>p(YGuabo#vsc$tALDJa?0gSW7@#Ow4;2^LvR$pK_FA*(rI zM^Nd)$OF+fDTo%PdvLW_D6ns0?KxhYw8?4?tXgQfX9*KmMkdQuwE%=8h zYhPs)nct(ni9JmFKm;!l>DNG(ScYM)le&M9mRZtr+n{H*+$2DjY bool: + """路径匹配 + + 支持: + - 精确匹配: /api/users == /api/users + - 参数匹配: /api/users/:id 匹配 /api/users/123 + - 通配符匹配: /api/:path 匹配 /api/users/123/profile + + Args: + pattern: 路由模式 (如 /api/users/:id) + path: 实际请求路径 (如 /api/users/123) + + Returns: + 是否匹配成功 + """ + if pattern == path: + return True + + if ":" not in pattern: + return False + + pattern_parts = pattern.strip("/").split("/") + path_parts = path.strip("/").split("/") + + # 如果最后一个 pattern 是 :path(通配符),允许更多路径段 + last_pattern = pattern_parts[-1] + if last_pattern.startswith(":") and len(path_parts) >= len(pattern_parts): + # 检查前面的段是否匹配 + for i, p in enumerate(pattern_parts[:-1]): + if i >= len(path_parts): + return False + if not p.startswith(":") and p != path_parts[i]: + return False + return True + + # 普通参数匹配,段数必须相同 + if len(pattern_parts) != len(path_parts): + return False + + for p, a in zip(pattern_parts, path_parts): + if not p.startswith(":") and p != a: + return False + + return True + + +def extract_path_params(pattern: str, path: str) -> dict[str, str]: + """从路径中提取参数 + + Args: + pattern: 路由模式 (如 /api/users/:id) + path: 实际请求路径 (如 /api/users/123) + + Returns: + 参数字典 (如 {"id": "123"}) + """ + params = {} + + if ":" not in pattern: + return params + + pattern_parts = pattern.strip("/").split("/") + path_parts = path.strip("/").split("/") + + for p, a in zip(pattern_parts, path_parts): + if p.startswith(":"): + param_name = p[1:] # 去掉 : + params[param_name] = a + + # 处理通配符 :path + last_pattern = pattern_parts[-1] + if last_pattern.startswith(":") and len(path_parts) > len(pattern_parts): + param_name = last_pattern[1:] + # 将剩余的路径段合并 + remaining = "/".join(path_parts[len(pattern_parts) - 1:]) + params[param_name] = remaining + + return params + + +class BaseRouter: + """路由器基类 + + 提供通用的路由注册和匹配功能,子类只需实现 handle() 方法 + """ + + def __init__(self): + self.routes: list[BaseRoute] = [] + + def add(self, method: str, path: str, handler: Callable): + """添加路由""" + self.routes.append(BaseRoute(method, path, handler)) + + def get(self, path: str, handler: Callable): + """GET 路由""" + self.add("GET", path, handler) + + def post(self, path: str, handler: Callable): + """POST 路由""" + self.add("POST", path, handler) + + def put(self, path: str, handler: Callable): + """PUT 路由""" + self.add("PUT", path, handler) + + def delete(self, path: str, handler: Callable): + """DELETE 路由""" + self.add("DELETE", path, handler) + + def find_route(self, method: str, path: str) -> Optional[tuple[BaseRoute, dict[str, str]]]: + """查找匹配的路由和路径参数 + + Args: + method: HTTP 方法 + path: 请求路径 + + Returns: + (路由, 路径参数) 或 None + """ + for route in self.routes: + if route.method == method and match_path(route.path, path): + params = extract_path_params(route.path, path) + return route, params + return None diff --git a/static/banner.svg b/static/banner.svg index 66f457c..81860ae 100644 --- a/static/banner.svg +++ b/static/banner.svg @@ -269,32 +269,32 @@ - 所有文档都在本地 dock/ 目录中 + 完整文档请访问项目 Wiki - + - 项目介绍 + Wiki/项目介绍 - 快速开始 + Wiki/快速开始 - 插件开发 + Wiki/插件开发 - 插件文档 + Wiki/插件文档 - 包管理 + Wiki/包管理 - 配置参考 + Wiki/配置参考 - 部署运维 + Wiki/部署运维 - 社区与贡献 + Wiki/社区与贡献 diff --git a/store/@{Falck}/web-toolkit/__pycache__/router.cpython-313.pyc b/store/@{Falck}/web-toolkit/__pycache__/router.cpython-313.pyc index 7faa2ea705cdc3a4b94b24c43ac358a6dc4248df..a02fe577619dd5f5d51d07a29f4b8756bb7eccfd 100644 GIT binary patch delta 753 zcmY*X&1(}u6rauR&Th7e>5|r_X=-AO#!VsBlAg4F6sk~34&6q32!u7eO;Yp4>;?o8 zL8R1^35(Q=5J3^Cc;D-l z0I{`+ZaL=y;1fOU8~-op&$r`~36)XVEBqova8W=)1Oz}7s9X@Jd=AdCPAL+LP!ZaV znbV33OY@p(EY+Kqfv{B7tU@W@(5zBnpPu@I5vSVkLQ^m&&_s$;`5;*1kf=iBE((f> z-Perk4%H7xDv-;5CLBG)k&sUAza;R`9+m?3Pa$Q`NdDx?u~{OAT8+zcC!G*z;{as2 zCb?-`UC~rNPV!lPiMKC`e*3-Xz7qpfK+{9=U!LCk)>{Am>h0GzcQLFQR;jLI_VU6q z=BNzJOyeOM5K&00ni++~Y|*f=v|6s|c|>(%p`_LHih&dXbEZ*QMU)u%D98jbM26X{ zSeSRpVZ`Aw1#%g24985Xj*RIGw=QXwg8hOS4?~9)4<9Tg>15ob&0Mxi?h|L^(amr> z+&SEplN<2kkh~p=-cN5V>>ZzKrMEoMZGT{Mu07Wo>-v=s9%awx?~`8e9Ap&99(A3U zIlWx45Pc0aSa`_JyMk#HrimjICMeK|IftY*=k*HR*Z8q1*CnSu2%EYlh7~pFS6NmUN_4?QF!Lu|#GGqQiOh5vP&;L2$l&Vj{YKRhge1u_HyPn6Zbm&QIzTK7wqWYUTP?7On+-v+ z_nB6P>LY(SuUHy2QptHMs%784IC4gxi&~kC@pj4@p=b(?WzX5Ns;1H@OI7Xenu)O* z1cyq@ZV>O2a^K)(PdN~}+*S@gdZl9nK=3?MsG-OtI!aa3+G!ouYgg6NnMBUOc899I zlhcgq7oV!Wm7=C)q|$mi1E1Zhn#jbV!J8JPXbJU$uzOY2v@|OfS2fF`sX0*ERMlld z12_aVqjF8)xtQBPd_?XFfr^V4x8=S+C-q?VRXYj3r>!}=-C07Eos@To=!My@c~i~7iOW| zrX><8v)}G%*jWdAaK-?L_sNfP$5qcI&zc;*BZvR~RN=*y-KBv;tMW6<^mw!8s4=tE z33;%!`BdN|7KNMf*Q^06ToIlnm!spyt9j1mp*^|G(Z^u)GS3dDkqu1T0HWS8gHP&~ z?QIwU-5`A5z=LvB@4VExn9CPUU!Sfk$wT{rI`Y}vCO<+KGYiG5EL#W>Q;`Xan-KlwCe$5_{KG!=7H|byTIC5ddN#J6OtCDg(*z)EoItuXoipk%$w*Xm>e)& zv=o?$$-AXMN~rFJ!G?vc@)WEhOd&yA$~0F?9j0rD>>?fl!6!x}mO|FQ_~7TokAAs% z`=?ufb#Au^OlLu7b0d=xiSEPx4irc$TT)MJR(!q@mJ!j3(`RZ!oXMh-V$tIev$d#| zocBWWsXEAR7-uFxKibi1*Ui1ldsp<^r*574X4l&A(bDkIa&Yka#cLM}ZVTaCCwHHp-`86|I;}0-&7rgL*P1zIE;uKzQ zEd?T>x_XUk8Z!0~LS|0j+KhRy1~5usyyB8VL}bi@Yia%K57saL``(9Fj@#TJdKlJz zF(TR`JbY4FsIo@NvUx*K+x&X~w8Q&{TIQLQH6L+1oH_zi96EWchoxq5jNOi}&uW{? z+MH&hdB`&;&Q1Ot0L?sec>4sP=7%7FXCD72&y{*7{?2`eQ}2)PG>%*!MpF;-5>t?O!Ox@ITa& zZxs0UeP|#*@m=WPHDQq}2Se8{T)R*>vKHJ^3hpVs@vjqi2Zn%!{ZCdT5(oi(84>Ar zP-ij{ppXX$?L&b#84aQ6M}dzL8b*QiWVfRemXBk148_wRY$1_~TNFc%;$HyzJOk(8 zAxzO56t5mSh@$bRd=$GVFh!f6gTO~+@Hc@IBNfRdK3m``1ghczR(%=z9eWkoB}Vh? zOSCYLP!yq9z6VFe3TF|DJz&)SJX>pjk*&4ABDaZwe4;|2Dx6*6Kfm}pHYJqt25+6!efSS?98;bkgxp!vUB*nQ<*v+w!x{d;Ks~Uz>G^>S-yy^IC6U|qD*j|#+Hv)p~{eQ;p&q)9P diff --git a/store/@{Falck}/web-toolkit/__pycache__/template.cpython-313.pyc b/store/@{Falck}/web-toolkit/__pycache__/template.cpython-313.pyc index d3cd6302dc3cf15cddb4a2f0f4a7ad4cd4fb8b00..0dac3c016f8be02a685fad1f8abcdd6162e9e3a3 100644 GIT binary patch delta 3079 zcmb_eeQZz;dm=brce)bZOb?z=9RLx6FiW+M5u;A!_ec{?s)+^r&la8`&4lxPCA zXoQNIg-TjT6Qi;wMU@swlS9G*=>VZtO$iCqrdgwMi$$}cl-e~r*g2w(s2p{+SY!yP zIYF;zWn5pz^(c`v7nis+cT~=!3m7`Vu$+tCT1B*cr;0hvADSoX0ZT7fdby=ftI>R4 z+SDIa_{KJ^60IOz|3f?Np*Fy^VL~e(sSeIM2ec|09HcDQd}VlcZu zF0vBwldm(`vZ!pAnzGDg%iQ(=dtGvN2^UFM7;Xj>hSC?0#rG#D-o8=Rmq_b5n&_jM zOg1bUlAcKQb33lrMKDrT)o9eS8JbwR@#yAQD$d@ubOaZHW=NRySDmy^HZC2OPC6&6 z>rT4azbzkn%0Z_Ch=NKhfKoU6NK}c3{atJ%K2|CHw84#H7Z5{^rxGz5wor7U)j)L2 zUjV&)G0Nv)R(gLis~ARjhEMt zmv0=mZKS@-F{O(T7b{rTC}u47H*15!54eN{ZFNLt5!<(j@A6gK40(JtZes5|90!Dw znKax!cUlEvGXW_}6Z1Cz*s+YPxGXtG~HtAVjxgBPk!&`_QHVNga?l z-t*kRN6C@=Gfeha&I+vC8t_NHF9iSqmx1G{))(~P(4@Kuepo3ff;Vu>Q+Lx-H?n4QZJ~3*6B$-+msbwEC>~^9p;|$}3w0k% z_CTrb5zh)5NlU#@O}#=3e42O}%4|o~V$#VDRlQh_xnrQazXQ@Hvb0>q%B!Cy3hNgA zEK^-A2Lz!UGL1u+V#1Qz()z_umfI+zKwg z8C*X4Y++z5cm2?NM<#+>$1Ao@`YQSLT!gOLpNb9W7e$)Gy5l|!41O1p4YW<;PKeuWG>A!}A+un`C*3TuIJ#f#@ z{u8Q-{2w3+vo~t?l-pnf4wD!9$^)1jtYN{0JT$DdvP$T&b&_3yRiu-ot-W%OW9QsI z3b@XVU2A*n^8s7|AGWl-9q@4qs?f_DiU}t@242%xPbQ|aiRl3pbR$V2sTbge!cIEK z+G|6dh^HYZq21{82+k#(8)d0XX0UIz)9|-xz9J5_6YLOb92{=;R&9sEK&rpS{!-hY zzg-bHUw(GGi607nW>-KQz3Z15RHCKZ-^2Xmsgu z)StrOUqtpW2#p#<7QeKe>49WA(bP9c*>7s?RovqN$~zEcw>@-EWuGjpYDQq-TLU*K z@IJ!-NxH(8mz+&2;rQm4dm8^?;J*nUNw|sy7HxPo15-mxrnA(8uKYg(`yG8AiG&2V zq)U+SXVeNaLu}W2=o2`TzM09S_?MSIFp9-V53_d{)#foLvm4=4qDZiY^dQvFtHQ_9 zf*DDY7o68h0GIkM6&?&|Hsgy2s@u zhf@xYfDy3K=6lFWY@NC`KeKwV92oVL1Ym{i*rw~gv8OJ5n=fy~IUE1RbN5j@vzfT% z&17U#NkCqxH~E-u@*Bj!sa*uHu&voDuQw9yM}>Eb%iZVTB$J`*_OX8#2nrRHn^ delta 2522 zcmbtVZ%i9U7T;O#uGcok_JV;p1dM^Sn^K$rfpB*X2^5nwsZ(X~DY{xXuCstsW24z6 zx=WhCRc#MN(KXjmu2H47Io%hK`hga;qH3kIs&u-KaQRSf`=wtlQlzWoG_<{ws_Nbw zLvx|+r;fD0H~Ze3H}n3?n@>hQ8?3(P_j?5xAKdvz{x`LMs{ZS&g1<2CeG70+NDGu0 z0u>EHU4}>{qrq^cNSA9QWN3+xHZ{na9fpv4jdTqlM&-(6%=cOw?*6jK?R1Iv4 zpiLS8q<(hS6(vEYNxO;0`lYwp{m8BYaO7OU%u-#XxQI3YY@UV&m{;CSYS})y16Ga6 z4|ye!a(5H#WqDxN{>@Od2|AfUW4Bk{x&0=)z3`XYuis?f$Pcx0o4e33X3bG;b85Pf zwM`2xMPa}uhn1Luepsz6uSC^bfxcC>k7~<*R+5BNv46RHm5LeGqP8pCpB8ppjge+n zQlBH*^1s!)WZz!2?-~AW&YZUMrJ^oX{OJUCWpBx*<^%gy`}UBI<$m7{S@(g~BB8xN zbjcAr_d4VpJ%mef=gq^;&B0;C&;U8g21D0+aScrXz*Zwb9FqIdC8%~3;R%4_%&&EX zkHIXVhaGR;$`>s=Tg;iXn^rT63@%4&#!0t^I;{XX)Y%$%-FVI}!(>dnCZxn3Vh|{Jwdi8ONVMA!Pwm^kxunQz zcx&x_nvzDvt#pG9ds7P6ZR1H$2Cf;6T@8nqFW2oMB+1^b-ycC|=(7k|0u~+z6OX}^ z8WLoT9c##i@#yFM=?m!#4|nbv(9;*w7j=oPH?()k75~vKZh%h1%u%Rm%@piSYp1b} zP$9-T*i#vQSfu zIg!II`K0_xu$Fc0tk1%INr`QO;et>}Nty%2J9vla830K2IL;>!9z%vgi$EMtu2eje z$Ev0sDB}^uU{X8KvfP1+}9C^Huov;>N zl%fd>z)>DQF?#s;aeaHR9rtwBwoO{3&!J7s9hBZyv~9N9*gTH;uyzCdcTV_RJ2db9 zT9Q2-8`VNkd->eub5|!84t&^kv;Y16wZQPII{b5E{f7Yh%j4{Y4#(sC@hn86sO%L+ z)nRg~ZZ8=oMR!Ub^_;~1yKS86+p6P7p$Qh{C!>Pk6QV*&hNQ@qq|o!Q=nfV@B{Om* zGn2?jBAi8t0q9=LQ9C3l54|l@%ubn(r;xX7KIQpxWE5HS9J|!q5V@Z>d}%RnICyV) zuX#}UJus~o*muq2>GfdU^|ntV@wG^N$$cx5SPLdzQSQpZjwUGN+Q|HqJe2ej9%T;T z2ic>~qv#62_RP(|dk|*d?o?CbC3+DRzE9u@OrYNr*=V|-z#|_H-aPvL(X~KgRZZ|+ z;km$m&mXU(PR{!}TN%kSSI)ok2-BlYxe5=W`h-OqzdY2HuiPW>Kibf;R!YyVU&@pbDS$1;9(f zKc&XIl1py9ZhR#G`O+)OzbvZ~GByMtQ))-oC_t|K7TR-0~iuoFF%b$^z1*whCS8-r)b`NR2!~ zZWy>?ge>(`=*rMH{J-J^o?rJ+2BdODd2sM1PO@LOMkBAHfcJWI*HM2pRXQ_MFb~q# S*wxlp=!i&0$d>{qT Optional[Any]: """处理请求""" method = request.get("method", "GET") path = request.get("path", "/") - - for route in self.routes: - if route.method == method and self._match(route.path, path): - return route.handler(request) + + result = self.find_route(method, path) + if result: + route, params = result + # 将路径参数注入到请求中 + request["path_params"] = params + return route.handler(request) return None - - def _match(self, pattern: str, path: str) -> bool: - """路径匹配""" - if pattern == path: - return True - if ":" in pattern: - pattern_parts = pattern.strip("/").split("/") - path_parts = path.strip("/").split("/") - if len(pattern_parts) != len(path_parts): - return False - for p, a in zip(pattern_parts, path_parts): - if not p.startswith(":") and p != a: - return False - return True - return False diff --git a/store/@{Falck}/web-toolkit/template.py b/store/@{Falck}/web-toolkit/template.py index 4b126f8..55f2a39 100644 --- a/store/@{Falck}/web-toolkit/template.py +++ b/store/@{Falck}/web-toolkit/template.py @@ -8,9 +8,10 @@ from typing import Any, Optional class TemplateEngine: """简单模板引擎""" - def __init__(self, root: str = "./templates"): + def __init__(self, root: str = "./templates", max_depth: int = 10): self.root = root self._cache: dict[str, str] = {} + self.max_depth = max_depth self._ensure_root() def _ensure_root(self): @@ -26,7 +27,7 @@ class TemplateEngine: def render(self, name: str, context: dict[str, Any]) -> str: """渲染模板""" template = self._load_template(name) - return self._render_template(template, context) + return self._render_template(template, context, depth=0) def _load_template(self, name: str) -> str: """加载模板""" @@ -88,8 +89,22 @@ class TemplateEngine: self._validate_ast(node.slice, allowed_names)) return False - def _render_template(self, template: str, context: dict[str, Any]) -> str: - """渲染模板内容""" + def _render_template(self, template: str, context: dict[str, Any], depth: int = 0) -> str: + """渲染模板内容 + + Args: + template: 模板内容 + context: 上下文变量 + depth: 当前递归深度 + + Raises: + RecursionError: 当嵌套深度超过 max_depth 时 + """ + if depth > self.max_depth: + raise RecursionError( + f"模板嵌套深度超过限制 ({self.max_depth}),可能存在无限递归" + ) + # 替换 {{ variable }} def replace_var(match): var_name = match.group(1).strip() @@ -102,14 +117,14 @@ class TemplateEngine: result = re.sub(r'\{\{(.*?)\}\}', replace_var, template) # 处理 {% if condition %} ... {% endif %} - result = self._process_if(result, context) + result = self._process_if(result, context, depth) # 处理 {% for item in list %} ... {% endfor %} - result = self._process_for(result, context) + result = self._process_for(result, context, depth) return result - def _process_if(self, template: str, context: dict) -> str: + def _process_if(self, template: str, context: dict, depth: int = 0) -> str: """处理 if 条件""" pattern = r'\{%\s*if\s+(.*?)\s*%\}(.*?){%\s*endif\s*%\}' @@ -118,11 +133,14 @@ class TemplateEngine: content = match.group(2) # 安全条件评估 value = self._safe_eval(condition, context) - return content if value else "" + if value: + # 递归处理嵌套内容,深度+1 + return self._render_template(content, context, depth + 1) + return "" return re.sub(pattern, replace_if, template, flags=re.DOTALL) - def _process_for(self, template: str, context: dict) -> str: + def _process_for(self, template: str, context: dict, depth: int = 0) -> str: """处理 for 循环""" pattern = r'\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}(.*?){%\s*endfor\s*%\}' @@ -138,7 +156,8 @@ class TemplateEngine: result = "" for item in items: loop_context = {**context, item_name: item} - result += self._render_template(content, loop_context) + # 递归处理嵌套内容,深度+1 + result += self._render_template(content, loop_context, depth + 1) return result return re.sub(pattern, replace_for, template, flags=re.DOTALL) diff --git a/store/@{FutureOSS}/http-api/__pycache__/router.cpython-313.pyc b/store/@{FutureOSS}/http-api/__pycache__/router.cpython-313.pyc index ee0b2dc386772ca43f177e7261a59b7e3e34eeff..0de4be3ce582d64c5083b5ea987b6ea71b5d7c0c 100644 GIT binary patch literal 1204 zcmZ8g-D@0G6uLxhS(8d~2fh}Nf?r{If!LnJ=57qJCd+q?x<@zry8CN+8<&OKkhGw1%!o!;PJ zk)Q-;_QPvBA-^l7dsPFe=J77R;IjR?P+*ebcuVoSF-6 ztpJ4zIYp|(w@(w_S+-`3tREEDT(6M6gx<5=2TyPBbicY^Iafiu`_s+ttsf6Q`KOE3o*HSJEECZ1uDY**;p<@f#pnB`dmCKdc%WtdOWH*xz;+g3HMzj4qd$9Y*&d1?xM11(-^(q5Mp!#a1x|oQ{#UzcJ)f>1T zn~OlCT$(FMbKSFLaW#ya6x69m5l8DUQ~rDsW#vuG6t z(UHg{Pa`3%yh(=Oiq47>d6tU=*wpLSFQ!O3yX5;frdmRD&IO%tstG!d5T&aja1n(uixW&cls1zFrQl+pJ{wR*OP-`uF&j2$AOwUf$S23DF;z9H z6fT~C+F@U*SISjrgFG_Jho)=IYj@WUi8}93{M|n@oPsQRB^bw3+G4#E#%rM74X7t4 zhZD{xc`E?coXofc#XQ*ka?aoyF74l0`vd%UDCxeIUH$@i2vQG@tBONI)3itA<;Udg VV=|5UPrIbeYn$)=Mbz1!?jI!KIEerN literal 3868 zcmb_fZ%kX)6~FJV9~dyf{D&?sqydM3(}ZjZ%i6V+Y@&grym&!^>B+&b#+k9*`<|C& z4OKOD5or^JOhIwhO0l$jFq*UyZBi0#+V)|ZHtoYoEr#>HtkOnXzBwgQK5pmS_iO_q z(xl}I&b{|{|Gab0{hf1#^E36x(x{xtex1tI^!PO*qiW8)Mw7KlnHQ8|@=og3yU zKP*teM^r%-Ul)fZDh2#`SLHsbW4xUbk(V1Hv5h<8c8vHglWU*CMCBH#42t5q8S z63`R1D{D0hMuW}bp&1=EtJJiaL^u`0c9o{Rl?uh25s#+58Ks69i^lbM0)}0h7EVk- zLoiHA(Pq>O!fMbo!wi|xDJ^80GU3%n*{H;7B*q1ag91!;HM zC=jT!ovgaAJi(s57MJZzl{+x96IEIXMY7097FC{f0bzjRKp3Tjh^oSnicLrbLn=2R zl?-ujLfj0gQmZDb)#}Nbeo?Jqh)4A}->aUih40lebeFn|)x$}w+Pq&#VKn~E&}oc? z5Em^0+M|mc?E-jFU{_HN1O|Qp9Ny78qMIeXq1_CF8+bljZ{@Nw-;fqpl)cYRS-jj+ zC)JX+B6db;55ROkh^-2W$pnOEw_{oCwBwwuDmJF;SCp=2ry|<(RIsF>0PUwLC?r#5 z3qsomYiR%l2AF4Zy%-^18#*y`dZ>8AvfX{EQgT?2>85T~J9e|Xdtje!mTOM;uPA<| zs^y~2Lnu=nVtst7^rXPL5;Y6Zd79sO$#=g`NSqJyW1H8)3M@fxCv;$(kTA?1fw&8V zFL|lGhOOhrijQy^#NBp4i^&2$msr@&CtzoO4CBnU66A{hZ}2{{hq#H4Odvm5!L&sc zj`DkmUu2QN&Mm(D_}=Gte-ga`uKDA}w>xx76SQN%*D;zfea922c(`L05)K(3os?mT zXA|KIewjrP_R3kZHWM9w)c@l8lQhGliKFA@rnPHWzVahD6cxqfCL2|`uNUl{xettQjmq+6Q+}!`OUM$Cbi2BY>z|$k}+yU#7-wR3; zXn$6MVsXa6??8JPhX5-E<(;^r^2NDDuHpO<$P&lHK?!L*aNgk`d<*wMb_E5Mjm2F- z*Z;U{3n4pcxCtwri)jsKjBq=RZo^ zB*?C!NvM*ssA*N`@zjh?LlA0&m>#z(f%Vih&PUB@zhV=52rR+4ZdE%h(vmpGs>H0< zX>(DFOIlU2kYNJCxMpDH2=5dtb`J3hASM3WUW9?x_!|h|zq{^(m-F7Hyt`(;>w}l_ z?VYzrZjEF+bM1Yr?fuK`{Yz@D{pj3CzU8^wt+!e;gCFi*ZRuNX>B}0qmP2#HK>e1E zOz%IM4_p`LxxBajBOyKfp(p3Q8#aJ8vB*VLVTz3R0&z;D7+EzV#mpywk#;T`h+0*kIVQKi+o<7{p)m^%>6H?^Uk zdFQ{C^E`h?&9?qR%X#|29X|u@J@w4zXa^i>)bFK@P}3$9%_x8kgtnrO2jCdlHyBBpF>g3g)#f3C@>*3G9WM)_I@RD(%yp1 zOGnd?2}6}pS+#WJG4=`yFLk8_gt{`*2z4RUo%SNso%s<$-A|ddFMSAS?aMaftbGMV zk_NeSq(HFFc0Xoyp{7M@NKX|AR9V;+s-^lTSlP;>%JAEPnZ}aM3(06aVjD(;5eUaR z{UL Response: """处理请求""" - for route in self.routes: - if route.method == request.method and self._match(route.path, request.path): - return route.handler(request) + result = self.find_route(request.method, request.path) + if result: + route, params = result + # 将路径参数注入到请求中 + request.path_params = params + return route.handler(request) return Response(status=404, body='{"error": "Not Found"}') - - def _match(self, pattern: str, path: str) -> bool: - """路径匹配""" - if pattern == path: - return True - if ":" in pattern: - pattern_parts = pattern.strip("/").split("/") - path_parts = path.strip("/").split("/") - - # 检查前缀是否匹配 - for i, p in enumerate(pattern_parts): - if i >= len(path_parts): - return False - if not p.startswith(":") and p != path_parts[i]: - return False - - # 如果最后一个 pattern 是 :path(通配符),允许更多路径段 - last_pattern = pattern_parts[-1] - if last_pattern.startswith(":") and len(path_parts) >= len(pattern_parts): - return True - - # 否则必须精确匹配段数 - if len(pattern_parts) != len(path_parts): - return False - - return True - return False diff --git a/store/@{FutureOSS}/http-tcp/__pycache__/router.cpython-313.pyc b/store/@{FutureOSS}/http-tcp/__pycache__/router.cpython-313.pyc index 33a8bc10c7c2afe62e79176296c5d115ef9b7cc1..50c6c62b8cd81abb78aaddcb32fa4c9aec7b9c7f 100644 GIT binary patch delta 735 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 WBJj-a9|?#jZ7(uiKM@f2-v0x#A=q{R diff --git a/store/@{FutureOSS}/http-tcp/__pycache__/server.cpython-313.pyc b/store/@{FutureOSS}/http-tcp/__pycache__/server.cpython-313.pyc index 7a2d158607980cf68c52d0c13a51ab23fa2b91f5..6d65f99a5242ad04fbb3618a0e1cc88b4e27b968 100644 GIT binary patch delta 2204 zcmbtVZA?>F7(VCrlR{hQ2bEr+x25GvYOGQfEJGA<2$ZGQt75mYN-xk^FU_Us;Ky(u z&c*#$^~{Oc4>4{NGqYeYku6(d_F=Y}B{bbay)g?}mL>bKgnj&(`?GTjx0q$gzV46b zJnws+^Sz(EAH z7WBYrL2nn^925Y1Vc&O$s=Rst1d3H~)J_~@FN}POwG?7p77MHJLlPJZlpMt>Ii(Vu zE3@okRjiuTG*NJj8c0*jwh%Y&_@*l25?GN@5L%X z8@A@;7&uDSaau3#pHOh<^=1hO)id;av)TO(zr)I7VFGKT#+0vGN0SCqlAebtzYGGE$eHj zVx7w@?8q}lW*lSCtgFfS^m(6wgQ2>d&mjA9 z@$Bd+i?hwX9!+L{Qz^dT>c>IOz!sy&Oz#C7D}VruJue2YzTMEn*dVPD=d%KiKE z>o@?b6pU@g;LX)_2o8Z3h0Xznpz|NKrS*Td1>LsTpc!Q?-uB=F)p&BqyF=25d?Fqh zvA?EOKB0jaFpII$PAx(bo> zUo6HTDMfzpxTGA42~kNMYGINx9us)UaAY(( zF_23zkQg14^f?=s#K(^FQK>-Wk40iaOc<6_gCpa@QE>o$Z>>Vhv>nlrYQuD>; zHv&uamNdO(aclb$-I1X??t3@SS#H^H*cbLp-7d-a_S|ku`#P6=J!xN0#uv$Trdu~(Oy(`s=Q*ZEOZ2slSjhV_owsu>(wsX2G z>#o1reYtzd-I#Va-eean3lkZCSH`_>y5pOw+AQPAmb)(YO?PI?>#nnN)_2T@)8*Tz zdspSvtl6tT~ulZ*`tXqu^l@6CU}oTgwJwO4%$FQFRuPAH*4_jPNWCiH||!zhMxgJu?(O71Ebd4e*gdg delta 1290 zcmaKrO>7%Q6vtp1NCBW%5)#-NhY&{ilaZG<3>6n6zBs={J#l2x3w z8E+y55FkJ#2T*~B6b^ve0}=-=k@>2G#H~3X1fmC`m5>m(9*`p7LQc%Y+TbE!4!`$i z-h2OfZ?wC8>CeX#zsKV-L|60n56*OEJJI5lvz4XRie2#>cfIn6-C6cp?aEB8R_VUT zeG%tg55a?jMig5E2$^`611pt+H;<>_>Y$v(2+iDnvRIBIl>Ph;Qm4y zzCC&*9x_8Eb|YO@7e;giz8i?*G3sUD=um2Gju!XUTq5vHT%%?8;hHePkJC%o$|3kZ zD%EFGy=_0uP>|#bCYn9gAM5$1aF`*|Ddd025QB^y+@;G0O_qWthx?jTf?5-sVoii| z>603sX0L;0b3yEHOK&T{)wGQ7hPB~`M?^gMcnXYxcX_&F5m@pj@Qrv&i~JJ zf$o8Y_tUZe8dd4*?T%65x+LQm+>pvJCZAHOWSVLjX>ytxGDzWvh+SWU&*XI+fr=t? znqc^UmYl-?BgxO*U*DayO)%NX(4_xHYmv~A=H_q$Sy~)R>vi!~XWN1p_ zcZLKS*pUQ!w3FqTuTB;oMcw3x!>Rkis<%)+?T3loZd!HQz`ke`(juLGE*^t7)a9fv zHtc$6A3J`i z>A1GfFWc+Bux2$^Z9h?Oxt{HM^G(}b^p*&L9Yw`gU|Y-MweBsgigCSL$Y;1COpg$T zCR{9@z?;x1UU*`K9z4fAlq1V5vP5Bo$@9#&D14sE`hw%xD;;07+HKoykQZ1$v|Top z5hd*3BXx!ihTb)h9L=k*u>GqPohU^vSVUsy^H3cv;t#uIbc4g6!@m9t2Kedt8T>V5 zN}n9Na*)IDF2feXd*GLJYy&p3$$N!M_}b+^5VLzvV<;O>;Z@i)8u$bF)fi7Ma`?n= Jh>>Jy?O#uX3X=c; diff --git a/store/@{FutureOSS}/http-tcp/router.py b/store/@{FutureOSS}/http-tcp/router.py index 6f4b66c..31d9581 100644 --- a/store/@{FutureOSS}/http-tcp/router.py +++ b/store/@{FutureOSS}/http-tcp/router.py @@ -1,63 +1,21 @@ """TCP HTTP 路由器""" from typing import Callable, Optional, Any +from oss.shared.router import BaseRouter, match_path -class TcpRoute: - """TCP HTTP 路由""" - def __init__(self, method: str, path: str, handler: Callable): - self.method = method - self.path = path - self.handler = handler - - -class TcpRouter: +class TcpRouter(BaseRouter): """TCP HTTP 路由器""" - def __init__(self): - self.routes: list[TcpRoute] = [] - - def add(self, method: str, path: str, handler: Callable): - """添加路由""" - self.routes.append(TcpRoute(method, path, handler)) - - def get(self, path: str, handler: Callable): - """GET 路由""" - self.add("GET", path, handler) - - def post(self, path: str, handler: Callable): - """POST 路由""" - self.add("POST", path, handler) - - def put(self, path: str, handler: Callable): - """PUT 路由""" - self.add("PUT", path, handler) - - def delete(self, path: str, handler: Callable): - """DELETE 路由""" - self.add("DELETE", path, handler) - def handle(self, request: dict) -> dict: """处理请求""" method = request.get("method", "GET") path = request.get("path", "/") - - for route in self.routes: - if route.method == method and self._match(route.path, path): - return route.handler(request) + + result = self.find_route(method, path) + if result: + route, params = result + # 将路径参数注入到请求中 + request["path_params"] = params + return route.handler(request) return {"status": 404, "headers": {}, "body": "Not Found"} - - def _match(self, pattern: str, path: str) -> bool: - """路径匹配""" - if pattern == path: - return True - if ":" in pattern: - pattern_parts = pattern.strip("/").split("/") - path_parts = path.strip("/").split("/") - if len(pattern_parts) != len(path_parts): - return False - for p, a in zip(pattern_parts, path_parts): - if not p.startswith(":") and p != a: - return False - return True - return False diff --git a/store/@{FutureOSS}/http-tcp/server.py b/store/@{FutureOSS}/http-tcp/server.py index 1d16515..7598762 100644 --- a/store/@{FutureOSS}/http-tcp/server.py +++ b/store/@{FutureOSS}/http-tcp/server.py @@ -77,8 +77,35 @@ class TcpHttpServer: break buffer += data - # 检查 HTTP 请求是否完整 + # 检查 HTTP 请求头是否完整 if b"\r\n\r\n" in buffer: + # 先解析请求头以获取 Content-Length + header_end = buffer.find(b"\r\n\r\n") + header_text = buffer[:header_end].decode("utf-8", errors="replace") + + # 从请求头中提取 Content-Length + content_length = 0 + for line in header_text.split("\r\n")[1:]: + if line.lower().startswith("content-length:"): + content_length = int(line.split(":", 1)[1].strip()) + break + + # 计算 body 起始位置 + body_start_pos = header_end + 4 # \r\n\r\n + body_received = len(buffer) - body_start_pos + + # 等待完整 body + if body_received < content_length: + # 继续接收剩余数据 + while body_received < content_length: + remaining = content_length - body_received + chunk = client.conn.recv(min(4096, remaining)) + if not chunk: + break + buffer += chunk + body_received += len(chunk) + + # 现在解析完整请求 request = self._parse_request(buffer) if request: # 触发请求事件 diff --git a/store/@{FutureOSS}/i18n/__init__.py b/store/@{FutureOSS}/i18n/__init__.py new file mode 100644 index 0000000..aa29a23 --- /dev/null +++ b/store/@{FutureOSS}/i18n/__init__.py @@ -0,0 +1 @@ +"""i18n 国际化多语言支持插件""" diff --git a/store/@{FutureOSS}/i18n/__pycache__/i18n.cpython-313.pyc b/store/@{FutureOSS}/i18n/__pycache__/i18n.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5160e736024b9706d12c7b1470b933624e66bbfe GIT binary patch literal 7380 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 literal 0 HcmV?d00001 diff --git a/store/@{FutureOSS}/i18n/__pycache__/middleware.cpython-313.pyc b/store/@{FutureOSS}/i18n/__pycache__/middleware.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6606d8ace22570bdef677eed59d0f2a866895f21 GIT binary patch literal 3994 zcmbVPU2qfE6~3!ot=20U8G|IpvWc+yv9K+hVrFns0^zTeAJckK>W)3t2ze30^2)ip zN-?fy+_XsqgkpwJ+tW<#6jDT{)J~fwZs-G#eMF-)Qn%BTnOeqv6E&T@^r7eOYK^T3 z(&>FTd+#~-|9tm5XP3&$c?6|xLppNFiqJnv!zfIlva}Z}*O7oQ5-7oRl)@Bz>7%A2 zG^UR*nDG!jbJTo<#jFQap*kd(tC7I&H0d*7OW5SK#MeP9vVE82IehZucRX{mcNWJ! zn!Ee)BI(rOHL2W*0VNWXf>D**AB;wWr=p^lQn^l19*9Y@7}6)xC0l4L4M62O>Ort4 zgiQj4X~BdU!7ea8<~9@A$64kqg;{~^F$Yxy(0ir6T+^2eYw(&d?X6JPd>amOJkr}66@M7S;-UqH$8Ukq4!C?ao?m$DgWSdB z{Ed+{!VJbR)+DZKmKIsO|y&;D4fFCu!Pu~0B765S4; z=jBjH98j8%L9ci)EP8S`KjPbbp2fT43%|Xo?}6`7*-%V69SMhM*tAiI$4Pr>6vTDZ zRS`8T$Zy(BH56LC;8zc7MV%|h7Hk9|hTr`tT0j8$-^bGevts=63Ss0mcc|8ocHV## z>=#v5KecLoH7?@8z(5cO`&EWqj+aqcQ3^*SQKiG8f&tkGmxJ)qDkF>0)7VZlWguj- zg2&@gH_J*4i!FPH4#kx?7EcJma0_u}OaHQ$eFK9k7YIb8h!O~>uGJ9m8HP$SYdeS$ zly%m;ZOzuyO&*&%m2o$x+|9}U*{-zvP=fi4t133Ul6D_RFn{5ymS8YaEm(wu$%<-` z#B)mEw4}0Fgsn-dTtKUe;16on?HRx`lil0JRz5r#6!7|&m=+oyWl7w$^SEd(7F1+B$y_?KkyPW&<9q2dz$=%$=DPS#05v3xEQq9^s zRss2E#9*(8<@5F;Dm!lv>fA;iIpMYrc+dr1z|g6@&IhEuOc>*?Lh#Zk*B`AeNOdb=i0paEF zT2gls;UZ!K5ziA*ON6#P4j00m%z+MwNh_E*S7pxxB?vgGqlkHQHJMWW4~P+zuSCx3 zjH4;#Xi9!>y8Cwbtm|G~y7^$*aVWv(S>$qOoDC^wL((EsZ3Y5WvX|js(0OQKa#E9SW5lped+2GOEl&FE|sw7IaKK!b6<65)^1C! z-S$PxOA{yV9mu*iW~=J+7F5}oM@%K3VDoEGS@{*~1#5=ekm5FszxElo1s1NYALUZq zb6J;rZ0PFH*zncigk_YzV0*-(_3mr!3GOkAY|ha$H@S?}o3eVdJK8h0nv|{Per-$2 z*80HiO4zZ3T$D}sqO9PE6b>!NATSYcXmnW#ga>r_B&I<-Otm7(1oTycLV;uo+LGi& z8`gE`@E(LDh+=;FMOci)%Hu#6e!Ap1Mlc)CISxGs4*Jcd=+~fxu1(LflE;4hWrVEA z1M#-g0?ncVghXIfRE)g?O|bDzV4S=AvqHvkXYAo`emyt)yMds!X-Ug)i|TC|o`vmUkSK5A$fE@?U+ed!d^wgBU^cc6WB23lLSgKEY-k8#9iEl%pZJF70T}ICi8Q zJ7)HO?%0($n6+1qikE+st*x8vp6E`lO9s=mtylR`I=iLuR^NMlH~L4}vB9f@*M`y+ z^^Yvb*`DW7h4ae!3+Kl-WvaKOs<)->jrX}m4RzWhUj;Ve01a%r;D!j;N&<^|irys9 z1~iN>fYW5KelN@!W&p4>xSFg|Q~+4SFHj1QWFW3Ft02jg9LEY4V?7O$ulU(#fv#)k zv#hHiNtZf*=@@D39U#fY2|EHLjb!($SX5a(4<<=1$%nBUMgTxx#LZB_J`i3;M`9~c zY$t-maIZzjB;jYZypUY#(MPrDO&OD14!03Oo}o;tW$@R;px8HeX|Ys_N9H_N>?3Dlz(Bb0oXnO&|0$2`i#??a(a{XpF4et zLjnD3AdXn|YSJSoIHKcBBx;6CP(2e0y%VgO^*)Oc=icNr2R|`R^>&noD zq4BniyD{Z%Oxv68b4?mjy|kVYYe}${7E>KwJ4tOxUJa=&9|-ivdgD=2w*??32BQUw zJrFn@!LkyC+**o(*%Ao!#zIhGdLtnPlYczS5TV6A;(nC|BESTp+EdoA%Ml1bd{iQ# zKu`giI2Bh!IS{yw^p`}U9VYjz2~v@N0OC#b$i$hsN9$?xL263*HzMCWztvnd-kV48 zNp@+U*@nlYmG7i%=BA{aNAQ_BcaQ#X_%W&GYi#D`sgp$AoIEol-|qjCRQ09FVi2kK z!AE7z$}z~gmB9fBBl-!*(bS5VEc*tcarnXTfl-w8!)hBo1Rb6nLgl`xU*t>p2pGtN klnI*iG(}N=MbG^WS^t4H{~c}qk`*Yb`CsrOP3ar?FA&hV<^TWy literal 0 HcmV?d00001 diff --git a/store/@{FutureOSS}/i18n/i18n.py b/store/@{FutureOSS}/i18n/i18n.py new file mode 100644 index 0000000..befbaa5 --- /dev/null +++ b/store/@{FutureOSS}/i18n/i18n.py @@ -0,0 +1,156 @@ +"""i18n 核心引擎""" +import json +import re +from pathlib import Path +from typing import Any, Optional + + +class I18nEngine: + """国际化引擎""" + + def __init__(self): + self._translations: dict[str, dict[str, Any]] = {} # {locale: {key: value}} + self._current_locale: str = "zh-CN" + self._fallback_locale: str = "en-US" + self._supported_locales: list[str] = [] + self._locales_dir: str = "" + + def load_locales(self, locales_dir: str, locales: list[str]): + """加载语言文件 + + Args: + locales_dir: 语言文件目录路径 + locales: 支持的语言列表 + """ + self._locales_dir = locales_dir + self._supported_locales = locales + locales_path = Path(locales_dir) + + if not locales_path.exists(): + locales_path.mkdir(parents=True, exist_ok=True) + return + + for locale in locales: + locale_file = locales_path / f"{locale}.json" + if locale_file.exists(): + try: + content = locale_file.read_text(encoding="utf-8") + self._translations[locale] = json.loads(content) + except (json.JSONDecodeError, Exception) as e: + print(f"[i18n] 加载语言文件失败 {locale_file}: {e}") + self._translations[locale] = {} + + def set_locale(self, locale: str): + """设置当前语言""" + if locale in self._supported_locales: + self._current_locale = locale + + def get_locale(self) -> str: + """获取当前语言""" + return self._current_locale + + def set_fallback(self, locale: str): + """设置回退语言""" + self._fallback_locale = locale + + def t(self, key: str, locale: Optional[str] = None, **kwargs) -> str: + """翻译文本 + + Args: + key: 翻译键 (支持点号分隔的嵌套路径,如 "user.greeting") + locale: 指定语言 (默认使用当前语言) + **kwargs: 插值参数 + + Returns: + 翻译后的文本 + """ + target_locale = locale or self._current_locale + + # 尝试从指定语言获取 + value = self._get_nested(key, self._translations.get(target_locale, {})) + + # 如果未找到,尝试从回退语言获取 + if value is None and target_locale != self._fallback_locale: + value = self._get_nested(key, self._translations.get(self._fallback_locale, {})) + + # 仍未找到,返回键名 + if value is None: + return key + + # 插值处理: {{name}} 或 {name} + return self._interpolate(value, kwargs) + + def _get_nested(self, key: str, data: dict) -> Any: + """获取嵌套字典值""" + keys = key.split(".") + current = data + for k in keys: + if isinstance(current, dict) and k in current: + current = current[k] + else: + return None + return current + + def _interpolate(self, text: str, kwargs: dict) -> str: + """插值替换: {{name}} 或 {name}""" + # 支持 {{name}} 格式 + result = re.sub(r'\{\{(\w+)\}\}', lambda m: str(kwargs.get(m.group(1), m.group(0))), text) + # 支持 {name} 格式 (如果未被 {{}} 替换) + result = re.sub(r'\{(\w+)\}', lambda m: str(kwargs.get(m.group(1), m.group(0))), result) + return result + + def get_supported_locales(self) -> list[str]: + """获取支持的语言列表""" + return self._supported_locales + + def is_valid_locale(self, locale: str) -> bool: + """检查语言是否有效""" + return locale in self._supported_locales + + def detect_locale(self, accept_language: Optional[str] = None, + query_lang: Optional[str] = None, + cookie_lang: Optional[str] = None) -> str: + """检测语言优先级 + + Args: + accept_language: HTTP Accept-Language 头 + query_lang: URL 查询参数 ?lang=xx + cookie_lang: Cookie 中的语言 + + Returns: + 检测到的语言代码 + """ + # 1. 查询参数优先级最高 + if query_lang and self.is_valid_locale(query_lang): + return query_lang + + # 2. Cookie 次之 + if cookie_lang and self.is_valid_locale(cookie_lang): + return cookie_lang + + # 3. Accept-Language 头 + if accept_language: + # 解析 "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7" + languages = [] + for part in accept_language.split(","): + part = part.strip() + if ";q=" in part: + lang, q = part.split(";q=") + languages.append((lang.strip(), float(q))) + else: + languages.append((part, 1.0)) + + # 按权重排序 + languages.sort(key=lambda x: x[1], reverse=True) + + for lang, _ in languages: + # 精确匹配 + if self.is_valid_locale(lang): + return lang + # 前缀匹配 (zh 匹配 zh-CN, zh-TW) + for supported in self._supported_locales: + if supported.startswith(lang + "-") or lang.startswith(supported.split("-")[0] + "-"): + return supported + + # 4. 默认语言 + return self._current_locale diff --git a/store/@{FutureOSS}/i18n/locales/en-US.json b/store/@{FutureOSS}/i18n/locales/en-US.json new file mode 100644 index 0000000..959d446 --- /dev/null +++ b/store/@{FutureOSS}/i18n/locales/en-US.json @@ -0,0 +1,51 @@ +{ + "common": { + "success": "Success", + "error": "Error", + "not_found": "Not Found", + "forbidden": "Forbidden", + "unauthorized": "Unauthorized", + "server_error": "Internal Server Error", + "bad_request": "Bad Request", + "ok": "OK", + "cancel": "Cancel", + "save": "Save", + "delete": "Delete", + "edit": "Edit", + "create": "Create", + "search": "Search", + "loading": "Loading...", + "no_data": "No Data", + "confirm": "Confirm", + "back": "Back" + }, + "health": { + "status": "Running", + "service": "Service", + "version": "Version", + "uptime": "Uptime" + }, + "api": { + "welcome": "Welcome to FutureOSS API", + "docs": "API Documentation", + "rate_limit": "Rate limit exceeded, please try again later", + "invalid_request": "Invalid request parameters", + "missing_param": "Missing required parameter: {{param}}", + "invalid_param": "Invalid parameter format: {{param}}" + }, + "errors": { + "400": "Bad Request", + "401": "Please login first", + "403": "You don't have permission to perform this action", + "404": "The requested resource was not found", + "500": "Internal server error, please try again later", + "502": "Bad Gateway", + "503": "Service temporarily unavailable, please try again later" + }, + "plugin": { + "i18n_name": "Internationalization", + "i18n_desc": "Provides translation loading, language detection, and HTTP middleware", + "locale_changed": "Locale changed to {{locale}}", + "locale_not_supported": "Unsupported locale: {{locale}}" + } +} diff --git a/store/@{FutureOSS}/i18n/locales/zh-CN.json b/store/@{FutureOSS}/i18n/locales/zh-CN.json new file mode 100644 index 0000000..0f97598 --- /dev/null +++ b/store/@{FutureOSS}/i18n/locales/zh-CN.json @@ -0,0 +1,51 @@ +{ + "common": { + "success": "成功", + "error": "错误", + "not_found": "未找到", + "forbidden": "禁止访问", + "unauthorized": "未授权", + "server_error": "服务器内部错误", + "bad_request": "请求格式错误", + "ok": "确定", + "cancel": "取消", + "save": "保存", + "delete": "删除", + "edit": "编辑", + "create": "创建", + "search": "搜索", + "loading": "加载中...", + "no_data": "暂无数据", + "confirm": "确认", + "back": "返回" + }, + "health": { + "status": "运行正常", + "service": "服务", + "version": "版本", + "uptime": "运行时间" + }, + "api": { + "welcome": "欢迎使用 FutureOSS API", + "docs": "API 文档", + "rate_limit": "请求频率过高,请稍后重试", + "invalid_request": "无效的请求参数", + "missing_param": "缺少必需参数: {{param}}", + "invalid_param": "参数格式错误: {{param}}" + }, + "errors": { + "400": "请求格式错误", + "401": "请先登录", + "403": "您没有权限执行此操作", + "404": "请求的资源不存在", + "500": "服务器内部错误,请稍后重试", + "502": "网关错误", + "503": "服务暂时不可用,请稍后重试" + }, + "plugin": { + "i18n_name": "国际化多语言支持", + "i18n_desc": "提供翻译加载、语言检测和 HTTP 中间件功能", + "locale_changed": "语言已切换为 {{locale}}", + "locale_not_supported": "不支持的语言: {{locale}}" + } +} diff --git a/store/@{FutureOSS}/i18n/locales/zh-TW.json b/store/@{FutureOSS}/i18n/locales/zh-TW.json new file mode 100644 index 0000000..f7cddb3 --- /dev/null +++ b/store/@{FutureOSS}/i18n/locales/zh-TW.json @@ -0,0 +1,51 @@ +{ + "common": { + "success": "成功", + "error": "錯誤", + "not_found": "找不到", + "forbidden": "禁止存取", + "unauthorized": "未授權", + "server_error": "伺服器內部錯誤", + "bad_request": "請求格式錯誤", + "ok": "確定", + "cancel": "取消", + "save": "儲存", + "delete": "刪除", + "edit": "編輯", + "create": "建立", + "search": "搜尋", + "loading": "載入中...", + "no_data": "暫無資料", + "confirm": "確認", + "back": "返回" + }, + "health": { + "status": "運作正常", + "service": "服務", + "version": "版本", + "uptime": "運行時間" + }, + "api": { + "welcome": "歡迎使用 FutureOSS API", + "docs": "API 文件", + "rate_limit": "請求頻率過高,請稍後重試", + "invalid_request": "無效的請求參數", + "missing_param": "缺少必要參數: {{param}}", + "invalid_param": "參數格式錯誤: {{param}}" + }, + "errors": { + "400": "請求格式錯誤", + "401": "請先登入", + "403": "您沒有權限執行此操作", + "404": "請求的資源不存在", + "500": "伺服器內部錯誤,請稍後重試", + "502": "閘道錯誤", + "503": "服務暫時不可用,請稍後重試" + }, + "plugin": { + "i18n_name": "國際化多語言支援", + "i18n_desc": "提供翻譯載入、語言偵測和 HTTP 中介軟體功能", + "locale_changed": "語言已切換為 {{locale}}", + "locale_not_supported": "不支援的語言: {{locale}}" + } +} diff --git a/store/@{FutureOSS}/i18n/main.py b/store/@{FutureOSS}/i18n/main.py new file mode 100644 index 0000000..ca7f2d9 --- /dev/null +++ b/store/@{FutureOSS}/i18n/main.py @@ -0,0 +1,215 @@ +"""i18n 国际化多语言支持插件""" +import json +from pathlib import Path +from oss.plugin.types import Plugin, register_plugin_type +from .i18n import I18nEngine +from .middleware import I18nMiddleware + + +class I18nPlugin(Plugin): + """i18n 国际化插件""" + + def __init__(self): + self.engine = I18nEngine() + self.middleware_handler = None + + def meta(self): + """插件元数据""" + from oss.plugin.types import Metadata, PluginConfig, Manifest + return Manifest( + metadata=Metadata( + name="i18n", + version="1.0.0", + author="FutureOSS", + description="国际化多语言支持 - 提供翻译加载/语言切换/HTTP中间件" + ), + config=PluginConfig( + enabled=True, + args={ + "default_locale": "zh-CN", + "fallback_locale": "en-US", + "supported_locales": ["zh-CN", "en-US", "zh-TW"] + } + ), + dependencies=[] + ) + + def init(self, deps: dict = None): + """初始化插件 + + 加载语言文件并初始化中间件 + """ + # 获取插件配置 + config = {} + if deps: + config = deps.get("config", {}) + + # 默认配置 + default_locale = config.get("default_locale", "zh-CN") + fallback_locale = config.get("fallback_locale", "en-US") + supported_locales = config.get("supported_locales", ["zh-CN", "en-US", "zh-TW"]) + locales_dir = config.get("locales_dir", "locales") + + # 解析 locales_dir 相对路径 + plugin_dir = Path(__file__).parent + full_locales_dir = plugin_dir / locales_dir + + # 设置回退语言 + self.engine.set_fallback(fallback_locale) + + # 加载语言文件 + self.engine.load_locales(str(full_locales_dir), supported_locales) + + # 设置默认语言 + self.engine.set_locale(default_locale) + + # 初始化中间件 + self.middleware_handler = I18nMiddleware(self.engine, config) + + print(f"[i18n] 已加载语言: {', '.join(supported_locales)}") + print(f"[i18n] 默认语言: {default_locale}") + + def start(self): + """启动插件 + + 注册 API 路由(如果有 http-api 依赖) + """ + # 如果有 http-api 依赖,注册 i18n 相关路由 + http_api = None + if hasattr(self, 'set_http_api'): + http_api = getattr(self, '_http_api', None) + + if http_api and hasattr(http_api, 'router'): + http_api.router.get("/api/i18n/locales", self._locales_handler) + http_api.router.get("/api/i18n/translate", self._translate_handler) + http_api.router.post("/api/i18n/locale", self._change_locale_handler) + print("[i18n] API 路由已注册") + + def stop(self): + """停止插件""" + print("[i18n] 插件已停止") + + def health(self) -> bool: + """健康检查""" + return self.engine is not None + + def stats(self) -> dict: + """获取插件统计""" + return { + "current_locale": self.engine.get_locale(), + "supported_locales": self.engine.get_supported_locales(), + "loaded_translations": len(self.engine._translations) + } + + # ========== 依赖注入 Setter ========== + + def set_http_api(self, http_api): + """注入 http-api 依赖""" + self._http_api = http_api + + # ========== API 处理器 ========== + + def _locales_handler(self, request): + """获取支持的语言列表""" + from oss.plugin.types import Response + t = getattr(request, 't', self.engine.t) + + locales = [] + for locale in self.engine.get_supported_locales(): + locales.append({ + "code": locale, + "name": t(f"plugin.i18n_name", locale=locale) + }) + + return Response( + status=200, + body=json.dumps({ + "current": self.engine.get_locale(), + "supported": locales + }), + headers={"Content-Type": "application/json"} + ) + + def _translate_handler(self, request): + """翻译接口 + + GET /api/i18n/translate?key=user.greeting&locale=en-US&name=World + """ + from oss.plugin.types import Response + t = getattr(request, 't', self.engine.t) + + # 解析查询参数 + query = request.path.split("?", 1)[-1] if "?" in request.path else "" + params = {} + for param in query.split("&"): + if "=" in param: + key, value = param.split("=", 1) + params[key] = value + + key = params.get("key", "") + locale = params.get("locale", None) + + if not key: + return Response( + status=400, + body=json.dumps({"error": t("api.missing_param", param="key")}), + headers={"Content-Type": "application/json"} + ) + + # 翻译 + result = t(key, locale=locale, **params) + + return Response( + status=200, + body=json.dumps({ + "key": key, + "locale": locale or self.engine.get_locale(), + "text": result + }), + headers={"Content-Type": "application/json"} + ) + + def _change_locale_handler(self, request): + """切换语言接口 + + POST /api/i18n/locale + Body: {"locale": "en-US"} + """ + from oss.plugin.types import Response + t = getattr(request, 't', self.engine.t) + + try: + body = json.loads(request.body) if hasattr(request, 'body') and request.body else {} + except json.JSONDecodeError: + body = {} + + new_locale = body.get("locale", "") + + if not new_locale: + return Response( + status=400, + body=json.dumps({"error": t("api.missing_param", param="locale")}), + headers={"Content-Type": "application/json"} + ) + + if not self.engine.is_valid_locale(new_locale): + return Response( + status=400, + body=json.dumps({"error": t("plugin.locale_not_supported", locale=new_locale)}), + headers={"Content-Type": "application/json"} + ) + + self.engine.set_locale(new_locale) + + return Response( + status=200, + body=json.dumps({"message": t("plugin.locale_changed", locale=new_locale)}), + headers={"Content-Type": "application/json"} + ) + + +register_plugin_type("I18nPlugin", I18nPlugin) + + +def New(): + return I18nPlugin() diff --git a/store/@{FutureOSS}/i18n/manifest.json b/store/@{FutureOSS}/i18n/manifest.json new file mode 100644 index 0000000..1d63ad7 --- /dev/null +++ b/store/@{FutureOSS}/i18n/manifest.json @@ -0,0 +1,23 @@ +{ + "metadata": { + "name": "i18n", + "version": "1.0.0", + "author": "FutureOSS", + "description": "国际化多语言支持 - 提供翻译加载/语言切换/HTTP中间件", + "type": "middleware" + }, + "config": { + "enabled": true, + "args": { + "default_locale": "zh-CN", + "fallback_locale": "en-US", + "locales_dir": "locales", + "supported_locales": ["zh-CN", "en-US", "zh-TW"], + "auto_detect": true, + "cookie_name": "locale", + "query_param": "lang" + } + }, + "dependencies": [], + "permissions": ["lifecycle"] +} diff --git a/store/@{FutureOSS}/i18n/middleware.py b/store/@{FutureOSS}/i18n/middleware.py new file mode 100644 index 0000000..16b1444 --- /dev/null +++ b/store/@{FutureOSS}/i18n/middleware.py @@ -0,0 +1,90 @@ +"""i18n HTTP 中间件""" +import json +from typing import Optional, Callable +from oss.plugin.types import Response + + +class I18nMiddleware: + """i18n 中间件 + + 自动检测语言并注入到请求上下文 + 检测优先级: + 1. URL 查询参数 ?lang=xx + 2. Cookie locale=xx + 3. Accept-Language 头 + 4. 默认语言 + """ + + def __init__(self, engine, config: dict = None): + self.engine = engine + self.cookie_name = (config or {}).get("cookie_name", "locale") + self.query_param = (config or {}).get("query_param", "lang") + + def handle(self, request: dict, next_fn: Callable) -> Response: + """处理请求 + + 1. 检测语言 + 2. 将语言注入到请求上下文 + 3. 调用下一个中间件/处理器 + 4. 可选: 在响应中添加 Content-Language 头 + """ + # 解析查询参数 + query_lang = self._parse_query_param(request.get("query", "")) + + # 解析 Cookie + cookie_lang = self._parse_cookie(request.get("headers", {})) + + # 解析 Accept-Language + accept_language = request.get("headers", {}).get("Accept-Language", + request.get("headers", {}).get("accept-language", "")) + + # 检测语言 + locale = self.engine.detect_locale( + accept_language=accept_language if accept_language else None, + query_lang=query_lang, + cookie_lang=cookie_lang + ) + + # 设置当前语言 + self.engine.set_locale(locale) + + # 注入到请求上下文 + request["locale"] = locale + request["t"] = self.engine.t # 提供翻译函数 + + # 调用下一个处理器 + response = next_fn() + + # 在响应中添加 Content-Language 头 + if isinstance(response, Response): + response.headers["Content-Language"] = locale + + return response + + def _parse_query_param(self, query_string: str) -> Optional[str]: + """从查询字符串解析语言参数""" + if not query_string: + return None + + # 解析 ?lang=xx 或 &lang=xx + params = {} + for param in query_string.lstrip("?").split("&"): + if "=" in param: + key, value = param.split("=", 1) + params[key.strip()] = value.strip() + + return params.get(self.query_param) + + def _parse_cookie(self, headers: dict) -> Optional[str]: + """从 Cookie 解析语言参数""" + cookie_header = headers.get("Cookie", headers.get("cookie", "")) + if not cookie_header: + return None + + cookies = {} + for cookie in cookie_header.split(";"): + if "=" in cookie: + key, value = cookie.split("=", 1) + cookies[key.strip()] = value.strip() + + return cookies.get(self.cookie_name) diff --git a/store/@{FutureOSS}/ws-api/__pycache__/middleware.cpython-313.pyc b/store/@{FutureOSS}/ws-api/__pycache__/middleware.cpython-313.pyc index 5a94e6e22c5950be539b84b9360a43aab4dfd722..32115613bf8fd51aa27f6de4b16a2b9637d842d3 100644 GIT binary patch delta 564 zcmZ9JJ!lj`6vyA}%ZmX^sWF<{}FQ!Mg__ha7IyvNMP%wERpc-|2}T^ib6 z{`tYHnspPsHMGtRL<}*BY0P4tgDh-9ND>wq2@P39A{w!XM{u5pbP!hT6_Rkl11Msj zRvAKLi2^(%o3z4RI0H6R*adoz6uA0go}Q}I$luB?IZ~l@-XUiG&5PiU0T4k#1zY%E zElkyCjbAb6F?U#k>5Ep0U?Dl{0pPx5UWX1z7=j9}o>;R*t4))1v27%eq<)LGUwa^B zef9pWq@Jd?7wc`heD|?*F(pqRQVWq)->k`jK)W&G*LQddJAThbqZN$5@LMWx7i<3K z{_WxN*Zy+zc)8UZG~HmA!l`TIAnn1HwUSdhNw!>4>SbG=g=d+=u=EiHvpcD~jy$TP zlNuK_^d71Hq-r%gt3KKJ={!2Wy9*ri*J*2TY~9~ywv+1sVmAYQ!|LYqM$;bIXdI-v z{n=1~ydo3qH7}@)Gfk${b?36{^U*g~--mDK_Q6*#9YJQ)59gdWKt}ffAxE(D4@!7( A8vp zm@}s_YO<6H0Tn#!T>fHV&yyW%UryihWcRk0llK(?l`|xRG&4;8$|fMS1jw*sg;*KF z6wL^h0TLmslQr2@8CfU$vx|x{fn1u$5W*D9sLRX1kjoUx5X3q;kX@LSF@!mUW%5LJ z2W7TfOu6MnARpdh&P=H&;s&ycctC_8kSJyY5|i0DEcg{cykH>l<0V4_!-L7;9HtTM zJH#)`I$jrdyddCsLs+WA?;|sVgcIY>Vvrx29a$U&7!R^DI`Xj`Rnf~_nDxi=vpVe)$p4Rd86`w!SIKK>5o?<{Pb(jOT>gaCt>?BvM)`U@O# zo%KJ9L2hjqS7n^XsLV2-RdsSOrwya! Optional[str]: """执行中间件链""" idx = 0 + current_message = message - async def next_fn(): - nonlocal idx + async def next_fn(msg=None): + nonlocal idx, current_message + if msg is not None: + current_message = msg if idx < len(self.middlewares): mw = self.middlewares[idx] idx += 1 - return await mw.process(client, message, next_fn) - return message + return await mw.process(client, current_message, next_fn) + return current_message return await next_fn() diff --git a/website/index.html b/website/index.html index dd5867b..8ed24e5 100644 --- a/website/index.html +++ b/website/index.html @@ -32,8 +32,8 @@ 2026 · 插件驱动 · 一切皆可扩展

- OSS - Runtime + Future + OSS

一切皆为插件的开发者工具运行时框架

协议、中间件、通知渠道……所有功能均以插件形式加载。内置熔断降级、依赖自动解析、事件驱动等企业级稳定性机制。