diff --git a/.idea/workspace.xml b/.idea/workspace.xml index fb63dbe..d456bda 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,11 +5,22 @@ - + + + + + + + + - + + + + + - { + "keyToString": { + "Django 服务器.chunyu_project.executor": "Run", + "ModuleVcsDetector.initialDetectionPerformed": "true", + "Notification.DisplayName-DoNotAsk-DatabaseConfigFileWatcher.found": "发现新的/更新的数据库配置", + "Notification.DoNotAsk-DatabaseConfigFileWatcher.found": "true", + "Python.tests.executor": "Run", + "RunOnceActivity.MCP Project settings loaded": "true", + "RunOnceActivity.OpenDjangoStructureViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", + "RunOnceActivity.git.unshallow": "true", + "RunOnceActivity.typescript.service.memoryLimit.init": "true", + "git-widget-placeholder": "main", + "last_opened_file_path": "C:/Users/12914/PycharmProjects/chunyu_project/api", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable", + "vue.rearranger.settings.migration": "true" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "sqlite" + "keyToStringList": { + "DatabaseDriversLRU": [ + "sqlite" ] } -}]]> +} + - @@ -134,7 +145,12 @@ @@ -171,6 +203,6 @@ - + \ No newline at end of file diff --git a/api/4c9da042-07f7-4b23-8e8e-8db67cdad1a8.png b/api/4c9da042-07f7-4b23-8e8e-8db67cdad1a8.png new file mode 100644 index 0000000..f860599 Binary files /dev/null and b/api/4c9da042-07f7-4b23-8e8e-8db67cdad1a8.png differ diff --git a/api/__pycache__/__init__.cpython-312.pyc b/api/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..54c9d71 Binary files /dev/null and b/api/__pycache__/__init__.cpython-312.pyc differ diff --git a/api/__pycache__/admin.cpython-312.pyc b/api/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..e4b6154 Binary files /dev/null and b/api/__pycache__/admin.cpython-312.pyc differ diff --git a/api/__pycache__/apps.cpython-312.pyc b/api/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..716b080 Binary files /dev/null and b/api/__pycache__/apps.cpython-312.pyc differ diff --git a/api/__pycache__/models.cpython-312.pyc b/api/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..a772236 Binary files /dev/null and b/api/__pycache__/models.cpython-312.pyc differ diff --git a/api/__pycache__/urls.cpython-312.pyc b/api/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..a04b3d5 Binary files /dev/null and b/api/__pycache__/urls.cpython-312.pyc differ diff --git a/api/migrations/__pycache__/__init__.cpython-312.pyc b/api/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..8d9939e Binary files /dev/null and b/api/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/api/tests.py b/api/tests.py index 7ce503c..8f6ea3d 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,3 +1,19 @@ -from django.test import TestCase +# -*- coding=utf-8 +from qcloud_cos import CosConfig +from qcloud_cos import CosS3Client +import sys +import logging -# Create your tests here. +# 正常情况日志级别使用 INFO,需要定位时可以修改为 DEBUG,此时 SDK 会打印和服务端的通信信息 +logging.basicConfig(level=logging.INFO, stream=sys.stdout) + +# 1. 设置用户属性, 包括 secret_id, secret_key, region 等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成 +secret_id = 'SecretId' # 替换为用户的 SecretId,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi +secret_key = 'SecretKey' # 替换为用户的 SecretKey,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi +region = 'ap-beijing' # 替换为用户的 region,已创建桶归属的 region 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket + # COS支持的所有 region 列表参见 https://cloud.tencent.com/document/product/436/6224 +token = None # 如果使用永久密钥不需要填入token,如果使用临时密钥需要填入,临时密钥生成和使用指引参见 https://cloud.tencent.com/document/product/436/14048 +scheme = 'https' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填 + +config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme) +client = CosS3Client(config) \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index f45bf9e..02bea08 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,8 +1,28 @@ + from django.contrib import admin + from django.urls import path, include + from .views.GetIPDataView import GetIPDataView -from .views.BaiduFanyiView import BaiduFanyiView + +from .views.BaiduFanyiView import BaiduFanyiView, AllLangTypeViews, AutoLangTypeViews, RecognizeLangTypeViews, PictureRecognizeViews, SpeechRecognitionView + urlpatterns = [ + path("getIpData/", GetIPDataView.as_view()), + path("baiduFanyi/", BaiduFanyiView.as_view()), + + path("getAllLangType/", AllLangTypeViews.as_view()), + + path("getAutoLangType/", AutoLangTypeViews.as_view()), + + path("recognizeLangType/", RecognizeLangTypeViews.as_view()), + + path("pictureRecognize/", PictureRecognizeViews.as_view()), + + path("speechRecognition/", SpeechRecognitionView.as_view()), + + + ] \ No newline at end of file diff --git a/api/views/BaiduFanyiView.py b/api/views/BaiduFanyiView.py index a56a71c..fc6ce50 100644 --- a/api/views/BaiduFanyiView.py +++ b/api/views/BaiduFanyiView.py @@ -1,4 +1,8 @@ +from symtable import Class + +from django.core.signing import base64_hmac from rest_framework.decorators import permission_classes +from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.permissions import AllowAny from rest_framework.views import APIView @@ -13,6 +17,12 @@ import random import json from hashlib import md5 +from twisted.scripts.htmlizer import header + +from .info.baidu_lang_info import languages, auto_lang + +from .info.baidu_fanyi_appid import appid, appkey, endpoint + def make_md5(s, encoding='utf-8'): return md5(s.encode(encoding)).hexdigest() @@ -20,21 +30,48 @@ def make_md5(s, encoding='utf-8'): @permission_classes([AllowAny]) class BaiduFanyiView(APIView): def post(self, request): + """ + + 请求参数为 q 翻译内容 文本长度限制3000字以下 + from_lang 原语言 + to_lang 翻译语言 (支持 auto 为自动识别) + + + :param request: + :return: + """ print(request.data) # Set your own appid/appkey. - appid = '20220718001275847' - appkey = 'CJHmvf7qGs3szQy32cMg' - from_lang = 'en' - to_lang = 'zh' - endpoint = 'http://api.fanyi.baidu.com' + from_lang = request.data.get('from_lang') + to_lang = request.data.get('to_lang') + + if (from_lang not in languages) or (to_lang not in languages) or (to_lang == "auto"): + return Response( + { + "message": "翻译语种异常" + }, + status=status.HTTP_400_BAD_REQUEST + ) + + if from_lang == to_lang: + return Response( + { + "message": "翻译数据异常" + } + ) + path = '/api/trans/vip/translate' url = endpoint + path query = request.data['q'] + if len(query) > 3000: + return Response({"请求数据长度过长已经超过3000."}, status=status.HTTP_400_BAD_REQUEST) + + salt = random.randint(32768, 65536) sign = make_md5(appid + query + str(salt) + appkey) @@ -44,5 +81,169 @@ class BaiduFanyiView(APIView): r = requests.post(url, params=payload, headers=headers) result = r.json() + return Response(result, status=status.HTTP_200_OK) +@permission_classes([AllowAny]) +class AutoLangTypeViews(APIView): # 获取可用自动识别的所有语言类型 + def get(self, request): + return Response(auto_lang, status=status.HTTP_200_OK) + +@permission_classes([AllowAny]) +class AllLangTypeViews(APIView): # 获取可用自动识别的所有语言类型 + def get(self, request): + return Response(languages, status=status.HTTP_200_OK) + + +@permission_classes([AllowAny]) +class RecognizeLangTypeViews(APIView): # 文本语种识别 + """ + + 请求参数为 q 翻译内容 文本长度限制3000字以下 + + :param request: + :return: + """ + def post(self, request): + + path = '/api/trans/vip/language' + url = endpoint + path + + query = request.data.get("q") + + if len(query) > 3000: + return Response({"请求数据长度过长已经超过3000."}, status=status.HTTP_400_BAD_REQUEST) + + salt = random.randint(32768, 65536) + sign = make_md5(appid + query + str(salt) + appkey) + + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + payload = {'appid': appid, 'q': query, 'salt': salt, 'sign': sign} + + r = requests.post(url, params=payload, headers=headers) + result = r.json() + + code = result.get('error_code') + + if code == 0: + return Response(result, status=status.HTTP_200_OK) + elif code == 54009: + return Response({ + "message": "不在课识别语种范围." + }, status=status.HTTP_400_BAD_REQUEST) + else : + return Response({ + "message": "其他问题请联系管理员." + }, status=status.HTTP_400_BAD_REQUEST) + +import requests +import random +from hashlib import md5 + +from .info.baidu_fanyi_appid import appid as app_id, appkey as app_key + +from .info.baidu_lang_info import cuid, mac, p_code, p_lang_type + +@permission_classes([AllowAny]) +class PictureRecognizeViews(APIView): # 图片翻译 + + parser_classes = [MultiPartParser, FormParser] + + def post(self, request): + + file_data = request.FILES['file'].read() + + path = '/api/trans/sdk/picture' + url = endpoint + path + + from_lang = request.GET.get("from_lang") + to_lang = request.GET.get("to_lang") + picture_type = request.GET.get("picture") + + if (from_lang == to_lang) or (to_lang == "auto"): + return Response({"message": "翻译数据错误."}, status=status.HTTP_400_BAD_REQUEST) + if (from_lang not in p_lang_type.keys()) or (to_lang not in p_lang_type.keys()) : + return Response({"message": "翻译语种异常."}, status=status.HTTP_400_BAD_REQUEST) + + def get_md5(string, encoding='utf-8'): + return md5(string.encode(encoding)).hexdigest() + + salt = random.randint(32768, 65536) + sign = get_md5(app_id + md5(file_data).hexdigest() + str(salt) + cuid + mac + app_key) + + payload = {'from': from_lang, 'to': to_lang, 'appid': app_id, 'salt': salt, 'sign': sign, 'cuid': cuid, + 'mac': mac} + image = {'image': (f"p_image.{ picture_type }", file_data, "multipart/form-data")} + + response = requests.post(url, params=payload, files=image) + result = response.json() + + if result['error_code'] == 0: + return Response(result, status=status.HTTP_200_OK) + else: + result = p_code[result['error_code']] + return Response(result, status=status.HTTP_400_BAD_REQUEST) + +from .info.baidu_lang_info import y_speech_type, pcm_y_lang_type, y_lang_type + +import time + +import hmac +import hashlib +import base64 + + +@permission_classes([AllowAny]) +class SpeechRecognitionView(APIView): + + parser_classes = [MultiPartParser, FormParser] + + def post(self, request): + speech_type = request.GET.get("speech_type") + + if speech_type not in y_speech_type: + return Response({"message": "不支持的语音类型."}, status=status.HTTP_400_BAD_REQUEST) + + from_lang = request.GET.get('from_lang') + to_lang = request.GET.get('to_lang') + + if (from_lang == to_lang) or (to_lang == "auto"): + return Response({"message": "翻译数据错误."}, status=status.HTTP_400_BAD_REQUEST) + + + if (from_lang not in (pcm_y_lang_type.keys() if speech_type == "pcm" else y_lang_type.keys())) or (to_lang not in (pcm_y_lang_type.keys() if speech_type == "pcm" else y_lang_type.keys())): + return Response({"message": "翻译语种异常."}, status=status.HTTP_400_BAD_REQUEST) + + voice = request.FILES.get("voice").read() + + base64_hmac_voice = base64.b64encode(voice).decode('utf-8') + + timestamp = str(int(time.time())) + + path = "/api/trans/v2/voicetrans" + + url = endpoint + path + + msg = appid + timestamp + base64_hmac_voice + sign = base64.b64encode(hmac.new(app_key.encode("utf-8"), msg.encode("utf-8"), digestmod=hashlib.sha256).digest()) + + print(sign) + + headers = { + "Content-Type": "application/json", + "X-Appid": app_id, + "X-Timestamp": timestamp, + "X-Sign": sign, + "X-EncryptType": "hmac256" + } + + data = { + "format": speech_type, + "voice": base64_hmac_voice, + "to": to_lang, + "from": from_lang, + } + + response = requests.post(url, headers = headers,data = data) + + result = response.json() return Response(result, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/api/views/__pycache__/BaiduFanyiView.cpython-312.pyc b/api/views/__pycache__/BaiduFanyiView.cpython-312.pyc new file mode 100644 index 0000000..cbdc3a7 Binary files /dev/null and b/api/views/__pycache__/BaiduFanyiView.cpython-312.pyc differ diff --git a/api/views/__pycache__/GetIPDataView.cpython-312.pyc b/api/views/__pycache__/GetIPDataView.cpython-312.pyc new file mode 100644 index 0000000..b07b3bd Binary files /dev/null and b/api/views/__pycache__/GetIPDataView.cpython-312.pyc differ diff --git a/api/views/__pycache__/__init__.cpython-312.pyc b/api/views/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..2d019e3 Binary files /dev/null and b/api/views/__pycache__/__init__.cpython-312.pyc differ diff --git a/api/views/info/__init__.py b/api/views/info/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/views/info/__pycache__/__init__.cpython-312.pyc b/api/views/info/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..1b34ed6 Binary files /dev/null and b/api/views/info/__pycache__/__init__.cpython-312.pyc differ diff --git a/api/views/info/__pycache__/baidu_fanyi_appid.cpython-312.pyc b/api/views/info/__pycache__/baidu_fanyi_appid.cpython-312.pyc new file mode 100644 index 0000000..fac5ad0 Binary files /dev/null and b/api/views/info/__pycache__/baidu_fanyi_appid.cpython-312.pyc differ diff --git a/api/views/info/__pycache__/baidu_lang_info.cpython-312.pyc b/api/views/info/__pycache__/baidu_lang_info.cpython-312.pyc new file mode 100644 index 0000000..0858e30 Binary files /dev/null and b/api/views/info/__pycache__/baidu_lang_info.cpython-312.pyc differ diff --git a/api/views/info/baidu_fanyi_appid.py b/api/views/info/baidu_fanyi_appid.py new file mode 100644 index 0000000..f0cb899 --- /dev/null +++ b/api/views/info/baidu_fanyi_appid.py @@ -0,0 +1,4 @@ +appid = '20220718001275847' +appkey = 'CJHmvf7qGs3szQy32cMg' + +endpoint = 'http://api.fanyi.baidu.com' \ No newline at end of file diff --git a/api/views/info/baidu_lang_info.py b/api/views/info/baidu_lang_info.py new file mode 100644 index 0000000..695fe83 --- /dev/null +++ b/api/views/info/baidu_lang_info.py @@ -0,0 +1,1184 @@ +# 图片识别支持类型 +cuid = 'APICUID' +mac = 'mac' + +p_lang_type = { + "zh": "中文", + "en": "英语", + "jp": "日语", + "kor": "韩语", + "fra": "法语", + "spa": "西班牙语", + "ru": "俄语", + "pt": "葡萄牙语", + "de": "德语", + "it": "意大利语", + "dan": "丹麦语", + "nl": "荷兰语", + "may": "马来语", + "swe": "瑞典语", + "id": "印尼语", + "pl": "波兰语", + "rom": "罗马尼亚语", + "tr": "土耳其语", + "el": "希腊语", + "hu": "匈牙利语", + "auto": "自动识别" +} + + + +p_code = { + "0": {"code": "0", "message": "成功"}, + "52001": {"code": "52001", "message": "请求超时"}, + "52002": {"code": "52002", "message": "服务端系统错误"}, + "52003": {"code": "52003", "message": "未授权用户"}, + "52010": {"code": "52010", "message": "开放设备授权容量不足"}, + "54000": {"code": "54000", "message": "必填参数为空或固定参数有误"}, + "54001": {"code": "54001", "message": "签名错误"}, + "54003": {"code": "54003", "message": "访问频率受限"}, + "54004": {"code": "54004", "message": "账户余额不足"}, + "54005": {"code": "54005", "message": "长query请求频繁"}, + "58000": {"code": "58000", "message": "客户端IP非法"}, + "58001": {"code": "58001", "message": "译文语言方向不支持"}, + "69001": {"code": "69001", "message": "上传图片数据有误"}, + "69002": {"code": "69002", "message": "图片识别超时"}, + "69003": {"code": "69003", "message": "内容识别失败"}, + "69004": {"code": "69004", "message": "识别内容为空"}, + "69005": {"code": "69005", "message": "图片大小超限(超过4M)"}, + "69006": {"code": "69006", "message": "图片尺寸不符合标准(最短边至少30px,最长边最大4096px)"}, + "69007": {"code": "69007", "message": "图片格式不支持(png/jpg)"}, + "69008": {"code": "69008", "message": "设备号为空"}, + "69012": {"code": "69012", "message": "文字贴合参数异常"} +} + +# 语音识别翻译 +y_lang_type = {'zh': '中文', 'en': '英语', 'jp': '日语', 'kor': '韩语', 'yue': '粤语', 'ru': '俄语', 'de': '德语', 'fra': '法语', 'th': '泰语', 'pt': '葡萄牙语', 'spa': '西班牙语', 'ara': '阿拉伯语(巴林)'} + +pcm_y_lang_type = {'zh': '中文', 'yue': '粤语'} + +y_speech_type = ["pcm", "wav", "amr", "m4a"] # 最多支持的语音类型 + + +# 通用文本翻译 +languages = {'ara': {'name': '阿拉伯语', 'code': 'ara', 'detect': True}, 'gle': {'name': '爱尔兰语', 'code': 'gle', 'detect': True}, 'oci': {'name': '奥克语', 'code': 'oci', 'detect': True}, 'alb': {'name': '阿尔巴尼亚语', 'code': 'alb', 'detect': True}, 'arq': {'name': '阿尔及利亚阿拉伯语', 'code': 'arq', 'detect': False}, 'aka': {'name': '阿肯语', 'code': 'aka', 'detect': False}, 'arg': {'name': '阿拉贡语', 'code': 'arg', 'detect': False}, 'amh': {'name': '阿姆哈拉语', 'code': 'amh', 'detect': True}, 'asm': {'name': '阿萨姆语', 'code': 'asm', 'detect': True}, 'aym': {'name': '艾马拉语', 'code': 'aym', 'detect': False}, 'aze': {'name': '阿塞拜疆语', 'code': 'aze', 'detect': True}, 'ast': {'name': '阿斯图里亚斯语', 'code': 'ast', 'detect': True}, 'oss': {'name': '奥塞梯语', 'code': 'oss', 'detect': False}, 'est': {'name': '爱沙尼亚语', 'code': 'est', 'detect': True}, 'oji': {'name': '奥杰布瓦语', 'code': 'oji', 'detect': False}, 'ori': {'name': '奥里亚语', 'code': 'ori', 'detect': True}, 'orm': {'name': '奥罗莫语', 'code': 'orm', 'detect': False}, 'pl': {'name': '波兰语', 'code': 'pl', 'detect': True}, 'per': {'name': '波斯语', 'code': 'per', 'detect': True}, 'bre': {'name': '布列塔尼语', 'code': 'bre', 'detect': True}, 'bak': {'name': '巴什基尔语', 'code': 'bak', 'detect': False}, 'baq': {'name': '巴斯克语', 'code': 'baq', 'detect': True}, 'pot': {'name': '巴西葡萄牙语', 'code': 'pot', 'detect': False}, 'bel': {'name': '白俄罗斯语', 'code': 'bel', 'detect': True}, 'ber': {'name': '柏柏尔语', 'code': 'ber', 'detect': True}, 'pam': {'name': '邦板牙语', 'code': 'pam', 'detect': False}, 'bul': {'name': '保加利亚语', 'code': 'bul', 'detect': True}, 'sme': {'name': '北方萨米语', 'code': 'sme', 'detect': False}, 'ped': {'name': '北索托语', 'code': 'ped', 'detect': False}, 'bem': {'name': '本巴语', 'code': 'bem', 'detect': False}, 'bli': {'name': '比林语', 'code': 'bli', 'detect': False}, 'bis': {'name': '比斯拉马语', 'code': 'bis', 'detect': False}, 'bal': {'name': '俾路支语', 'code': 'bal', 'detect': False}, 'ice': {'name': '冰岛语', 'code': 'ice', 'detect': True}, 'bos': {'name': '波斯尼亚语', 'code': 'bos', 'detect': True}, 'bho': {'name': '博杰普尔语', 'code': 'bho', 'detect': False}, 'chv': {'name': '楚瓦什语', 'code': 'chv', 'detect': False}, 'tso': {'name': '聪加语', 'code': 'tso', 'detect': False}, 'dan': {'name': '丹麦语', 'code': 'dan', 'detect': True}, 'de': {'name': '德语', 'code': 'de', 'detect': True}, 'tat': {'name': '鞑靼语', 'code': 'tat', 'detect': True}, 'sha': {'name': '掸语', 'code': 'sha', 'detect': False}, 'tet': {'name': '德顿语', 'code': 'tet', 'detect': False}, 'div': {'name': '迪维希语', 'code': 'div', 'detect': False}, 'log': {'name': '低地德语', 'code': 'log', 'detect': True}, 'ru': {'name': '俄语', 'code': 'ru', 'detect': True}, 'fra': {'name': '法语', 'code': 'fra', 'detect': True}, 'fil': {'name': '菲律宾语', 'code': 'fil', 'detect': True}, 'fin': {'name': '芬兰语', 'code': 'fin', 'detect': True}, 'san': {'name': '梵语', 'code': 'san', 'detect': False}, 'fri': {'name': '弗留利语', 'code': 'fri', 'detect': False}, 'ful': {'name': '富拉尼语', 'code': 'ful', 'detect': False}, 'fao': {'name': '法罗语', 'code': 'fao', 'detect': False}, 'gla': {'name': '盖尔语', 'code': 'gla', 'detect': False}, 'kon': {'name': '刚果语', 'code': 'kon', 'detect': False}, 'ups': {'name': '高地索布语', 'code': 'ups', 'detect': False}, 'hkm': {'name': '高棉语', 'code': 'hkm', 'detect': True}, 'kal': {'name': '格陵兰语', 'code': 'kal', 'detect': False}, 'geo': {'name': '格鲁吉亚语', 'code': 'geo', 'detect': True}, 'guj': {'name': '古吉拉特语', 'code': 'guj', 'detect': True}, 'gra': {'name': '古希腊语', 'code': 'gra', 'detect': False}, 'eno': {'name': '古英语', 'code': 'eno', 'detect': False}, 'grn': {'name': '瓜拉尼语', 'code': 'grn', 'detect': False}, 'kor': {'name': '韩语', 'code': 'kor', 'detect': True}, 'nl': {'name': '荷兰语', 'code': 'nl', 'detect': True}, 'hup': {'name': '胡帕语', 'code': 'hup', 'detect': False}, 'hak': {'name': '哈卡钦语', 'code': 'hak', 'detect': False}, 'ht': {'name': '海地语', 'code': 'ht', 'detect': False}, 'mot': {'name': '黑山语', 'code': 'mot', 'detect': False}, 'hau': {'name': '豪萨语', 'code': 'hau', 'detect': False}, 'kir': {'name': '吉尔吉斯语', 'code': 'kir', 'detect': False}, 'glg': {'name': '加利西亚语', 'code': 'glg', 'detect': True}, 'frn': {'name': '加拿大法语', 'code': 'frn', 'detect': False}, 'cat': {'name': '加泰罗尼亚语', 'code': 'cat', 'detect': True}, 'cs': {'name': '捷克语', 'code': 'cs', 'detect': True}, 'kab': {'name': '卡拜尔语', 'code': 'kab', 'detect': True}, 'kan': {'name': '卡纳达语', 'code': 'kan', 'detect': True}, 'kau': {'name': '卡努里语', 'code': 'kau', 'detect': False}, 'kah': {'name': '卡舒比语', 'code': 'kah', 'detect': False}, 'cor': {'name': '康瓦尔语', 'code': 'cor', 'detect': False}, 'xho': {'name': '科萨语', 'code': 'xho', 'detect': True}, 'cos': {'name': '科西嘉语', 'code': 'cos', 'detect': False}, 'cre': {'name': '克里克语', 'code': 'cre', 'detect': False}, 'cri': {'name': '克里米亚鞑靼语', 'code': 'cri', 'detect': False}, 'kli': {'name': '克林贡语', 'code': 'kli', 'detect': False}, 'hrv': {'name': '克罗地亚语', 'code': 'hrv', 'detect': True}, 'que': {'name': '克丘亚语', 'code': 'que', 'detect': False}, 'kas': {'name': '克什米尔语', 'code': 'kas', 'detect': False}, 'kok': {'name': '孔卡尼语', 'code': 'kok', 'detect': False}, 'kur': {'name': '库尔德语', 'code': 'kur', 'detect': True}, 'lat': {'name': '拉丁语', 'code': 'lat', 'detect': True}, 'lao': {'name': '老挝语', 'code': 'lao', 'detect': False}, 'rom': {'name': '罗马尼亚语', 'code': 'rom', 'detect': True}, 'lag': {'name': '拉特加莱语', 'code': 'lag', 'detect': False}, 'lav': {'name': '拉脱维亚语', 'code': 'lav', 'detect': True}, 'lim': {'name': '林堡语', 'code': 'lim', 'detect': False}, 'lin': {'name': '林加拉语', 'code': 'lin', 'detect': False}, 'lug': {'name': '卢干达语', 'code': 'lug', 'detect': False}, 'ltz': {'name': '卢森堡语', 'code': 'ltz', 'detect': False}, 'ruy': {'name': '卢森尼亚语', 'code': 'ruy', 'detect': False}, 'kin': {'name': '卢旺达语', 'code': 'kin', 'detect': True}, 'lit': {'name': '立陶宛语', 'code': 'lit', 'detect': True}, 'roh': {'name': '罗曼什语', 'code': 'roh', 'detect': False}, 'ro': {'name': '罗姆语', 'code': 'ro', 'detect': False}, 'loj': {'name': '逻辑语', 'code': 'loj', 'detect': False}, 'may': {'name': '马来语', 'code': 'may', 'detect': True}, 'bur': {'name': '缅甸语', 'code': 'bur', 'detect': True}, 'mar': {'name': '马拉地语', 'code': 'mar', 'detect': False}, 'mg': {'name': '马拉加斯语', 'code': 'mg', 'detect': True}, 'mal': {'name': '马拉雅拉姆语', 'code': 'mal', 'detect': True}, 'mac': {'name': '马其顿语', 'code': 'mac', 'detect': True}, 'mah': {'name': '马绍尔语', 'code': 'mah', 'detect': False}, 'mai': {'name': '迈蒂利语', 'code': 'mai', 'detect': True}, 'glv': {'name': '曼克斯语', 'code': 'glv', 'detect': False}, 'mau': {'name': '毛里求斯克里奥尔语', 'code': 'mau', 'detect': False}, 'mao': {'name': '毛利语', 'code': 'mao', 'detect': False}, 'ben': {'name': '孟加拉语', 'code': 'ben', 'detect': True}, 'mlt': {'name': '马耳他语', 'code': 'mlt', 'detect': True}, 'hmn': {'name': '苗语', 'code': 'hmn', 'detect': False}, 'nor': {'name': '挪威语', 'code': 'nor', 'detect': True}, 'nea': {'name': '那不勒斯语', 'code': 'nea', 'detect': False}, 'nbl': {'name': '南恩德贝莱语', 'code': 'nbl', 'detect': False}, 'afr': {'name': '南非荷兰语', 'code': 'afr', 'detect': True}, 'sot': {'name': '南索托语', 'code': 'sot', 'detect': False}, 'nep': {'name': '尼泊尔语', 'code': 'nep', 'detect': True}, 'pt': {'name': '葡萄牙语', 'code': 'pt', 'detect': True}, 'pan': {'name': '旁遮普语', 'code': 'pan', 'detect': True}, 'pap': {'name': '帕皮阿门托语', 'code': 'pap', 'detect': False}, 'pus': {'name': '普什图语', 'code': 'pus', 'detect': False}, 'nya': {'name': '齐切瓦语', 'code': 'nya', 'detect': False}, 'twi': {'name': '契维语', 'code': 'twi', 'detect': False}, 'chr': {'name': '切罗基语', 'code': 'chr', 'detect': False}, 'jp': {'name': '日语', 'code': 'jp', 'detect': True}, 'swe': {'name': '瑞典语', 'code': 'swe', 'detect': True}, 'srd': {'name': '萨丁尼亚语', 'code': 'srd', 'detect': False}, 'sm': {'name': '萨摩亚语', 'code': 'sm', 'detect': False}, 'sec': {'name': '塞尔维亚-克罗地亚语', 'code': 'sec', 'detect': False}, 'srp': {'name': '塞尔维亚语', 'code': 'srp', 'detect': True}, 'sol': {'name': '桑海语', 'code': 'sol', 'detect': False}, 'sin': {'name': '僧伽罗语', 'code': 'sin', 'detect': True}, 'epo': {'name': '世界语', 'code': 'epo', 'detect': True}, 'nob': {'name': '书面挪威语', 'code': 'nob', 'detect': True}, 'sk': {'name': '斯洛伐克语', 'code': 'sk', 'detect': True}, 'slo': {'name': '斯洛文尼亚语', 'code': 'slo', 'detect': True}, 'swa': {'name': '斯瓦希里语', 'code': 'swa', 'detect': True}, 'src': {'name': '塞尔维亚语(西里尔)', 'code': 'src', 'detect': False}, 'som': {'name': '索马里语', 'code': 'som', 'detect': True}, 'sco': {'name': '苏格兰语', 'code': 'sco', 'detect': False}, 'th': {'name': '泰语', 'code': 'th', 'detect': True}, 'tr': {'name': '土耳其语', 'code': 'tr', 'detect': True}, 'tgk': {'name': '塔吉克语', 'code': 'tgk', 'detect': True}, 'tam': {'name': '泰米尔语', 'code': 'tam', 'detect': True}, 'tgl': {'name': '他加禄语', 'code': 'tgl', 'detect': True}, 'tir': {'name': '提格利尼亚语', 'code': 'tir', 'detect': False}, 'tel': {'name': '泰卢固语', 'code': 'tel', 'detect': True}, 'tua': {'name': '突尼斯阿拉伯语', 'code': 'tua', 'detect': False}, 'tuk': {'name': '土库曼语', 'code': 'tuk', 'detect': False}, 'ukr': {'name': '乌克兰语', 'code': 'ukr', 'detect': True}, 'wln': {'name': '瓦隆语', 'code': 'wln', 'detect': True}, 'wel': {'name': '威尔士语', 'code': 'wel', 'detect': True}, 'ven': {'name': '文达语', 'code': 'ven', 'detect': False}, 'wol': {'name': '沃洛夫语', 'code': 'wol', 'detect': False}, 'urd': {'name': '乌尔都语', 'code': 'urd', 'detect': True}, 'spa': {'name': '西班牙语', 'code': 'spa', 'detect': True}, 'heb': {'name': '希伯来语', 'code': 'heb', 'detect': True}, 'el': {'name': '希腊语', 'code': 'el', 'detect': True}, 'hu': {'name': '匈牙利语', 'code': 'hu', 'detect': True}, 'fry': {'name': '西弗里斯语', 'code': 'fry', 'detect': True}, 'sil': {'name': '西里西亚语', 'code': 'sil', 'detect': False}, 'hil': {'name': '希利盖农语', 'code': 'hil', 'detect': False}, 'los': {'name': '下索布语', 'code': 'los', 'detect': False}, 'haw': {'name': '夏威夷语', 'code': 'haw', 'detect': False}, 'nno': {'name': '新挪威语', 'code': 'nno', 'detect': True}, 'nqo': {'name': '西非书面语', 'code': 'nqo', 'detect': False}, 'snd': {'name': '信德语', 'code': 'snd', 'detect': False}, 'sna': {'name': '修纳语', 'code': 'sna', 'detect': False}, 'ceb': {'name': '宿务语', 'code': 'ceb', 'detect': False}, 'syr': {'name': '叙利亚语', 'code': 'syr', 'detect': False}, 'sun': {'name': '巽他语', 'code': 'sun', 'detect': False}, 'en': {'name': '英语', 'code': 'en', 'detect': True}, 'hi': {'name': '印地语', 'code': 'hi', 'detect': True}, 'id': {'name': '印尼语', 'code': 'id', 'detect': True}, 'it': {'name': '意大利语', 'code': 'it', 'detect': True}, 'vie': {'name': '越南语', 'code': 'vie', 'detect': True}, 'yid': {'name': '意第绪语', 'code': 'yid', 'detect': False}, 'ina': {'name': '因特语', 'code': 'ina', 'detect': False}, 'ach': {'name': '亚齐语', 'code': 'ach', 'detect': False}, 'ing': {'name': '印古什语', 'code': 'ing', 'detect': False}, 'ibo': {'name': '伊博语', 'code': 'ibo', 'detect': False}, 'ido': {'name': '伊多语', 'code': 'ido', 'detect': False}, 'yor': {'name': '约鲁巴语', 'code': 'yor', 'detect': False}, 'arm': {'name': '亚美尼亚语', 'code': 'arm', 'detect': True}, 'iku': {'name': '伊努克提图特语', 'code': 'iku', 'detect': False}, 'zh': {'name': '中文(简体)', 'code': 'zh', 'detect': True}, 'cht': {'name': '中文(繁体)', 'code': 'cht', 'detect': True}, 'wyw': {'name': '中文(文言文)', 'code': 'wyw', 'detect': True}, 'yue': {'name': '中文(粤语)', 'code': 'yue', 'detect': True}, 'zaz': {'name': '扎扎其语', 'code': 'zaz', 'detect': False}, 'frm': {'name': '中古法语', 'code': 'frm', 'detect': False}, 'zul': {'name': '祖鲁语', 'code': 'zul', 'detect': False}, 'jav': {'name': '爪哇语', 'code': 'jav', 'detect': False}, 'auto': {'name': '自动检测', 'code': 'auto', 'detect': True}} +auto_lang = {'ara': {'name': '阿拉伯语', 'code': 'ara', 'detect': True}, 'gle': {'name': '爱尔兰语', 'code': 'gle', 'detect': True}, 'oci': {'name': '奥克语', 'code': 'oci', 'detect': True}, 'alb': {'name': '阿尔巴尼亚语', 'code': 'alb', 'detect': True}, 'amh': {'name': '阿姆哈拉语', 'code': 'amh', 'detect': True}, 'asm': {'name': '阿萨姆语', 'code': 'asm', 'detect': True}, 'aze': {'name': '阿塞拜疆语', 'code': 'aze', 'detect': True}, 'ast': {'name': '阿斯图里亚斯语', 'code': 'ast', 'detect': True}, 'est': {'name': '爱沙尼亚语', 'code': 'est', 'detect': True}, 'ori': {'name': '奥里亚语', 'code': 'ori', 'detect': True}, 'pl': {'name': '波兰语', 'code': 'pl', 'detect': True}, 'per': {'name': '波斯语', 'code': 'per', 'detect': True}, 'bre': {'name': '布列塔尼语', 'code': 'bre', 'detect': True}, 'baq': {'name': '巴斯克语', 'code': 'baq', 'detect': True}, 'bel': {'name': '白俄罗斯语', 'code': 'bel', 'detect': True}, 'ber': {'name': '柏柏尔语', 'code': 'ber', 'detect': True}, 'bul': {'name': '保加利亚语', 'code': 'bul', 'detect': True}, 'ice': {'name': '冰岛语', 'code': 'ice', 'detect': True}, 'bos': {'name': '波斯尼亚语', 'code': 'bos', 'detect': True}, 'dan': {'name': '丹麦语', 'code': 'dan', 'detect': True}, 'de': {'name': '德语', 'code': 'de', 'detect': True}, 'tat': {'name': '鞑靼语', 'code': 'tat', 'detect': True}, 'log': {'name': '低地德语', 'code': 'log', 'detect': True}, 'ru': {'name': '俄语', 'code': 'ru', 'detect': True}, 'fra': {'name': '法语', 'code': 'fra', 'detect': True}, 'fil': {'name': '菲律宾语', 'code': 'fil', 'detect': True}, 'fin': {'name': '芬兰语', 'code': 'fin', 'detect': True}, 'hkm': {'name': '高棉语', 'code': 'hkm', 'detect': True}, 'geo': {'name': '格鲁吉亚语', 'code': 'geo', 'detect': True}, 'guj': {'name': '古吉拉特语', 'code': 'guj', 'detect': True}, 'kor': {'name': '韩语', 'code': 'kor', 'detect': True}, 'nl': {'name': '荷兰语', 'code': 'nl', 'detect': True}, 'glg': {'name': '加利西亚语', 'code': 'glg', 'detect': True}, 'cat': {'name': '加泰罗尼亚语', 'code': 'cat', 'detect': True}, 'cs': {'name': '捷克语', 'code': 'cs', 'detect': True}, 'kab': {'name': '卡拜尔语', 'code': 'kab', 'detect': True}, 'kan': {'name': '卡纳达语', 'code': 'kan', 'detect': True}, 'xho': {'name': '科萨语', 'code': 'xho', 'detect': True}, 'hrv': {'name': '克罗地亚语', 'code': 'hrv', 'detect': True}, 'kur': {'name': '库尔德语', 'code': 'kur', 'detect': True}, 'lat': {'name': '拉丁语', 'code': 'lat', 'detect': True}, 'rom': {'name': '罗马尼亚语', 'code': 'rom', 'detect': True}, 'lav': {'name': '拉脱维亚语', 'code': 'lav', 'detect': True}, 'kin': {'name': '卢旺达语', 'code': 'kin', 'detect': True}, 'lit': {'name': '立陶宛语', 'code': 'lit', 'detect': True}, 'may': {'name': '马来语', 'code': 'may', 'detect': True}, 'bur': {'name': '缅甸语', 'code': 'bur', 'detect': True}, 'mg': {'name': '马拉加斯语', 'code': 'mg', 'detect': True}, 'mal': {'name': '马拉雅拉姆语', 'code': 'mal', 'detect': True}, 'mac': {'name': '马其顿语', 'code': 'mac', 'detect': True}, 'mai': {'name': '迈蒂利语', 'code': 'mai', 'detect': True}, 'ben': {'name': '孟加拉语', 'code': 'ben', 'detect': True}, 'mlt': {'name': '马耳他语', 'code': 'mlt', 'detect': True}, 'nor': {'name': '挪威语', 'code': 'nor', 'detect': True}, 'afr': {'name': '南非荷兰语', 'code': 'afr', 'detect': True}, 'nep': {'name': '尼泊尔语', 'code': 'nep', 'detect': True}, 'pt': {'name': '葡萄牙语', 'code': 'pt', 'detect': True}, 'pan': {'name': '旁遮普语', 'code': 'pan', 'detect': True}, 'jp': {'name': '日语', 'code': 'jp', 'detect': True}, 'swe': {'name': '瑞典语', 'code': 'swe', 'detect': True}, 'srp': {'name': '塞尔维亚语', 'code': 'srp', 'detect': True}, 'sin': {'name': '僧伽罗语', 'code': 'sin', 'detect': True}, 'epo': {'name': '世界语', 'code': 'epo', 'detect': True}, 'nob': {'name': '书面挪威语', 'code': 'nob', 'detect': True}, 'sk': {'name': '斯洛伐克语', 'code': 'sk', 'detect': True}, 'slo': {'name': '斯洛文尼亚语', 'code': 'slo', 'detect': True}, 'swa': {'name': '斯瓦希里语', 'code': 'swa', 'detect': True}, 'som': {'name': '索马里语', 'code': 'som', 'detect': True}, 'th': {'name': '泰语', 'code': 'th', 'detect': True}, 'tr': {'name': '土耳其语', 'code': 'tr', 'detect': True}, 'tgk': {'name': '塔吉克语', 'code': 'tgk', 'detect': True}, 'tam': {'name': '泰米尔语', 'code': 'tam', 'detect': True}, 'tgl': {'name': '他加禄语', 'code': 'tgl', 'detect': True}, 'tel': {'name': '泰卢固语', 'code': 'tel', 'detect': True}, 'ukr': {'name': '乌克兰语', 'code': 'ukr', 'detect': True}, 'wln': {'name': '瓦隆语', 'code': 'wln', 'detect': True}, 'wel': {'name': '威尔士语', 'code': 'wel', 'detect': True}, 'urd': {'name': '乌尔都语', 'code': 'urd', 'detect': True}, 'spa': {'name': '西班牙语', 'code': 'spa', 'detect': True}, 'heb': {'name': '希伯来语', 'code': 'heb', 'detect': True}, 'el': {'name': '希腊语', 'code': 'el', 'detect': True}, 'hu': {'name': '匈牙利语', 'code': 'hu', 'detect': True}, 'fry': {'name': '西弗里斯语', 'code': 'fry', 'detect': True}, 'nno': {'name': '新挪威语', 'code': 'nno', 'detect': True}, 'en': {'name': '英语', 'code': 'en', 'detect': True}, 'hi': {'name': '印地语', 'code': 'hi', 'detect': True}, 'id': {'name': '印尼语', 'code': 'id', 'detect': True}, 'it': {'name': '意大利语', 'code': 'it', 'detect': True}, 'vie': {'name': '越南语', 'code': 'vie', 'detect': True}, 'arm': {'name': '亚美尼亚语', 'code': 'arm', 'detect': True}, 'zh': {'name': '中文(简体)', 'code': 'zh', 'detect': True}, 'cht': {'name': '中文(繁体)', 'code': 'cht', 'detect': True}, 'wyw': {'name': '中文(文言文)', 'code': 'wyw', 'detect': True}, 'yue': {'name': '中文(粤语)', 'code': 'yue', 'detect': True}} + +total_languages = 153 +supported_detect = 91 +unsupported_detect = 62 + +lang = { # 通用文本翻译 +"statistics": { + "total_languages": 153, + "supported_detect": 91, + "unsupported_detect": 62 +}, +"languages": [ + { + "name": "阿拉伯语", + "code": "ara", + "detect": True + + }, + { + "name": "爱尔兰语", + "code": "gle", + "detect": True + + }, + { + "name": "奥克语", + "code": "oci", + "detect": True + + }, + { + "name": "阿尔巴尼亚语", + "code": "alb", + "detect": True + + }, + { + "name": "阿尔及利亚阿拉伯语", + "code": "arq", + "detect": False + }, + { + "name": "阿肯语", + "code": "aka", + "detect": False + }, + { + "name": "阿拉贡语", + "code": "arg", + "detect": False + }, + { + "name": "阿姆哈拉语", + "code": "amh", + "detect": True + + }, + { + "name": "阿萨姆语", + "code": "asm", + "detect": True + + }, + { + "name": "艾马拉语", + "code": "aym", + "detect": False + }, + { + "name": "阿塞拜疆语", + "code": "aze", + "detect": True + + }, + { + "name": "阿斯图里亚斯语", + "code": "ast", + "detect": True + + }, + { + "name": "奥塞梯语", + "code": "oss", + "detect": False + }, + { + "name": "爱沙尼亚语", + "code": "est", + "detect": True + + }, + { + "name": "奥杰布瓦语", + "code": "oji", + "detect": False + }, + { + "name": "奥里亚语", + "code": "ori", + "detect": True + + }, + { + "name": "奥罗莫语", + "code": "orm", + "detect": False + }, + { + "name": "波兰语", + "code": "pl", + "detect": True + + }, + { + "name": "波斯语", + "code": "per", + "detect": True + + }, + { + "name": "布列塔尼语", + "code": "bre", + "detect": True + + }, + { + "name": "巴什基尔语", + "code": "bak", + "detect": False + }, + { + "name": "巴斯克语", + "code": "baq", + "detect": True + + }, + { + "name": "巴西葡萄牙语", + "code": "pot", + "detect": False + }, + { + "name": "白俄罗斯语", + "code": "bel", + "detect": True + + }, + { + "name": "柏柏尔语", + "code": "ber", + "detect": True + + }, + { + "name": "邦板牙语", + "code": "pam", + "detect": False + }, + { + "name": "保加利亚语", + "code": "bul", + "detect": True + + }, + { + "name": "北方萨米语", + "code": "sme", + "detect": False + }, + { + "name": "北索托语", + "code": "ped", + "detect": False + }, + { + "name": "本巴语", + "code": "bem", + "detect": False + }, + { + "name": "比林语", + "code": "bli", + "detect": False + }, + { + "name": "比斯拉马语", + "code": "bis", + "detect": False + }, + { + "name": "俾路支语", + "code": "bal", + "detect": False + }, + { + "name": "冰岛语", + "code": "ice", + "detect": True + + }, + { + "name": "波斯尼亚语", + "code": "bos", + "detect": True + + }, + { + "name": "博杰普尔语", + "code": "bho", + "detect": False + }, + { + "name": "楚瓦什语", + "code": "chv", + "detect": False + }, + { + "name": "聪加语", + "code": "tso", + "detect": False + }, + { + "name": "丹麦语", + "code": "dan", + "detect": True + + }, + { + "name": "德语", + "code": "de", + "detect": True + + }, + { + "name": "鞑靼语", + "code": "tat", + "detect": True + + }, + { + "name": "掸语", + "code": "sha", + "detect": False + }, + { + "name": "德顿语", + "code": "tet", + "detect": False + }, + { + "name": "迪维希语", + "code": "div", + "detect": False + }, + { + "name": "低地德语", + "code": "log", + "detect": True + + }, + { + "name": "俄语", + "code": "ru", + "detect": True + + }, + { + "name": "法语", + "code": "fra", + "detect": True + + }, + { + "name": "菲律宾语", + "code": "fil", + "detect": True + + }, + { + "name": "芬兰语", + "code": "fin", + "detect": True + + }, + { + "name": "梵语", + "code": "san", + "detect": False + }, + { + "name": "弗留利语", + "code": "fri", + "detect": False + }, + { + "name": "富拉尼语", + "code": "ful", + "detect": False + }, + { + "name": "法罗语", + "code": "fao", + "detect": False + }, + { + "name": "盖尔语", + "code": "gla", + "detect": False + }, + { + "name": "刚果语", + "code": "kon", + "detect": False + }, + { + "name": "高地索布语", + "code": "ups", + "detect": False + }, + { + "name": "高棉语", + "code": "hkm", + "detect": True + + }, + { + "name": "格陵兰语", + "code": "kal", + "detect": False + }, + { + "name": "格鲁吉亚语", + "code": "geo", + "detect": True + + }, + { + "name": "古吉拉特语", + "code": "guj", + "detect": True + + }, + { + "name": "古希腊语", + "code": "gra", + "detect": False + }, + { + "name": "古英语", + "code": "eno", + "detect": False + }, + { + "name": "瓜拉尼语", + "code": "grn", + "detect": False + }, + { + "name": "韩语", + "code": "kor", + "detect": True + + }, + { + "name": "荷兰语", + "code": "nl", + "detect": True + + }, + { + "name": "胡帕语", + "code": "hup", + "detect": False + }, + { + "name": "哈卡钦语", + "code": "hak", + "detect": False + }, + { + "name": "海地语", + "code": "ht", + "detect": False + }, + { + "name": "黑山语", + "code": "mot", + "detect": False + }, + { + "name": "豪萨语", + "code": "hau", + "detect": False + }, + { + "name": "吉尔吉斯语", + "code": "kir", + "detect": False + }, + { + "name": "加利西亚语", + "code": "glg", + "detect": True + + }, + { + "name": "加拿大法语", + "code": "frn", + "detect": False + }, + { + "name": "加泰罗尼亚语", + "code": "cat", + "detect": True + + }, + { + "name": "捷克语", + "code": "cs", + "detect": True + + }, + { + "name": "卡拜尔语", + "code": "kab", + "detect": True + + }, + { + "name": "卡纳达语", + "code": "kan", + "detect": True + + }, + { + "name": "卡努里语", + "code": "kau", + "detect": False + }, + { + "name": "卡舒比语", + "code": "kah", + "detect": False + }, + { + "name": "康瓦尔语", + "code": "cor", + "detect": False + }, + { + "name": "科萨语", + "code": "xho", + "detect": True + + }, + { + "name": "科西嘉语", + "code": "cos", + "detect": False + }, + { + "name": "克里克语", + "code": "cre", + "detect": False + }, + { + "name": "克里米亚鞑靼语", + "code": "cri", + "detect": False + }, + { + "name": "克林贡语", + "code": "kli", + "detect": False + }, + { + "name": "克罗地亚语", + "code": "hrv", + "detect": True + + }, + { + "name": "克丘亚语", + "code": "que", + "detect": False + }, + { + "name": "克什米尔语", + "code": "kas", + "detect": False + }, + { + "name": "孔卡尼语", + "code": "kok", + "detect": False + }, + { + "name": "库尔德语", + "code": "kur", + "detect": True + + }, + { + "name": "拉丁语", + "code": "lat", + "detect": True + + }, + { + "name": "老挝语", + "code": "lao", + "detect": False + }, + { + "name": "罗马尼亚语", + "code": "rom", + "detect": True + + }, + { + "name": "拉特加莱语", + "code": "lag", + "detect": False + }, + { + "name": "拉脱维亚语", + "code": "lav", + "detect": True + + }, + { + "name": "林堡语", + "code": "lim", + "detect": False + }, + { + "name": "林加拉语", + "code": "lin", + "detect": False + }, + { + "name": "卢干达语", + "code": "lug", + "detect": False + }, + { + "name": "卢森堡语", + "code": "ltz", + "detect": False + }, + { + "name": "卢森尼亚语", + "code": "ruy", + "detect": False + }, + { + "name": "卢旺达语", + "code": "kin", + "detect": True + + }, + { + "name": "立陶宛语", + "code": "lit", + "detect": True + + }, + { + "name": "罗曼什语", + "code": "roh", + "detect": False + }, + { + "name": "罗姆语", + "code": "ro", + "detect": False + }, + { + "name": "逻辑语", + "code": "loj", + "detect": False + }, + { + "name": "马来语", + "code": "may", + "detect": True + + }, + { + "name": "缅甸语", + "code": "bur", + "detect": True + + }, + { + "name": "马拉地语", + "code": "mar", + "detect": False + }, + { + "name": "马拉加斯语", + "code": "mg", + "detect": True + + }, + { + "name": "马拉雅拉姆语", + "code": "mal", + "detect": True + + }, + { + "name": "马其顿语", + "code": "mac", + "detect": True + + }, + { + "name": "马绍尔语", + "code": "mah", + "detect": False + }, + { + "name": "迈蒂利语", + "code": "mai", + "detect": True + + }, + { + "name": "曼克斯语", + "code": "glv", + "detect": False + }, + { + "name": "毛里求斯克里奥尔语", + "code": "mau", + "detect": False + }, + { + "name": "毛利语", + "code": "mao", + "detect": False + }, + { + "name": "孟加拉语", + "code": "ben", + "detect": True + + }, + { + "name": "马耳他语", + "code": "mlt", + "detect": True + + }, + { + "name": "苗语", + "code": "hmn", + "detect": False + }, + { + "name": "挪威语", + "code": "nor", + "detect": True + + }, + { + "name": "那不勒斯语", + "code": "nea", + "detect": False + }, + { + "name": "南恩德贝莱语", + "code": "nbl", + "detect": False + }, + { + "name": "南非荷兰语", + "code": "afr", + "detect": True + + }, + { + "name": "南索托语", + "code": "sot", + "detect": False + }, + { + "name": "尼泊尔语", + "code": "nep", + "detect": True + + }, + { + "name": "葡萄牙语", + "code": "pt", + "detect": True + + }, + { + "name": "旁遮普语", + "code": "pan", + "detect": True + + }, + { + "name": "帕皮阿门托语", + "code": "pap", + "detect": False + }, + { + "name": "普什图语", + "code": "pus", + "detect": False + }, + { + "name": "齐切瓦语", + "code": "nya", + "detect": False + }, + { + "name": "契维语", + "code": "twi", + "detect": False + }, + { + "name": "切罗基语", + "code": "chr", + "detect": False + }, + { + "name": "日语", + "code": "jp", + "detect": True + + }, + { + "name": "瑞典语", + "code": "swe", + "detect": True + + }, + { + "name": "萨丁尼亚语", + "code": "srd", + "detect": False + }, + { + "name": "萨摩亚语", + "code": "sm", + "detect": False + }, + { + "name": "塞尔维亚-克罗地亚语", + "code": "sec", + "detect": False + }, + { + "name": "塞尔维亚语", + "code": "srp", + "detect": True + + }, + { + "name": "桑海语", + "code": "sol", + "detect": False + }, + { + "name": "僧伽罗语", + "code": "sin", + "detect": True + + }, + { + "name": "世界语", + "code": "epo", + "detect": True + + }, + { + "name": "书面挪威语", + "code": "nob", + "detect": True + + }, + { + "name": "斯洛伐克语", + "code": "sk", + "detect": True + + }, + { + "name": "斯洛文尼亚语", + "code": "slo", + "detect": True + + }, + { + "name": "斯瓦希里语", + "code": "swa", + "detect": True + + }, + { + "name": "塞尔维亚语(西里尔)", + "code": "src", + "detect": False + }, + { + "name": "索马里语", + "code": "som", + "detect": True + + }, + { + "name": "苏格兰语", + "code": "sco", + "detect": False + }, + { + "name": "泰语", + "code": "th", + "detect": True + + }, + { + "name": "土耳其语", + "code": "tr", + "detect": True + + }, + { + "name": "塔吉克语", + "code": "tgk", + "detect": True + + }, + { + "name": "泰米尔语", + "code": "tam", + "detect": True + + }, + { + "name": "他加禄语", + "code": "tgl", + "detect": True + + }, + { + "name": "提格利尼亚语", + "code": "tir", + "detect": False + }, + { + "name": "泰卢固语", + "code": "tel", + "detect": True + + }, + { + "name": "突尼斯阿拉伯语", + "code": "tua", + "detect": False + }, + { + "name": "土库曼语", + "code": "tuk", + "detect": False + }, + { + "name": "乌克兰语", + "code": "ukr", + "detect": True + + }, + { + "name": "瓦隆语", + "code": "wln", + "detect": True + + }, + { + "name": "威尔士语", + "code": "wel", + "detect": True + + }, + { + "name": "文达语", + "code": "ven", + "detect": False + }, + { + "name": "沃洛夫语", + "code": "wol", + "detect": False + }, + { + "name": "乌尔都语", + "code": "urd", + "detect": True + + }, + { + "name": "西班牙语", + "code": "spa", + "detect": True + + }, + { + "name": "希伯来语", + "code": "heb", + "detect": True + + }, + { + "name": "希腊语", + "code": "el", + "detect": True + + }, + { + "name": "匈牙利语", + "code": "hu", + "detect": True + + }, + { + "name": "西弗里斯语", + "code": "fry", + "detect": True + + }, + { + "name": "西里西亚语", + "code": "sil", + "detect": False + }, + { + "name": "希利盖农语", + "code": "hil", + "detect": False + }, + { + "name": "下索布语", + "code": "los", + "detect": False + }, + { + "name": "夏威夷语", + "code": "haw", + "detect": False + }, + { + "name": "新挪威语", + "code": "nno", + "detect": True + + }, + { + "name": "西非书面语", + "code": "nqo", + "detect": False + }, + { + "name": "信德语", + "code": "snd", + "detect": False + }, + { + "name": "修纳语", + "code": "sna", + "detect": False + }, + { + "name": "宿务语", + "code": "ceb", + "detect": False + }, + { + "name": "叙利亚语", + "code": "syr", + "detect": False + }, + { + "name": "巽他语", + "code": "sun", + "detect": False + }, + { + "name": "英语", + "code": "en", + "detect": True + + }, + { + "name": "印地语", + "code": "hi", + "detect": True + + }, + { + "name": "印尼语", + "code": "id", + "detect": True + + }, + { + "name": "意大利语", + "code": "it", + "detect": True + + }, + { + "name": "越南语", + "code": "vie", + "detect": True + + }, + { + "name": "意第绪语", + "code": "yid", + "detect": False + }, + { + "name": "因特语", + "code": "ina", + "detect": False + }, + { + "name": "亚齐语", + "code": "ach", + "detect": False + }, + { + "name": "印古什语", + "code": "ing", + "detect": False + }, + { + "name": "伊博语", + "code": "ibo", + "detect": False + }, + { + "name": "伊多语", + "code": "ido", + "detect": False + }, + { + "name": "约鲁巴语", + "code": "yor", + "detect": False + }, + { + "name": "亚美尼亚语", + "code": "arm", + "detect": True + + }, + { + "name": "伊努克提图特语", + "code": "iku", + "detect": False + }, + { + "name": "中文(简体)", + "code": "zh", + "detect": True + + }, + { + "name": "中文(繁体)", + "code": "cht", + "detect": True + + }, + { + "name": "中文(文言文)", + "code": "wyw", + "detect": True + + }, + { + "name": "中文(粤语)", + "code": "yue", + "detect": True + + }, + { + "name": "扎扎其语", + "code": "zaz", + "detect": False + }, + { + "name": "中古法语", + "code": "frm", + "detect": False + }, + { + "name": "祖鲁语", + "code": "zul", + "detect": False + }, + { + "name": "爪哇语", + "code": "jav", + "detect": False + }, + { + "name": "自动检测", + "code": "auto", + "detect": True + + } +] +} \ No newline at end of file diff --git a/app/__pycache__/apps.cpython-312.pyc b/app/__pycache__/apps.cpython-312.pyc index efa9a5f..5babf99 100644 Binary files a/app/__pycache__/apps.cpython-312.pyc and b/app/__pycache__/apps.cpython-312.pyc differ diff --git a/app/__pycache__/models.cpython-312.pyc b/app/__pycache__/models.cpython-312.pyc index 46df252..c9965e7 100644 Binary files a/app/__pycache__/models.cpython-312.pyc and b/app/__pycache__/models.cpython-312.pyc differ diff --git a/app/__pycache__/urls.cpython-312.pyc b/app/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..f80ba61 Binary files /dev/null and b/app/__pycache__/urls.cpython-312.pyc differ diff --git a/chunyu_project/__pycache__/asgi.cpython-312.pyc b/chunyu_project/__pycache__/asgi.cpython-312.pyc new file mode 100644 index 0000000..f1e22e9 Binary files /dev/null and b/chunyu_project/__pycache__/asgi.cpython-312.pyc differ diff --git a/chunyu_project/__pycache__/settings.cpython-312.pyc b/chunyu_project/__pycache__/settings.cpython-312.pyc index 429287c..f49c08c 100644 Binary files a/chunyu_project/__pycache__/settings.cpython-312.pyc and b/chunyu_project/__pycache__/settings.cpython-312.pyc differ diff --git a/chunyu_project/__pycache__/urls.cpython-312.pyc b/chunyu_project/__pycache__/urls.cpython-312.pyc index a059a93..cadcf29 100644 Binary files a/chunyu_project/__pycache__/urls.cpython-312.pyc and b/chunyu_project/__pycache__/urls.cpython-312.pyc differ diff --git a/chunyu_project/__pycache__/wsgi.cpython-312.pyc b/chunyu_project/__pycache__/wsgi.cpython-312.pyc deleted file mode 100644 index 30f5668..0000000 Binary files a/chunyu_project/__pycache__/wsgi.cpython-312.pyc and /dev/null differ diff --git a/chunyu_project/settings.py b/chunyu_project/settings.py index 35f570b..665a729 100644 --- a/chunyu_project/settings.py +++ b/chunyu_project/settings.py @@ -28,6 +28,11 @@ SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.Argon2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', +] + # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-j+-psq=m++l7hup73k1eg+wm-b&2_)!+_^o=6(=xc$1px*kqt@' @@ -67,12 +72,12 @@ ALLOWED_HOSTS = ["*"] CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", # 例如:您的React/Vue开发服务器 "http://127.0.0.1:3000", + "http://192.168.1.4:3000", + "https://192.168.1.4:3000", "http://*", "https://*" ] -# CORS设置 -CORS_ALLOW_ALL_ORIGINS = False # 允许凭据 CORS_ALLOW_CREDENTIALS = True @@ -143,6 +148,17 @@ SIMPLE_JWT = { 'USER_ID_CLAIM': 'user_id', } +# 邮件部分 + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 使用SMTP +EMAIL_HOST = 'smtp.qq.com' # 需要真实的SMTP服务器 +EMAIL_PORT = 465 +EMAIL_USE_SSL = True +EMAIL_HOST_USER = 'cs10086086@qq.com' # 真实邮箱账号 +EMAIL_HOST_PASSWORD = 'ldzpymytsinkfjhb' # 真实密码/授权码 +EMAIL_USE_TLS = False + + AUTH_USER_MODEL = 'user.FUser' INSTALLED_APPS = [ @@ -155,6 +171,8 @@ INSTALLED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.sites', # 第三方应用 @@ -168,7 +186,122 @@ INSTALLED_APPS = [ ] + +# Database +# https://docs.djangoproject.com/en/6.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'chunyu_project', # 数据库名 + 'USER': 'root', # 数据库用户名 + 'PASSWORD': 'mysql_NpyMCS', # 数据库密码 + 'HOST': '192.168.1.4', # 数据库主机,留空默认为localhost + 'PORT': '3306', # 数据库端口,留空默认为3306 + 'OPTIONS': { + 'charset': 'utf8mb4', + 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", + 'connect_timeout': 10, # 连接超时时间(秒) + }, + 'CONN_MAX_AGE': 3600, # 连接池最大存活时间(秒),0表示每次请求后关闭 + 'CONN_HEALTH_CHECKS': True, # 连接健康检查 + 'ATOMIC_REQUESTS': False, # 是否将每个HTTP请求包装在事务中 + 'AUTOCOMMIT': True, # 是否自动提交事务 + 'TIME_ZONE': 'Asia/Shanghai', + } +} + + +# Redis配置 +REDIS_HOST = '192.168.1.4' # Redis服务器地址 +REDIS_PORT = 6379 # Redis端口 +REDIS_DB = 0 # Redis数据库编号 +REDIS_PASSWORD = "+Xy2173240465" # Redis密码,没有密码则设为None +SESSION_DB = 1 +CELERY_BROKER_DB = 2 +CELERY_RESULT_BACKEND_DB = 2 + +# Django缓存配置(使用Redis作为缓存后端) +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}", + + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "PASSWORD": REDIS_PASSWORD, + "PARSER_CLASS": "redis.connection._HiredisParser", # 使用hiredis提高性能 + 'CONNECTION_POOL_CLASS': 'redis.BlockingConnectionPool', + 'CONNECTION_POOL_CLASS_KWARGS': { + 'max_connections': 50, + 'timeout': 20, + }, + 'MAX_CONNECTIONS': 50, + 'PICKLE_VERSION': -1, + 'SOCKET_CONNECT_TIMEOUT': 5, + 'SOCKET_TIMEOUT': 5, + 'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor', + 'SERIALIZER': 'django_redis.serializers.json.JSONSerializer', + 'COMPRESS_MIN_LENGTH': 1024 * 10, + 'IGNORE_EXCEPTIONS': True, # 缓存宕机时不崩溃 + }, + "KEY_PREFIX": "django_", # 缓存键前缀 + "VERSION": 1, # 缓存版本 + }, + 'session': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': f"redis://{REDIS_HOST}:{REDIS_PORT}/{SESSION_DB}", + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + } + }, + 'celery': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://:password@redis-celery:6379/0', + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + } + } +} + +# 会话配置 +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' +SESSION_CACHE_ALIAS = 'session' +SESSION_COOKIE_AGE = 1209600 +SESSION_COOKIE_SECURE = True +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = 'Lax' + +# Celery配置 +CELERY_BROKER_URL = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{CELERY_BROKER_DB}' +CELERY_RESULT_BACKEND = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{CELERY_RESULT_BACKEND_DB}' +CELERY_CACHE_BACKEND = 'celery' +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True + +# 缓存中间件 +CACHE_MIDDLEWARE_ALIAS = 'default' +CACHE_MIDDLEWARE_SECONDS = 60 * 15 +CACHE_MIDDLEWARE_KEY_PREFIX = 'chunyu_site' + +# 安全头 +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True +X_FRAME_OPTIONS = 'DENY' + +# 定时任务:预热缓存 +CELERY_BEAT_SCHEDULE = { + 'warm-up-cache-every-hour': { + 'task': 'core.tasks.warm_up_cache', + 'schedule': 3600.0, # 每小时 + }, + 'generate-daily-report': { + 'task': 'reports.tasks.generate_daily_report', + 'schedule': 86400.0, # 每天 + }, +} + MIDDLEWARE = [ + 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', @@ -177,8 +310,13 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware', ] +CACHE_MIDDLEWARE_ALIAS = 'default' +CACHE_MIDDLEWARE_SECONDS = 60 * 15 # 15分钟 +CACHE_MIDDLEWARE_KEY_PREFIX = 'mysite' + ROOT_URLCONF = 'chunyu_project.urls' TEMPLATES = [ @@ -200,16 +338,13 @@ TEMPLATES = [ ASGI_APPLICATION = 'chunyu_project.asgi.application' -# Database -# https://docs.djangoproject.com/en/6.0/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} +# 使用Redis作为会话后端 +SESSION_ENGINE = "django.contrib.sessions.backends.cache" +SESSION_CACHE_ALIAS = "default" +# 使用Redis作为消息代理 +CELERY_BROKER_URL = 'redis://localhost:6379/0' +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' # Password validation # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators @@ -246,3 +381,12 @@ USE_TZ = True # https://docs.djangoproject.com/en/6.0/howto/static-files/ STATIC_URL = 'static/' + +STATICFILES_DIRS = [ + BASE_DIR / "static", +] + +STATIC_ROOT = BASE_DIR / "staticfiles" + + + diff --git a/user/__pycache__/__init__.cpython-312.pyc b/user/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..2c758b6 Binary files /dev/null and b/user/__pycache__/__init__.cpython-312.pyc differ diff --git a/user/__pycache__/admin.cpython-312.pyc b/user/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..e609062 Binary files /dev/null and b/user/__pycache__/admin.cpython-312.pyc differ diff --git a/user/__pycache__/apps.cpython-312.pyc b/user/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..d07179c Binary files /dev/null and b/user/__pycache__/apps.cpython-312.pyc differ diff --git a/user/__pycache__/models.cpython-312.pyc b/user/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..b5e3266 Binary files /dev/null and b/user/__pycache__/models.cpython-312.pyc differ diff --git a/user/__pycache__/urls.cpython-312.pyc b/user/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..84bfd7b Binary files /dev/null and b/user/__pycache__/urls.cpython-312.pyc differ diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py index 525cdf0..7615b7e 100644 --- a/user/migrations/0001_initial.py +++ b/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.12 on 2026-03-25 21:58 +# Generated by Django 5.2.12 on 2026-04-04 09:10 import django.contrib.auth.models import django.contrib.auth.validators diff --git a/user/migrations/0002_fuser_issetpassword.py b/user/migrations/0002_fuser_issetpassword.py new file mode 100644 index 0000000..99770bb --- /dev/null +++ b/user/migrations/0002_fuser_issetpassword.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.12 on 2026-04-04 13:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='fuser', + name='isSetPassword', + field=models.BooleanField(blank=True, default=False, verbose_name='是否主动设置过密码'), + preserve_default=False, + ), + ] diff --git a/user/migrations/__pycache__/0001_initial.cpython-312.pyc b/user/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..db19b30 Binary files /dev/null and b/user/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/user/migrations/__pycache__/0002_fuser_issetpassword.cpython-312.pyc b/user/migrations/__pycache__/0002_fuser_issetpassword.cpython-312.pyc new file mode 100644 index 0000000..e5c15e8 Binary files /dev/null and b/user/migrations/__pycache__/0002_fuser_issetpassword.cpython-312.pyc differ diff --git a/user/migrations/__pycache__/__init__.cpython-312.pyc b/user/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..7018955 Binary files /dev/null and b/user/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/user/models.py b/user/models.py index 5d1c288..9db1e7e 100644 --- a/user/models.py +++ b/user/models.py @@ -8,6 +8,7 @@ class FUser(AbstractUser): phone_number = models.CharField(max_length=15, blank=True, verbose_name='手机号') avatar = models.ImageField(upload_to='avatars/', blank=True, null=True, verbose_name='头像') bio = models.TextField(max_length=500, blank=True, verbose_name='个人简介') + isSetPassword = models.BooleanField(blank=True, verbose_name='是否主动设置过密码') # class Meta: # # 定义在管理后台显示的名称 diff --git a/user/urls.py b/user/urls.py index 3a944c4..9546173 100644 --- a/user/urls.py +++ b/user/urls.py @@ -6,11 +6,13 @@ from rest_framework_simplejwt.views import ( TokenVerifyView, ) +from .views.user import SendUserEmail + urlpatterns = [ # JWT 认证接口 - path('user/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path('user/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('user/token/verify/', TokenVerifyView.as_view(), name='token_verify'), - + path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('token/verify/', TokenVerifyView.as_view(), name='token_verify'), + path('register_send/', SendUserEmail.as_view(), name='send_user_email'), ] \ No newline at end of file diff --git a/user/views.py b/user/views.py deleted file mode 100644 index 956f8b0..0000000 --- a/user/views.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.urls import path, include -from rest_framework_simplejwt.views import ( - TokenObtainPairView, - TokenRefreshView, - TokenVerifyView, -) - -urlpatterns = [ - # JWT 认证接口 - path('user/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path('user/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('user/token/verify/', TokenVerifyView.as_view(), name='token_verify'), - - -] \ No newline at end of file diff --git a/user/views/__init__.py b/user/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/views/__pycache__/__init__.cpython-312.pyc b/user/views/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..9c2ff45 Binary files /dev/null and b/user/views/__pycache__/__init__.cpython-312.pyc differ diff --git a/user/views/__pycache__/user.cpython-312.pyc b/user/views/__pycache__/user.cpython-312.pyc new file mode 100644 index 0000000..aecfc3d Binary files /dev/null and b/user/views/__pycache__/user.cpython-312.pyc differ diff --git a/user/views/user.py b/user/views/user.py new file mode 100644 index 0000000..bc7f268 --- /dev/null +++ b/user/views/user.py @@ -0,0 +1,75 @@ +from rest_framework.decorators import permission_classes +from rest_framework.parsers import MultiPartParser, FormParser +from rest_framework.permissions import AllowAny +from rest_framework.views import APIView + +from rest_framework.response import Response +from rest_framework import status + +from django.core.mail import EmailMessage + +import user.models +# import os +from utils import RandCode + +from django.core.cache import caches + +# 获取不同的缓存实例 +default_cache = caches['default'] +session_cache = caches['session'] +celery_cache = caches['celery'] + +from ..models import FUser + +class SendUserEmail(APIView): + permission_classes = [AllowAny] + def post(self, request): + to_email = request.data.get('to_email', None) + print(to_email) + + if to_email is None: + return Response(status=status.HTTP_400_BAD_REQUEST) + + subject = 'test邮件' + code = RandCode.get_digit_characters_code_8() + body = f"你的验证码为 {code}, 该验证码仅用于账号注册, 十分钟内有效." + from_email = 'cs10086086@qq.com' + to_email = [to_email] + + default_cache.set(f"register_{to_email[0]}", code, timeout=600) + + email = EmailMessage( + subject=subject, + body=body, + from_email=from_email, + to=to_email, + ) + + email.send() + return Response('邮件发送成功!', status=status.HTTP_201_CREATED) + +class RegisterByEmail(APIView): + permission_classes = [AllowAny] + def post(self, request): + code = request.data.get('code', None) + to_email = request.data.get('to_email', None) + + if code is None or to_email is None: + return Response(status=status.HTTP_400_BAD_REQUEST) + + vcode = default_cache.get(f"register_{to_email}") + if vcode is None: + return Response(status=status.HTTP_400_BAD_REQUEST) + + if code == vcode: + password = RandCode.get_alphanumeric_characters_code_8() + FUser.objects.create_user( + username=to_email, + email=to_email, + password=password + ) + return Response("创建成功.",status=status.HTTP_200_OK) + else: + return Response(status=status.HTTP_400_BAD_REQUEST) + + diff --git a/utils/RandCode.py b/utils/RandCode.py new file mode 100644 index 0000000..d5b3da8 --- /dev/null +++ b/utils/RandCode.py @@ -0,0 +1,99 @@ +# 包含所有大小写字母、数字和常见可见特殊字符 +all_visible_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,.<>?/~`\"'\\" +# 包含所有大小写字母和数字 +alphanumeric_characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +# 包含所有数字 +digit_characters = "0123456789" +# 包含常见可见特殊字符(不包括空格) +special_characters = "!@#$%^&*()-_=+[]{}|;:,.<>?/~`\"'\\" +# 包含26个大写字母 +uppercase_only = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +# 包含26个小写字母 +lowercase_only = "abcdefghijklmnopqrstuvwxyz" +# 包含26个小写字母和0-9 数字 +lowercase_digits = "abcdefghijklmnopqrstuvwxyz0123456789" +# 包含26个大写字母和0-9 数字 +uppercase_digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +import secrets + + +def get_all_visible_characters_code_8(): + code = ''.join(secrets.choice(all_visible_characters) for _ in range(8)) + + while(code[0] == '0'): + first_digit = secrets.choice(all_visible_characters) # 1-9 + code = first_digit + code[1:] + + return code + +def get_all_visible_characters_code_6(): + + code = ''.join(secrets.choice(all_visible_characters) for _ in range(6)) + + while(code[0] == '0'): + first_digit = secrets.choice(all_visible_characters) # 1-9 + code = first_digit + code[1:] + + return code + +def get_all_visible_characters_code(length): + code = ''.join(secrets.choice(all_visible_characters) for _ in range(length)) + + while(code[0] == '0'): + first_digit = secrets.choice(all_visible_characters) # 1-9 + code = first_digit + code[1:] + + return code + +def get_alphanumeric_characters_code(length): + code = ''.join(secrets.choice(alphanumeric_characters) for _ in range(length)) + + while(code[0] == '0'): + first_digit = secrets.choice(alphanumeric_characters) # 1-9 + code = first_digit + code[1:] + + return code + +def get_alphanumeric_characters_code_6(): + code = ''.join(secrets.choice(alphanumeric_characters) for _ in range(6)) + + while(code[0] == '0'): + first_digit = secrets.choice(alphanumeric_characters) # 1-9 + code = first_digit + code[1:] + + return code + +def get_alphanumeric_characters_code_8(): + code = ''.join(secrets.choice(alphanumeric_characters) for _ in range(8)) + + while(code[0] == '0'): + first_digit = secrets.choice(alphanumeric_characters) # 1-9 + code = first_digit + code[1:] + + return code + +def get_digit_characters_code(length): + code = ''.join(secrets.choice(digit_characters) for _ in range(length)) + while(code[0] == '0'): + first_digit = secrets.choice(digit_characters) + code = first_digit + code[1:] + + return code + +def get_digit_characters_code_6(): + code = ''.join(secrets.choice(digit_characters) for _ in range(6)) + + while(code[0] == '0'): + first_digit = secrets.choice(digit_characters) + code = first_digit + code[1:] + + return code + +def get_digit_characters_code_8(): + code = ''.join(secrets.choice(digit_characters) for _ in range(8)) + while(code[0] == '0'): + first_digit = secrets.choice(digit_characters) + code = first_digit + code[1:] + + return code \ No newline at end of file diff --git a/utils/__pycache__/RandCode.cpython-312.pyc b/utils/__pycache__/RandCode.cpython-312.pyc new file mode 100644 index 0000000..dbb781e Binary files /dev/null and b/utils/__pycache__/RandCode.cpython-312.pyc differ