feat: add email templates for verification codes in English, Chinese, and Traditional Chinese

This commit is contained in:
wxg0103 2025-07-10 15:01:56 +08:00
parent 28a909e226
commit fe78de5d3c
6 changed files with 395 additions and 152 deletions

View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="email code" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color: #ececec; padding: 35px">
<table
cellpadding="0"
style="
width: 800px;
height: 100%;
margin: 0px auto;
text-align: left;
position: relative;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
font-size: 14px;
line-height: 1.5;
box-shadow: rgb(153, 153, 153) 0px 0px 5px;
border-collapse: collapse;
background-position: initial initial;
background-repeat: initial initial;
background: #fff;
"
>
<tbody>
<tr>
<th
valign="middle"
style="
background: linear-gradient(
90deg,
#ebf1ff 24.34%,
#e5fbf8 56.18%,
#f2ebfe 90.18%
);
height: 25px;
line-height: 25px;
padding: 15px 35px;
border-bottom-width: 1px;
border-bottom-color: rgba(51, 112, 255);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
"
>
<div
style="
width: 500px;
background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);
-webkit-background-clip: text;
font-size: 16px;
-webkit-text-fill-color: transparent;
font-style: normal;
font-weight: 900;
color: #1f2329;
"
>
Intelligent Knowledge Q&A System
</div>
</th>
</tr>
<tr>
<td style="word-break: break-all">
<div
style="
padding: 25px 35px 40px;
background-color: #fff;
opacity: 0.8;
"
>
<h2 style="margin: 5px 0px">
<font color="#333333" style="line-height: 20px">
<font style="line-height: 22px" size="4">
Dear user:</font
>
</font>
</h2>
<!-- 中文 -->
<p>
<font color="#ff8c00" style="font-weight: 900">${code}</font
>&nbsp;&nbsp;This is your dynamic verification code. Please fill it in within 30 minutes. To protect the security of your account, please do not provide this verification code to anyone.
</p>
<br />
<div style="width: 100%; margin: 0 auto">
<div
style="
padding: 10px 10px 0;
border-top: 1px solid #ccc;
color: #747474;
margin-bottom: 20px;
line-height: 1.3em;
font-size: 12px;
text-align: right;
"
>
<p>Intelligent knowledge base project team</p>
<br />
<p>
Please do not reply to this system email<br />
</p>
<!--<p>©***</p>-->
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="email code" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color: #ececec; padding: 35px">
<table
cellpadding="0"
style="
width: 800px;
height: 100%;
margin: 0px auto;
text-align: left;
position: relative;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
font-size: 14px;
line-height: 1.5;
box-shadow: rgb(153, 153, 153) 0px 0px 5px;
border-collapse: collapse;
background-position: initial initial;
background-repeat: initial initial;
background: #fff;
"
>
<tbody>
<tr>
<th
valign="middle"
style="
background: linear-gradient(
90deg,
#ebf1ff 24.34%,
#e5fbf8 56.18%,
#f2ebfe 90.18%
);
height: 25px;
line-height: 25px;
padding: 15px 35px;
border-bottom-width: 1px;
border-bottom-color: rgba(51, 112, 255);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
"
>
<div
style="
width: 230px;
background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);
-webkit-background-clip: text;
font-size: 24px;
-webkit-text-fill-color: transparent;
font-style: normal;
font-weight: 900;
color: #1f2329;
"
>
智能知识库问答系统
</div>
</th>
</tr>
<tr>
<td style="word-break: break-all">
<div
style="
padding: 25px 35px 40px;
background-color: #fff;
opacity: 0.8;
"
>
<h2 style="margin: 5px 0px">
<font color="#333333" style="line-height: 20px">
<font style="line-height: 22px" size="4">
尊敬的用户:</font
>
</font>
</h2>
<!-- 中文 -->
<p>
<font color="#ff8c00" style="font-weight: 900">${code}</font
>&nbsp;&nbsp;为您的动态验证码请于30分钟内填写为保障帐户安全请勿向任何人提供此验证码。
</p>
<br />
<div style="width: 100%; margin: 0 auto">
<div
style="
padding: 10px 10px 0;
border-top: 1px solid #ccc;
color: #747474;
margin-bottom: 20px;
line-height: 1.3em;
font-size: 12px;
text-align: right;
"
>
<p>智能知识库项目组</p>
<br />
<p>
此为系统邮件,请勿回复<br />
</p>
<!--<p>©***</p>-->
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="email code" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color: #ececec; padding: 35px">
<table
cellpadding="0"
style="
width: 800px;
height: 100%;
margin: 0px auto;
text-align: left;
position: relative;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
font-size: 14px;
line-height: 1.5;
box-shadow: rgb(153, 153, 153) 0px 0px 5px;
border-collapse: collapse;
background-position: initial initial;
background-repeat: initial initial;
background: #fff;
"
>
<tbody>
<tr>
<th
valign="middle"
style="
background: linear-gradient(
90deg,
#ebf1ff 24.34%,
#e5fbf8 56.18%,
#f2ebfe 90.18%
);
height: 25px;
line-height: 25px;
padding: 15px 35px;
border-bottom-width: 1px;
border-bottom-color: rgba(51, 112, 255);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
"
>
<div
style="
width: 230px;
background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);
-webkit-background-clip: text;
font-size: 24px;
-webkit-text-fill-color: transparent;
font-style: normal;
font-weight: 900;
color: #1f2329;
"
>
智慧知識庫問答系統
</div>
</th>
</tr>
<tr>
<td style="word-break: break-all">
<div
style="
padding: 25px 35px 40px;
background-color: #fff;
opacity: 0.8;
"
>
<h2 style="margin: 5px 0px">
<font color="#333333" style="line-height: 20px">
<font style="line-height: 22px" size="4">
尊敬的用戶:</font
>
</font>
</h2>
<!-- 中文 -->
<p>
<font color="#ff8c00" style="font-weight: 900">${code}</font
>&nbsp;&nbsp;為您的動態驗證碼請於30分鐘內填寫為保障帳戶安全請勿向任何人提供此驗證碼。
</p>
<br />
<div style="width: 100%; margin: 0 auto">
<div
style="
padding: 10px 10px 0;
border-top: 1px solid #ccc;
color: #747474;
margin-bottom: 20px;
line-height: 1.3em;
font-size: 12px;
text-align: right;
"
>
<p>智慧知識庫專案組</p>
<br />
<p>
此為系統郵件,請勿回覆<br />
</p>
<!--<p>©***</p>-->
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

View File

@ -30,8 +30,7 @@ class VolcanicEngineTTIModelGeneralParams(BaseForm):
class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
access_key = forms.PasswordInputField('Access Key ID', required=True)
secret_key = forms.PasswordInputField('Secret Access Key', required=True)
api_key = forms.PasswordInputField('Api key', required=True)
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,
raise_exception=False):
@ -40,7 +39,7 @@ class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
raise AppApiException(ValidCode.valid_error.value,
gettext('{model_type} Model type is not supported').format(model_type=model_type))
for key in ['access_key', 'secret_key']:
for key in ['api_key']:
if key not in model_credential:
if raise_exception:
raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key))
@ -62,7 +61,7 @@ class VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):
return True
def encryption_dict(self, model: Dict[str, object]):
return {**model, 'secret_key': super().encryption(model.get('secret_key', ''))}
return {**model, 'api_key': super().encryption(model.get('api_key', ''))}
def get_model_params_setting_form(self, model_name):
return VolcanicEngineTTIModelGeneralParams()

View File

@ -7,122 +7,21 @@ pip install asyncio
pip install websockets
'''
import datetime
import hashlib
import hmac
import json
import logging
import sys
from typing import Dict
import requests
from common.utils.logger import maxkb_logger
from models_provider.base_model_provider import MaxKBBaseModel
from models_provider.impl.base_tti import BaseTextToImage
method = 'POST'
host = 'visual.volcengineapi.com'
region = 'cn-north-1'
endpoint = 'https://visual.volcengineapi.com'
service = 'cv'
req_key_dict = {
'general_v1.4': 'high_aes_general_v14',
'general_v2.0': 'high_aes_general_v20',
'general_v2.0_L': 'high_aes_general_v20_L',
'anime_v1.3': 'high_aes',
'anime_v1.3.1': 'high_aes',
}
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def getSignatureKey(key, dateStamp, regionName, serviceName):
kDate = sign(key.encode('utf-8'), dateStamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'request')
return kSigning
def formatQuery(parameters):
request_parameters_init = ''
for key in sorted(parameters):
request_parameters_init += key + '=' + parameters[key] + '&'
request_parameters = request_parameters_init[:-1]
return request_parameters
def signV4Request(access_key, secret_key, service, req_query, req_body):
if access_key is None or secret_key is None:
maxkb_logger.info('No access key is available.')
sys.exit()
t = datetime.datetime.utcnow()
current_date = t.strftime('%Y%m%dT%H%M%SZ')
# current_date = '20210818T095729Z'
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
canonical_uri = '/'
canonical_querystring = req_query
signed_headers = 'content-type;host;x-content-sha256;x-date'
payload_hash = hashlib.sha256(req_body.encode('utf-8')).hexdigest()
content_type = 'application/json'
canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + \
'\n' + 'x-content-sha256:' + payload_hash + \
'\n' + 'x-date:' + current_date + '\n'
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + \
'\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
algorithm = 'HMAC-SHA256'
credential_scope = datestamp + '/' + region + '/' + service + '/' + 'request'
string_to_sign = algorithm + '\n' + current_date + '\n' + credential_scope + '\n' + hashlib.sha256(
canonical_request.encode('utf-8')).hexdigest()
signing_key = getSignatureKey(secret_key, datestamp, region, service)
signature = hmac.new(signing_key, (string_to_sign).encode(
'utf-8'), hashlib.sha256).hexdigest()
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + \
credential_scope + ', ' + 'SignedHeaders=' + \
signed_headers + ', ' + 'Signature=' + signature
headers = {'X-Date': current_date,
'Authorization': authorization_header,
'X-Content-Sha256': payload_hash,
'Content-Type': content_type
}
# ************* SEND THE REQUEST *************
request_url = endpoint + '?' + canonical_querystring
maxkb_logger.info('\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++')
maxkb_logger.info('Request URL = ' + request_url)
try:
r = requests.post(request_url, headers=headers, data=req_body)
except Exception as err:
maxkb_logger.info(f'error occurred: {err}')
raise
else:
maxkb_logger.info('\nRESPONSE++++++++++++++++++++++++++++++++++++')
maxkb_logger.info(f'Response code: {r.status_code}\n')
# 使用 replace 方法将 \u0026 替换为 &
resp_str = r.text.replace("\\u0026", "&")
if r.status_code != 200:
raise Exception(f'Error: {resp_str}')
return json.loads(resp_str)['data']['image_urls']
from volcenginesdkarkruntime import Ark
class VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage):
access_key: str
secret_key: str
api_key: str
model_version: str
params: dict
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.access_key = kwargs.get('access_key')
self.secret_key = kwargs.get('secret_key')
self.api_key = kwargs.get('api_key')
self.model_version = kwargs.get('model_version')
self.params = kwargs.get('params')
@ -138,33 +37,25 @@ class VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage):
optional_params['params'][key] = value
return VolcanicEngineTextToImage(
model_version=model_name,
access_key=model_credential.get('access_key'),
secret_key=model_credential.get('secret_key'),
api_key=model_credential.get('api_key'),
**optional_params
)
def check_auth(self):
res = self.generate_image('生成一张小猫图片')
return True
def generate_image(self, prompt: str, negative_prompt: str = None):
# 请求Query按照接口文档中填入即可
query_params = {
'Action': 'CVProcess',
'Version': '2022-08-31',
}
formatted_query = formatQuery(query_params)
size = self.params.pop('size', '512*512').split('*')
body_params = {
"req_key": req_key_dict[self.model_version],
"prompt": prompt,
"model_version": self.model_version,
"return_url": True,
"width": int(size[0]),
"height": int(size[1]),
client = Ark(
# 此为默认路径,您可根据业务所在地域进行配置
base_url="https://ark.cn-beijing.volces.com/api/v3",
# 从环境变量中获取您的 API Key。此为默认方式您可根据需要进行修改
api_key=self.api_key,
)
file_urls = []
imagesResponse = client.images.generate(
model=self.model_version,
prompt=prompt,
**self.params
}
formatted_body = json.dumps(body_params)
return signV4Request(self.access_key, self.secret_key, service, formatted_query, formatted_body)
def is_cache_model(self):
return False
)
file_urls.append(imagesResponse.data[0].url)
return file_urls

View File

@ -55,23 +55,8 @@ model_info_list = [
ModelTypeConst.TTS,
volcanic_engine_tts_model_credential, VolcanicEngineTextToSpeech
),
ModelInfo('general_v2.0',
_('Universal 2.0-Vincent Diagram'),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
ModelInfo('general_v2.0_L',
_('Universal 2.0Pro-Vincent Chart'),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
ModelInfo('general_v1.4',
_('Universal 1.4-Vincent Chart'),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
ModelInfo('anime_v1.3.1',
_('Animation 1.3.1-Vincent Picture'),
ModelInfo('doubao-seedream-3-0-t2i-250415',
_(''),
ModelTypeConst.TTI,
volcanic_engine_tti_model_credential, VolcanicEngineTextToImage
),
@ -105,7 +90,9 @@ class VolcanicEngineModelProvider(IModelProvider):
return model_info_manage
def get_model_provide_info(self):
return ModelProvideInfo(provider='model_volcanic_engine_provider', name=_('volcano engine'), icon=get_file_content(
os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl', 'volcanic_engine_model_provider',
'icon',
'volcanic_engine_icon_svg')))
return ModelProvideInfo(provider='model_volcanic_engine_provider', name=_('volcano engine'),
icon=get_file_content(
os.path.join(PROJECT_DIR, "apps", 'models_provider', 'impl',
'volcanic_engine_model_provider',
'icon',
'volcanic_engine_icon_svg')))