1
This commit is contained in:
Generated
+64
-32
@@ -5,11 +5,22 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="a7465ceb-8d86-4d89-899e-8242cccf605d" name="更改" comment="test">
|
||||
<change afterPath="$PROJECT_DIR$/api/views/BaiduFanyiView.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/api/4c9da042-07f7-4b23-8e8e-8db67cdad1a8.png" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/api/views/info/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/api/views/info/baidu_fanyi_appid.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/api/views/info/baidu_lang_info.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/user/views/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/user/views/user.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/RandCode.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/api/tests.py" beforeDir="false" afterPath="$PROJECT_DIR$/api/tests.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/api/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/api/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/api/views/GetIPDataView.py" beforeDir="false" afterPath="$PROJECT_DIR$/api/views/GetIPDataView.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/api/views/BaiduFanyiView.py" beforeDir="false" afterPath="$PROJECT_DIR$/api/views/BaiduFanyiView.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/chunyu_project/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/chunyu_project/settings.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/user/migrations/0001_initial.py" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/user/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/user/models.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/user/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/user/urls.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/user/views.py" beforeDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -41,40 +52,40 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"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/user",
|
||||
"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"
|
||||
<component name="PropertiesComponent">{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\12914\PycharmProjects\chunyu_project\api" />
|
||||
<recent name="C:\Users\12914\PycharmProjects\chunyu_project\user" />
|
||||
<recent name="C:\Users\12914\PycharmProjects\chunyu_project\chunyu_project" />
|
||||
<recent name="C:\Users\12914\PycharmProjects\chunyu_project\api" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\12914\PycharmProjects\chunyu_project\user" />
|
||||
@@ -134,7 +145,12 @@
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1774436691939</updated>
|
||||
<workItem from="1774436692980" duration="11637000" />
|
||||
<workItem from="1774474993123" duration="15275000" />
|
||||
<workItem from="1774474993123" duration="16653000" />
|
||||
<workItem from="1774593859979" duration="23580000" />
|
||||
<workItem from="1774733219728" duration="5609000" />
|
||||
<workItem from="1775124800510" duration="1393000" />
|
||||
<workItem from="1775290631675" duration="21356000" />
|
||||
<workItem from="1775375846640" duration="2674000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="test">
|
||||
<option name="closed" value="true" />
|
||||
@@ -160,7 +176,23 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1774565193807</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="4" />
|
||||
<task id="LOCAL-00004" summary="test">
|
||||
<option name="closed" value="true" />
|
||||
<created>1774572888898</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1774572888898</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="test">
|
||||
<option name="closed" value="true" />
|
||||
<created>1774573186526</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1774573186526</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="6" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -171,6 +203,6 @@
|
||||
<option name="LAST_COMMIT_MESSAGE" value="test" />
|
||||
</component>
|
||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||
<SUITE FILE_PATH="coverage/chunyu_project$tests.coverage" NAME="tests 覆盖结果" MODIFIED="1774462107469" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/app" />
|
||||
<SUITE FILE_PATH="coverage/chunyu_project$tests.coverage" NAME="tests 覆盖结果" MODIFIED="1774621019782" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/api" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+18
-2
@@ -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)
|
||||
+21
-1
@@ -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()),
|
||||
|
||||
|
||||
|
||||
]
|
||||
+206
-5
@@ -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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
appid = '20220718001275847'
|
||||
appkey = 'CJHmvf7qGs3szQy32cMg'
|
||||
|
||||
endpoint = 'http://api.fanyi.baidu.com'
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+155
-11
@@ -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"
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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:
|
||||
# # 定义在管理后台显示的名称
|
||||
|
||||
+6
-4
@@ -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'),
|
||||
|
||||
]
|
||||
@@ -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'),
|
||||
|
||||
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
Binary file not shown.
Reference in New Issue
Block a user