add update prox
This commit is contained in:
		| @ -41,7 +41,67 @@ def set_default_routes(value='youDao'): | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def ensure_use_personal_config_column(table_name='translate_config'): | ||||
|     """ | ||||
|     确保 translate_config 表存在 usePersonalConfig 字段,默认值为 'true' | ||||
|     """ | ||||
|     try: | ||||
|         from django.db import connection | ||||
|         with connection.cursor() as cursor: | ||||
|             # 当前数据库名 | ||||
|             cursor.execute("SELECT DATABASE()") | ||||
|             db_name = cursor.fetchone()[0] | ||||
|  | ||||
|             # 检测列是否存在 | ||||
|             cursor.execute( | ||||
|                 """ | ||||
|                 SELECT COUNT(*) FROM information_schema.COLUMNS | ||||
|                 WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s AND COLUMN_NAME=%s | ||||
|                 """, | ||||
|                 [db_name, table_name, 'usePersonalConfig'] | ||||
|             ) | ||||
|             exists = cursor.fetchone()[0] > 0 | ||||
|  | ||||
|             if not exists: | ||||
|                 cursor.execute( | ||||
|                     """ | ||||
|                     ALTER TABLE translate_config | ||||
|                       ADD COLUMN usePersonalConfig VARCHAR(8) NOT NULL DEFAULT 'true' | ||||
|                       COMMENT '是否启用当前联系人配置:true/false' | ||||
|                     """ | ||||
|                 ) | ||||
|                 print('[OK] Added column usePersonalConfig to translate_config') | ||||
|             else: | ||||
|                 print('[SKIP] Column usePersonalConfig already exists') | ||||
|     except Exception as e: | ||||
|         print('[WARN] ensure_use_personal_config_column failed:', e) | ||||
|  | ||||
|  | ||||
| def repair_use_personal_config_values(): | ||||
|     """ | ||||
|     将已有数据中 NULL 或 空字符串 的 usePersonalConfig 修复为 'true' | ||||
|     """ | ||||
|     try: | ||||
|         from django.db import connection | ||||
|         with connection.cursor() as cursor: | ||||
|             cursor.execute( | ||||
|                 """ | ||||
|                 UPDATE translate_config | ||||
|                    SET usePersonalConfig = 'true' | ||||
|                  WHERE usePersonalConfig IS NULL OR usePersonalConfig = '' | ||||
|                 """ | ||||
|             ) | ||||
|             print('[OK] Repaired translate_config.usePersonalConfig NULL/empty -> true') | ||||
|     except Exception as e: | ||||
|         print('[WARN] repair_use_personal_config_values failed:', e) | ||||
|  | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     real, hist = set_default_routes('youDao') | ||||
|     print('OK set defaults: ', real, hist) | ||||
|     # 确保字段存在并修复数据 | ||||
|     ensure_use_personal_config_column() | ||||
|     repair_use_personal_config_values() | ||||
|     print('OK ensure/repair usePersonalConfig complete') | ||||
|  | ||||
|  | ||||
| @ -257,14 +257,14 @@ def translate_api(request): | ||||
|                         target_user = user | ||||
|  | ||||
|                     if not safe_deduct_character_count(username=target_user.username, key=target_user.user_api_key, deduction=-char_count): | ||||
|                          | ||||
|  | ||||
|                         return Response( | ||||
|                             {"error": f"账户可用字符不足,请充值"}, | ||||
|                             status=status.HTTP_400_BAD_REQUEST | ||||
|                         ) | ||||
|                     # 设备字符增加 | ||||
|                     if not safe_deduct_character_add(device=device, user_id=user.id, deduction=char_count): | ||||
|                          | ||||
|  | ||||
|                         return Response( | ||||
|                             {"error": f"设备字符添加失败"}, | ||||
|                             status=status.HTTP_400_BAD_REQUEST | ||||
| @ -287,24 +287,24 @@ def translate_api(request): | ||||
|                     #     contact_name=contact_name, | ||||
|                     # ) | ||||
|                     # save_record.save() | ||||
|                      | ||||
|  | ||||
|                     return Response( | ||||
|                         {"translate_text": translate_text}, | ||||
|                     ) | ||||
|                 except Exception as e: | ||||
|                      | ||||
|  | ||||
|                     return Response( | ||||
|                         {"error": f"翻译服务调用失败,请检查:{e}"}, | ||||
|                         status=status.HTTP_400_BAD_REQUEST | ||||
|                     ) | ||||
|             except Exception as e: | ||||
|                  | ||||
|  | ||||
|                 return Response( | ||||
|                     {"error": f"查询翻译服务或者编码出错:{e} "}, | ||||
|                     status=status.HTTP_400_BAD_REQUEST | ||||
|                 ) | ||||
|         except Exception as e: | ||||
|              | ||||
|  | ||||
|             return Response( | ||||
|                 {"error": f"翻译失败,请重试或更换翻译服务!"}, | ||||
|                 status=status.HTTP_400_BAD_REQUEST | ||||
| @ -592,7 +592,7 @@ def user_login(request): | ||||
|     username = data['username'] | ||||
|     password = data['password'] | ||||
|     device = data['device'] | ||||
|      | ||||
|  | ||||
|     # 用户认证 - 使用Django的认证系统 | ||||
|     user = authenticate(request, username=username, password=password) | ||||
|     if user is None or not user.is_active: | ||||
| @ -652,7 +652,7 @@ def user_login(request): | ||||
|                 "message": "账户已禁用该设备" | ||||
|             } | ||||
|         ) | ||||
|      | ||||
|  | ||||
|     # 判断是否是子账户,如果不是,剩余字符数返回父账户的 | ||||
|     if user.parent_id is not None: | ||||
|         parent_user = Users.objects.get(id=user.parent_id) | ||||
| @ -714,7 +714,7 @@ def user_register(request): | ||||
|                 "message": "请求参数缺少或者错误" | ||||
|             } | ||||
|         ) | ||||
|      | ||||
|  | ||||
|     username = data['username'] | ||||
|     password = data['password'] | ||||
|     # email = data['email'] | ||||
| @ -780,7 +780,7 @@ def user_register(request): | ||||
|                 "message": "注册失败,请稍后重试" | ||||
|             } | ||||
|         ) | ||||
|      | ||||
|  | ||||
| @api_view(['GET']) | ||||
| @permission_classes([AllowAny]) | ||||
| @renderer_classes([JSONRenderer]) | ||||
| @ -800,4 +800,113 @@ def get_system_config(request): | ||||
|             "message": "success", | ||||
|             "config_value": config_value | ||||
|         } | ||||
|     ) | ||||
|     ) | ||||
|  | ||||
| # 语言检测(后端统一接口) | ||||
| from rest_framework.decorators import api_view, permission_classes, renderer_classes | ||||
| from rest_framework.permissions import AllowAny | ||||
| from rest_framework.renderers import JSONRenderer | ||||
| from rest_framework.response import Response | ||||
|  | ||||
|  | ||||
| def _heuristic_detect(text: str): | ||||
|     import re | ||||
|     s = text or "" | ||||
|     if not isinstance(s, str): | ||||
|         s = str(s) | ||||
|     s = s.strip() | ||||
|     if not s: | ||||
|         return {"lang": "und", "prob": 0.0, "reliable": False} | ||||
|     # Unicode 脚本特征 | ||||
|     if re.search(r"[\u4e00-\u9fff]", s): | ||||
|         return {"lang": "zh", "prob": 0.99, "reliable": True} | ||||
|     if re.search(r"[\u3040-\u30ff]", s): | ||||
|         return {"lang": "ja", "prob": 0.95, "reliable": True} | ||||
|     if re.search(r"[\uac00-\ud7af]", s): | ||||
|         return {"lang": "ko", "prob": 0.95, "reliable": True} | ||||
|     if re.search(r"[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]", s): | ||||
|         return {"lang": "ar", "prob": 0.9, "reliable": True} | ||||
|     if re.search(r"[\u0400-\u04FF\u0500-\u052F]", s): | ||||
|         return {"lang": "ru", "prob": 0.9, "reliable": True} | ||||
|     # 英文:含英文字母,且不含其它显著脚本 | ||||
|     if re.search(r"[A-Za-z]", s): | ||||
|         if not re.search(r"[^\x00-\x7F]", s): | ||||
|             return {"lang": "en", "prob": 0.85, "reliable": True} | ||||
|         # 允许表情/全角符号等 | ||||
|         if not (re.search(r"[\u4e00-\u9fff]", s) or re.search(r"[\u3040-\u30ff]", s) or re.search(r"[\uac00-\ud7af]", s)): | ||||
|             return {"lang": "en", "prob": 0.7, "reliable": False} | ||||
|     # 拉丁扩展粗略判断 | ||||
|     if re.search(r"[àâäæçéèêëîïôœùûüÿñ¡¿]", s, flags=re.I): | ||||
|         if re.search(r"ñ|¡|¿|á|é|í|ó|ú", s, flags=re.I): | ||||
|             return {"lang": "es", "prob": 0.6, "reliable": False} | ||||
|         if re.search(r"ç|à|â|ä|é|è|ê|ë|î|ï|ô|œ|ù|û|ü|ÿ", s, flags=re.I): | ||||
|             return {"lang": "fr", "prob": 0.6, "reliable": False} | ||||
|         if re.search(r"ã|õ|á|é|í|ó|ú", s, flags=re.I): | ||||
|             return {"lang": "pt", "prob": 0.6, "reliable": False} | ||||
|     return {"lang": "und", "prob": 0.0, "reliable": False} | ||||
|  | ||||
|  | ||||
| @api_view(['GET', 'POST']) | ||||
| @permission_classes([AllowAny]) | ||||
| @renderer_classes([JSONRenderer]) | ||||
| def detect_language(request): | ||||
|     """ | ||||
|     统一语言检测接口: | ||||
|     - GET:  /api/detect_language?text=... | ||||
|     - POST: {"text": "..."} 或 {"texts": ["...","..."]} | ||||
|     返回:code=2000, data={ single | list }, message | ||||
|     未知/无法判断时返回 lang=und,prob=0.0,reliable=false(按你的策略前端放行) | ||||
|     """ | ||||
|     import json | ||||
|     # 尝试使用可选依赖(未安装则自动回退) | ||||
|     pycld3 = None | ||||
|     langdetect = None | ||||
|     try: | ||||
|         from pycld3 import NNetLanguageIdentifier  # type: ignore | ||||
|         pycld3 = NNetLanguageIdentifier(min_num_bytes=0, max_num_bytes=1000) | ||||
|     except Exception: | ||||
|         pycld3 = None | ||||
|     try: | ||||
|         import langdetect as _ld  # type: ignore | ||||
|         langdetect = _ld | ||||
|     except Exception: | ||||
|         langdetect = None | ||||
|  | ||||
|     def detect_one(text: str): | ||||
|         if pycld3: | ||||
|             try: | ||||
|                 r = pycld3.FindLanguage(text or "") | ||||
|                 if r and r.language: | ||||
|                     lang = r.language if r.language != 'unknown' else 'und' | ||||
|                     return {"lang": lang, "prob": float(getattr(r, 'probability', 0.0) or 0.0), "reliable": bool(getattr(r, 'is_reliable', False))} | ||||
|             except Exception: | ||||
|                 pass | ||||
|         if langdetect: | ||||
|             try: | ||||
|                 from langdetect import detect_langs  # type: ignore | ||||
|                 cand = detect_langs(text or "") | ||||
|                 if cand: | ||||
|                     c0 = cand[0] | ||||
|                     return {"lang": getattr(c0, 'lang', 'und') or 'und', "prob": float(getattr(c0, 'prob', 0.0) or 0.0), "reliable": float(getattr(c0, 'prob', 0.0) or 0.0) >= 0.85} | ||||
|             except Exception: | ||||
|                 pass | ||||
|         return _heuristic_detect(text) | ||||
|  | ||||
|     try: | ||||
|         if request.method == 'GET': | ||||
|             text = request.GET.get('text', '') | ||||
|             data = detect_one(text) | ||||
|             return Response({"code": 2000, "message": "success", "data": data}) | ||||
|         # POST | ||||
|         body = request.data if hasattr(request, 'data') else {} | ||||
|         text = body.get('text') | ||||
|         texts = body.get('texts') | ||||
|         if text is not None: | ||||
|             data = detect_one(text) | ||||
|             return Response({"code": 2000, "message": "success", "data": data}) | ||||
|         if isinstance(texts, list): | ||||
|             out = [detect_one(x) for x in texts] | ||||
|             return Response({"code": 2000, "message": "success", "data": out}) | ||||
|         return Response({"code": 401, "message": "参数缺失:text 或 texts"}) | ||||
|     except Exception as e: | ||||
|         return Response({"code": 5000, "message": f"detect failed: {e}"}) | ||||
|  | ||||
| @ -4,3 +4,30 @@ from django.apps import AppConfig | ||||
| class TranslateConfig(AppConfig): | ||||
|     default_auto_field = 'django.db.models.BigAutoField' | ||||
|     name = 'translate' | ||||
|  | ||||
|     def ready(self): | ||||
|         # 在 migrate 之后自动确保/修复 usePersonalConfig 字段(幂等) | ||||
|         import os | ||||
|         from django.db.models.signals import post_migrate | ||||
|  | ||||
|         def _init_after_migrate(sender, **kwargs): | ||||
|             # 允许通过环境变量关闭,默认开启 | ||||
|             if os.environ.get('RUN_TRANSLATE_DEFAULTS', '1') != '1': | ||||
|                 return | ||||
|             try: | ||||
|                 from set_translate_defaults import ( | ||||
|                     set_default_routes, | ||||
|                     ensure_use_personal_config_column, | ||||
|                     repair_use_personal_config_values, | ||||
|                 ) | ||||
|                 # 设置默认翻译线路,确保系统配置完整 | ||||
|                 set_default_routes('youDao') | ||||
|                 # 保证列存在并修复历史数据 | ||||
|                 ensure_use_personal_config_column() | ||||
|                 repair_use_personal_config_values() | ||||
|                 print('[OK] post_migrate: translate defaults and usePersonalConfig ensured') | ||||
|             except Exception as e: | ||||
|                 print('[WARN] post_migrate init failed:', e) | ||||
|  | ||||
|         # 仅绑定当前应用,避免重复 | ||||
|         post_migrate.connect(_init_after_migrate, sender=self) | ||||
|  | ||||
| @ -0,0 +1,19 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| from django.core.management.base import BaseCommand | ||||
|  | ||||
| class Command(BaseCommand): | ||||
|     help = 'Ensure translate default routes and translate_config.usePersonalConfig (create/repair)' | ||||
|  | ||||
|     def handle(self, *args, **options): | ||||
|         from set_translate_defaults import ( | ||||
|             set_default_routes, | ||||
|             ensure_use_personal_config_column, | ||||
|             repair_use_personal_config_values, | ||||
|         ) | ||||
|         real, hist = set_default_routes('youDao') | ||||
|         self.stdout.write(self.style.SUCCESS(f'Set default routes: {real}, {hist}')) | ||||
|         ensure_use_personal_config_column() | ||||
|         repair_use_personal_config_values() | ||||
|         self.stdout.write(self.style.SUCCESS('Ensured/repair usePersonalConfig complete')) | ||||
|  | ||||
| @ -6,7 +6,7 @@ from .views import TranslationServiceModelViewSet, DeviceModelViewSet, DeviceCod | ||||
|     BillingRecordsModelViewSet | ||||
| from .views import StatusCodeModelViewSet | ||||
| from .views import TranslationRecordModelViewSet | ||||
| from .api import translate_api, check_login, consumption, renewal, user_login, user_register, get_system_config | ||||
| from .api import translate_api, check_login, consumption, renewal, user_login, user_register, get_system_config, detect_language | ||||
| from django.urls import path | ||||
|  | ||||
| from dvadmin.system.views.user import create_sub, list_sub, delete_sub, close_global_proxy_password_verification, update_sub, change_password, list_route, list_note, create_note, update_note, delete_note, get_default_translate_route_config | ||||
| @ -28,6 +28,7 @@ urlpatterns = [ | ||||
|     path("api/user_login", user_login, name='user_login'), | ||||
|     path("api/user_register", user_register, name='user_register'), | ||||
|     path("api/get_system_config", get_system_config, name='get_system_config'), | ||||
|     path("api/detect_language", detect_language, name='detect_language'), | ||||
|     # 字符套餐购买 | ||||
|     path("api/consumption/", consumption, name='consumption'), | ||||
|     # 设备续期 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 unknown
					unknown