Title: Upgrade to FutureOSS v1.1.0 with enterprise-grade security and deployment features

Key features implemented:
- New RELEASE_v1.1.0.md with comprehensive release notes for security upgrades and new features
- New firewall.py plugin implementing dynamic IP filtering, port management, and attack detection
- New frp_proxy.py plugin for FRP-based internal network tunneling and proxy services
- New ftp_server.py plugin providing secure file transfer with user management and access control
- New multi_lang_deploy.py orchestrator supporting automated detection and deployment of Python/Node.js/Go/Java/PHP projects
- New ops_toolbox.py with backup/recovery, health checks, and resource quota management
- New security_gateway.py with API rate limiting, JWT authentication, audit logging, and circuit breaker protection
- New HTML5/CSS3/JS-based webui replacing PHP templates with modern responsive design and real-time metrics
- New manifest.json files for all plugins adding configuration schemas and dependency declarations
- Updated .gitignore with refined ignore patterns for development environments
- Modified core plugin manifests to include internationalization dependencies and enhanced configurations
- Removed legacy PHP template files from webui frontend views
- Enhanced plugin bridge, storage, signature verification with multilingual support and security improvements
This commit is contained in:
qwen.ai[bot]
2026-04-25 00:01:05 +00:00
parent 1393dbe3eb
commit f8853ca45e
40 changed files with 1833 additions and 171 deletions

View File

@@ -0,0 +1,8 @@
{
"signature": "N8pwPuJxnjP/hgMG4QLYQy7Z6e1P1KctYLJYoQniALDFT1qb11RDm1w4KUbzNIY82XM56B10zYF88dTQiGMrtbgoExE0gtUvmF3THvEd+aWhQ0m5/2war2w+j02BWH0TvJqxhb5nHCyhA4CknJANWp4wZr9EPjDseb+OhXC3GECKpChVrmM9/DWM6TtjlmGol14kq+jUnrS5EWNSa1hlsLzKIrS3Jf5fLaButDUr6YuQkATRKl6F41M8+JHJwVVw5D1fRSqCZ4xFWwN90Gtdd22JFSeB9iVE2Myb3UurPzTVvJ0B/JE9yxFDhA1B7PtuF/WeWlm060QRWdlwFfO9NjUJOeOGQstn34DUG2xL/q3yF66SjnHcHs67DqVq9lCQ961jQq0QveKunV4u8uBJd4IGH4MTq5W7Be8GDgSZcll5HLG3HBL+9XYf4mJzc7dh88Y0UV+dOabD2SJCwBmMxgzDx+Dx8RwWx7b9IYZvmXz6fxtXhqfV6AFq2oY/+4Xjwn4nq7VOCgx8PxLrUvmuacmCwlar/rXuvHT0YsN/XXmJK9o/3NYsNp/go8Vm0XW0btJ+FnQw4O4OKPvSSd+Ip+tk2rLi7CuZGi0WEVp2o23gUNLXoHkKFrtms02Et6zC9AFwP2gLF+NnaMWImup54owxgDos9s6l2ejTD653rYE=",
"signer": "FutureOSS",
"algorithm": "RSA-SHA256",
"timestamp": 1775964953.002281,
"plugin_hash": "55f90852ff6fbd82bc5a51ea4ebc2725f1316a7a5f9d423ee10a7e571aad339a",
"author": "FutureOSS"
}

View File

@@ -0,0 +1 @@
"""i18n 国际化多语言支持插件"""

View File

@@ -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

View File

@@ -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}}"
}
}

View File

@@ -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}}"
}
}

View File

@@ -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}}"
}
}

View File

@@ -0,0 +1,216 @@
"""i18n 国际化多语言支持插件"""
import json
from pathlib import Path
from oss.logger.logger import Log
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)
Log.info("i18n", f"已加载语言: {', '.join(supported_locales)}")
Log.info("i18n", f"默认语言: {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)
Log.info("i18n", "API 路由已注册")
def stop(self):
"""停止插件"""
Log.error("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()

View File

@@ -0,0 +1,24 @@
{
"metadata": {
"name": "i18n",
"version": "1.1.0",
"author": "FutureOSS",
"description": "国际化多语言支持 - 提供翻译加载/语言切换/HTTP中间件/WebUI集成",
"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", "ja-JP", "ko-KR", "fr-FR", "de-DE", "es-ES"],
"auto_detect": true,
"cookie_name": "locale",
"query_param": "lang",
"header_name": "Accept-Language"
}
},
"dependencies": [],
"permissions": ["lifecycle", "http-api"]
}

View File

@@ -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)