Your commit message
This commit is contained in:
parent
06dfa7cd9d
commit
ca32eef513
23
MyApi/API_Log.py
Normal file
23
MyApi/API_Log.py
Normal file
@ -0,0 +1,23 @@
|
||||
import logging
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
|
||||
# 日志配置
|
||||
def get_logger(name, log_file, when='midnight', backup_count=7):
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
# 创建TimedRotatingFileHandler
|
||||
handler = TimedRotatingFileHandler(
|
||||
log_file,
|
||||
when=when, # 按天切割
|
||||
interval=1, # 每1天切割一次
|
||||
backupCount=backup_count # 保留7个备份
|
||||
)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
logger.addHandler(handler)
|
||||
return logger
|
||||
|
||||
|
190
MyApi/AiMssage.py
Normal file
190
MyApi/AiMssage.py
Normal file
@ -0,0 +1,190 @@
|
||||
from django.shortcuts import render
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
from dashscope import Application
|
||||
from django.http import JsonResponse
|
||||
from .models import User, ChatRecord
|
||||
from django.utils.crypto import get_random_string
|
||||
from Crypto.Cipher import AES
|
||||
import base64
|
||||
from .views import update_usage_count
|
||||
|
||||
def decrypt_param(x, t):
|
||||
# 创建AES解密器对象
|
||||
key = b'qw5w6SFE2D1jmxyd'
|
||||
iv = b'345GDFED433223DF'
|
||||
|
||||
# 检查 x 和 t 是否为空
|
||||
if not x or not t:
|
||||
return False
|
||||
|
||||
try:
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
# 将密文进行Base64解码
|
||||
ciphertext_bytes = base64.b64decode(x)
|
||||
# 使用解密器解密密文
|
||||
plaintext_bytes = cipher.decrypt(ciphertext_bytes)
|
||||
# 删除填充字符
|
||||
plaintext = plaintext_bytes.rstrip(b'\0').decode('utf-8')
|
||||
# 比较解密后的明文和 t
|
||||
if plaintext.rstrip('\x03') == t.rstrip('\x03'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"解密过程出现错误: {e}")
|
||||
return False
|
||||
|
||||
def call_bailian_app(request):
|
||||
body = request.body.decode("utf-8")
|
||||
data = json.loads(body)
|
||||
user_id = data.get("user_id")
|
||||
openid = data.get("openid")
|
||||
role_name = data.get("role", "AI助手") # 默认角色为“默认AI聊天”
|
||||
prompt = data.get("prompt")
|
||||
x = request.headers.get('X', '')
|
||||
t = request.headers.get('T', '')
|
||||
increment = -1
|
||||
function_type = 'call_bailian_app'
|
||||
result = update_usage_count(openid, increment, function_type)
|
||||
if decrypt_param(x, t):
|
||||
if result['success']:
|
||||
print(f"收到请求: user_id={user_id}, role_name={role_name}, prompt={prompt}")
|
||||
try:
|
||||
user = User.objects.get(nickname=user_id)
|
||||
print(f"找到用户: {user.nickname}")
|
||||
except User.DoesNotExist:
|
||||
print("用户不存在")
|
||||
return JsonResponse({"message": "用户不存在"}, status=404)
|
||||
|
||||
# 检查是否已有该用户和角色的会话
|
||||
conversation = ChatRecord.objects.filter(nickname=user.nickname, role=role_name).first()
|
||||
if not conversation:
|
||||
print(f"新会话ID")
|
||||
response = Application.call(
|
||||
app_id=get_app_id(role_name),
|
||||
prompt=prompt,
|
||||
api_key="sk-9458cae1bed7460c9780e52ca6005cae",
|
||||
)
|
||||
conversation_id = response.output.get('session_id')
|
||||
else:
|
||||
conversation_id = conversation.conversation_id
|
||||
response = Application.call(
|
||||
app_id=get_app_id(role_name),
|
||||
prompt=prompt,
|
||||
api_key="sk-9458cae1bed7460c9780e52ca6005cae",
|
||||
session_id=conversation_id
|
||||
)
|
||||
print(f"已有会话ID: {conversation_id}")
|
||||
|
||||
print(f"API调用响应状态: {response.status_code}")
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
print(f"API调用失败: request_id={response.request_id}, message={response.message}")
|
||||
return JsonResponse({
|
||||
"message": "调用应用程序失败",
|
||||
"request_id": response.request_id,
|
||||
"code": response.status_code,
|
||||
"details": response.message
|
||||
}, status=response.status_code)
|
||||
|
||||
ai_response = response.output.get('text') if response.output else "AI无回复"
|
||||
print(f"AI回复: {ai_response}")
|
||||
|
||||
ChatRecord.objects.create(
|
||||
openid=user.openid,
|
||||
nickname=user.nickname,
|
||||
conversation_id=conversation_id,
|
||||
role=role_name,
|
||||
message_content=prompt,
|
||||
is_response=False
|
||||
)
|
||||
|
||||
# 保存AI回复
|
||||
ChatRecord.objects.create(
|
||||
openid=user.openid,
|
||||
nickname=user.nickname,
|
||||
conversation_id=conversation_id,
|
||||
role=role_name,
|
||||
message_content=ai_response,
|
||||
is_response=True
|
||||
)
|
||||
|
||||
# 检索会话的最近10条消息
|
||||
last_messages = ChatRecord.objects.filter(nickname=user.nickname, conversation_id=conversation_id).order_by('-timestamp')[:10]
|
||||
last_messages_list = [
|
||||
{
|
||||
"content": msg.message_content,
|
||||
"is_response": msg.is_response,
|
||||
"role":msg.role,
|
||||
"timestamp": msg.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||||
} for msg in reversed(last_messages)
|
||||
]
|
||||
|
||||
print(f"最近10条消息: {last_messages_list}")
|
||||
|
||||
return JsonResponse({
|
||||
"message": "成功",
|
||||
"ai_response": ai_response,
|
||||
"conversation_id": conversation_id,
|
||||
"last_messages": last_messages_list
|
||||
}, status=HTTPStatus.OK)
|
||||
else:
|
||||
return JsonResponse(result)
|
||||
else:
|
||||
return JsonResponse({'error': '非法参数'}, status=400)
|
||||
|
||||
# 新增接口:获取用户和角色的最近10条聊天记录
|
||||
def get_recent_chat_records(request):
|
||||
body = request.body.decode("utf-8")
|
||||
data = json.loads(body)
|
||||
user_id = data.get("user_id")
|
||||
role_name = data.get("role", "AI助手") # 默认角色为“默认AI聊天”
|
||||
x = request.headers.get('X', '')
|
||||
t = request.headers.get('T', '')
|
||||
if decrypt_param(x, t):
|
||||
print(f"收到请求: user_id={user_id}, role_name={role_name}")
|
||||
|
||||
try:
|
||||
user = User.objects.get(nickname=user_id)
|
||||
print(f"找到用户: {user.nickname}")
|
||||
except User.DoesNotExist:
|
||||
print("用户不存在")
|
||||
return JsonResponse({"message": "用户不存在"}, status=404)
|
||||
|
||||
# 获取该用户和角色的最近10条聊天记录
|
||||
last_messages = ChatRecord.objects.filter(nickname=user.nickname, role=role_name).order_by('-timestamp')[:10]
|
||||
last_messages_list = [
|
||||
{
|
||||
"content": msg.message_content,
|
||||
"is_response": msg.is_response,
|
||||
"role": msg.role,
|
||||
"timestamp": msg.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||||
} for msg in reversed(last_messages)
|
||||
]
|
||||
|
||||
print(f"最近10条消息: {last_messages_list}")
|
||||
|
||||
return JsonResponse({
|
||||
"message": "成功",
|
||||
"last_messages": last_messages_list
|
||||
}, status=HTTPStatus.OK)
|
||||
else:
|
||||
return JsonResponse({'error': '非法参数'}, status=400)
|
||||
def get_app_id(role_name):
|
||||
role_app_id_map = {
|
||||
"法律咨询": "446cde34829e4d6490667b1271dee537",
|
||||
"行业分析师": "bdce7fba11b147ca846a1b4d1f5e7e72",
|
||||
"写工作报告": "f340613da5574815975acdc10bac4e47",
|
||||
"产品顾问": "f9b0612793e94a669fe7ad2fecbda50e",
|
||||
"写爆款文案": "2c8d6675a9794711a886a5e8cf714afe",
|
||||
"写公众号文章": "4fb69f6bd9bb49c9a6329202c6f28c84",
|
||||
"文案改写": "b94734c09c414814bc0505a4e5b636c6",
|
||||
"招聘助理": "c70caf3fd67b44c4947df70f05cf9f10",
|
||||
"AI助手": "92d23cd2c5d64ef5ac6ad02692fc510b",
|
||||
"小红书文案": "c5211023cbf64d548ea55a47c516007a",
|
||||
"写广告文案": "6c9770da90ec47979ce899cec49c396c",
|
||||
}
|
||||
|
||||
return role_app_id_map.get(role_name, "app_id_default")
|
||||
|
22
MyApi/WXBizDataCrypt.py
Normal file
22
MyApi/WXBizDataCrypt.py
Normal file
@ -0,0 +1,22 @@
|
||||
from base64 import b64decode
|
||||
from Crypto.Cipher import AES
|
||||
import json
|
||||
|
||||
class WXBizDataCrypt:
|
||||
def __init__(self, appid, session_key):
|
||||
self.appid = appid
|
||||
self.session_key = b64decode(session_key)
|
||||
|
||||
def decrypt(self, encrypted_data, iv):
|
||||
# AES解密
|
||||
try:
|
||||
cipher = AES.new(self.session_key, AES.MODE_CBC, b64decode(iv))
|
||||
decrypted = json.loads(self._unpad(cipher.decrypt(b64decode(encrypted_data))))
|
||||
if decrypted['watermark']['appid'] != self.appid:
|
||||
raise Exception('Invalid Buffer')
|
||||
return decrypted
|
||||
except Exception as e:
|
||||
raise Exception('Failed to decrypt data')
|
||||
|
||||
def _unpad(self, s):
|
||||
return s[:-ord(s[len(s)-1:])]
|
0
MyApi/__init__.py
Normal file
0
MyApi/__init__.py
Normal file
3
MyApi/admin.py
Normal file
3
MyApi/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
MyApi/apps.py
Normal file
6
MyApi/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MyapiConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'MyApi'
|
BIN
MyApi/demo.mp3
Normal file
BIN
MyApi/demo.mp3
Normal file
Binary file not shown.
BIN
MyApi/demo.mp4
Normal file
BIN
MyApi/demo.mp4
Normal file
Binary file not shown.
BIN
MyApi/demo.mp4.mp3
Normal file
BIN
MyApi/demo.mp4.mp3
Normal file
Binary file not shown.
615
MyApi/migrations/0001_initial.py
Normal file
615
MyApi/migrations/0001_initial.py
Normal file
@ -0,0 +1,615 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 04:23
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Administrator",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("username", models.CharField(max_length=150, unique=True)),
|
||||
("password", models.CharField(max_length=128)),
|
||||
("role", models.CharField(max_length=100)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("last_login", models.DateTimeField(blank=True, null=True)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
"db_table": "Administrator",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ApiCallLog",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"source",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[("index", "Index"), ("weixin", "Weixin")],
|
||||
max_length=100,
|
||||
null=True,
|
||||
verbose_name="来源",
|
||||
),
|
||||
),
|
||||
(
|
||||
"openid",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="openid"
|
||||
),
|
||||
),
|
||||
(
|
||||
"nickname",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="用户标识"
|
||||
),
|
||||
),
|
||||
(
|
||||
"wxid",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="微信id"
|
||||
),
|
||||
),
|
||||
(
|
||||
"wechat_alias",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="微信号"
|
||||
),
|
||||
),
|
||||
("api_name", models.CharField(max_length=255, verbose_name="调用接口")),
|
||||
(
|
||||
"is_successful",
|
||||
models.BooleanField(default=False, verbose_name="是否成功"),
|
||||
),
|
||||
("remarks", models.TextField(blank=True, null=True, verbose_name="备注")),
|
||||
(
|
||||
"call_time",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="调用时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "API调用日志",
|
||||
"verbose_name_plural": "API调用日志",
|
||||
"db_table": "api_call_log",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ChatRecord",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("openid", models.CharField(default=None, max_length=150)),
|
||||
("nickname", models.CharField(max_length=150, verbose_name="用户昵称")),
|
||||
(
|
||||
"conversation_id",
|
||||
models.CharField(max_length=100, verbose_name="会话ID"),
|
||||
),
|
||||
("role", models.CharField(max_length=100, verbose_name="角色")),
|
||||
("message_content", models.TextField(verbose_name="消息内容")),
|
||||
(
|
||||
"is_response",
|
||||
models.BooleanField(default=False, verbose_name="是否为AI回复"),
|
||||
),
|
||||
(
|
||||
"timestamp",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="时间戳"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "聊天记录",
|
||||
"verbose_name_plural": "聊天记录",
|
||||
"db_table": "chat_record",
|
||||
"ordering": ["-timestamp"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Copywriting",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"openid",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="openid"
|
||||
),
|
||||
),
|
||||
(
|
||||
"nickname",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="用户标识"
|
||||
),
|
||||
),
|
||||
("text_content", models.TextField(verbose_name="文本内容")),
|
||||
("source", models.CharField(max_length=255, verbose_name="来源")),
|
||||
(
|
||||
"tag",
|
||||
models.CharField(default=None, max_length=255, verbose_name="标签"),
|
||||
),
|
||||
("popularity", models.IntegerField(default=0, verbose_name="热度")),
|
||||
(
|
||||
"added_time",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="添加时间"),
|
||||
),
|
||||
(
|
||||
"is_approved",
|
||||
models.BooleanField(default=False, verbose_name="审核通过"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "文案库",
|
||||
"verbose_name_plural": "文案库",
|
||||
"db_table": "Copywriting",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FriendRequest",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("fromusername", models.CharField(max_length=255, verbose_name="微信ID")),
|
||||
(
|
||||
"alias",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="微信号"
|
||||
),
|
||||
),
|
||||
(
|
||||
"fromnickname",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="昵称"
|
||||
),
|
||||
),
|
||||
(
|
||||
"country",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="国家"
|
||||
),
|
||||
),
|
||||
(
|
||||
"province",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="省份"
|
||||
),
|
||||
),
|
||||
(
|
||||
"city",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="城市"
|
||||
),
|
||||
),
|
||||
(
|
||||
"sex",
|
||||
models.CharField(
|
||||
blank=True, max_length=1, null=True, verbose_name="性别"
|
||||
),
|
||||
),
|
||||
(
|
||||
"scene",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="来源"
|
||||
),
|
||||
),
|
||||
("time", models.DateTimeField(verbose_name="申请时间")),
|
||||
("towxid", models.CharField(max_length=255, verbose_name="目标微信ID")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "添加好友请求",
|
||||
"verbose_name_plural": "添加好友请求",
|
||||
"db_table": "FriendRequest",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="IDCounter",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("count", models.IntegerField(default=10000)),
|
||||
],
|
||||
options={
|
||||
"db_table": "IDCounter",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="MembershipType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
models.CharField(max_length=50, unique=True, verbose_name="卡类型"),
|
||||
),
|
||||
("title", models.CharField(max_length=100, verbose_name="标题")),
|
||||
("description", models.TextField(verbose_name="描述")),
|
||||
(
|
||||
"price",
|
||||
models.DecimalField(
|
||||
decimal_places=2, max_digits=10, verbose_name="价格"
|
||||
),
|
||||
),
|
||||
("duration_days", models.IntegerField(verbose_name="有效天数")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "会员卡类型",
|
||||
"verbose_name_plural": "会员卡类型",
|
||||
"db_table": "membership_type",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="RedemptionCard",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"code",
|
||||
models.CharField(max_length=100, unique=True, verbose_name="卡密"),
|
||||
),
|
||||
(
|
||||
"card_type",
|
||||
models.CharField(
|
||||
choices=[("member", "会员卡"), ("quota", "额度卡")],
|
||||
max_length=50,
|
||||
verbose_name="卡类型",
|
||||
),
|
||||
),
|
||||
(
|
||||
"created_date",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="生成日期"),
|
||||
),
|
||||
("expiry_date", models.DateTimeField(verbose_name="到期时间")),
|
||||
("is_used", models.BooleanField(default=False, verbose_name="是否已使用")),
|
||||
(
|
||||
"used_by_openid",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="使用者OpenID"
|
||||
),
|
||||
),
|
||||
(
|
||||
"used_by_nickname",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="使用者昵称"
|
||||
),
|
||||
),
|
||||
(
|
||||
"card_creation_time",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="卡密创建时间"),
|
||||
),
|
||||
(
|
||||
"validity_period",
|
||||
models.PositiveIntegerField(default=0, verbose_name="有效期"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "兑换卡",
|
||||
"verbose_name_plural": "兑换卡",
|
||||
"db_table": "redemption_card",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TransactionLog",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"transaction_no",
|
||||
models.CharField(max_length=255, unique=True, verbose_name="交易编号"),
|
||||
),
|
||||
(
|
||||
"transaction_status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "待处理"),
|
||||
("completed", "完成"),
|
||||
("failed", "失败"),
|
||||
],
|
||||
default="pending",
|
||||
max_length=50,
|
||||
verbose_name="交易状态",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_openid",
|
||||
models.CharField(max_length=150, verbose_name="用户OpenID"),
|
||||
),
|
||||
(
|
||||
"transaction_type",
|
||||
models.CharField(
|
||||
choices=[("member", "会员购买"), ("quota", "续费会员")],
|
||||
max_length=50,
|
||||
verbose_name="交易类型",
|
||||
),
|
||||
),
|
||||
(
|
||||
"transaction_amount",
|
||||
models.DecimalField(
|
||||
decimal_places=2, max_digits=10, verbose_name="交易金额"
|
||||
),
|
||||
),
|
||||
(
|
||||
"remark",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("day", "一天会员充值"),
|
||||
("week", "一周会员充值"),
|
||||
("month", "一月会员充值"),
|
||||
("season", "三月会员充值"),
|
||||
("year", "一年会员充值"),
|
||||
],
|
||||
null=True,
|
||||
verbose_name="备注",
|
||||
),
|
||||
),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "交易记录",
|
||||
"verbose_name_plural": "交易记录",
|
||||
"db_table": "transaction_log",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("openid", models.CharField(max_length=150, unique=True)),
|
||||
("wxid", models.CharField(blank=True, max_length=150, null=True)),
|
||||
(
|
||||
"wechat_number",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="微信号"
|
||||
),
|
||||
),
|
||||
("nickname", models.CharField(blank=True, max_length=150, null=True)),
|
||||
("gender", models.CharField(blank=True, max_length=10, null=True)),
|
||||
("region", models.CharField(blank=True, max_length=150, null=True)),
|
||||
("email", models.EmailField(blank=True, max_length=254, null=True)),
|
||||
("phone", models.CharField(blank=True, max_length=20, null=True)),
|
||||
("password", models.CharField(blank=True, max_length=128, null=True)),
|
||||
("scene", models.CharField(blank=True, max_length=100, null=True)),
|
||||
("is_member", models.BooleanField(default=False)),
|
||||
("member_start_time", models.BigIntegerField(blank=True, null=True)),
|
||||
("member_end_time", models.BigIntegerField(blank=True, null=True)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("usage_count", models.IntegerField(blank=True, default=0, null=True)),
|
||||
("coins", models.IntegerField(default=100, verbose_name="金币数量")),
|
||||
(
|
||||
"balance",
|
||||
models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0.0,
|
||||
max_digits=10,
|
||||
verbose_name="用户余额",
|
||||
),
|
||||
),
|
||||
(
|
||||
"inviter_nickname",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="邀请人昵称"
|
||||
),
|
||||
),
|
||||
(
|
||||
"invitees_count",
|
||||
models.IntegerField(default=0, verbose_name="邀请人数量"),
|
||||
),
|
||||
(
|
||||
"daily_video_quota",
|
||||
models.IntegerField(default=5, verbose_name="每日视频生成额度"),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
"db_table": "User",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="VideoExtractionRecord",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"extraction_link",
|
||||
models.URLField(max_length=2048, verbose_name="提取链接"),
|
||||
),
|
||||
(
|
||||
"source",
|
||||
models.CharField(
|
||||
choices=[("index", "Index"), ("weixin", "Weixin")],
|
||||
max_length=100,
|
||||
verbose_name="来源",
|
||||
),
|
||||
),
|
||||
(
|
||||
"openid",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, null=True, verbose_name="openid"
|
||||
),
|
||||
),
|
||||
(
|
||||
"nickname",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="用户标识"
|
||||
),
|
||||
),
|
||||
(
|
||||
"video_title",
|
||||
models.TextField(blank=True, null=True, verbose_name="视频标题"),
|
||||
),
|
||||
(
|
||||
"wxid",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="微信id"
|
||||
),
|
||||
),
|
||||
(
|
||||
"wechat_alias",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="微信号"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_successful",
|
||||
models.CharField(
|
||||
blank=True, max_length=255, null=True, verbose_name="是否提取成功"
|
||||
),
|
||||
),
|
||||
(
|
||||
"exception_reason",
|
||||
models.TextField(blank=True, null=True, verbose_name="异常原因"),
|
||||
),
|
||||
(
|
||||
"extraction_time",
|
||||
models.DateTimeField(auto_now_add=True, verbose_name="提取时间"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "视频提取记录",
|
||||
"verbose_name_plural": "视频提取记录",
|
||||
"db_table": "VideoExtractionRecord",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="VideoTask",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("openid", models.CharField(max_length=150)),
|
||||
("nickname", models.CharField(max_length=150)),
|
||||
("task_id", models.CharField(max_length=255, unique=True)),
|
||||
("task_type", models.CharField(max_length=50)),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("PENDING", "待处理"),
|
||||
("IN_PROGRESS", "处理中"),
|
||||
("RUNNING", "运行中"),
|
||||
("SUCCEEDED", "成功"),
|
||||
("FAILED", "失败"),
|
||||
],
|
||||
default="PENDING",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("text_prompt", models.TextField(null=True)),
|
||||
("width", models.IntegerField(null=True)),
|
||||
("height", models.IntegerField(null=True)),
|
||||
("motion_score", models.IntegerField(null=True)),
|
||||
("style", models.CharField(blank=True, max_length=255, null=True)),
|
||||
("seconds", models.IntegerField(null=True)),
|
||||
("image_url", models.TextField(blank=True, null=True)),
|
||||
("result_url", models.TextField(blank=True, null=True)),
|
||||
("qiniu_url", models.TextField(blank=True, null=True)),
|
||||
("progress", models.FloatField(default=0.0)),
|
||||
("error_message", models.TextField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
"db_table": "video_task",
|
||||
"ordering": ["-created_at"],
|
||||
},
|
||||
),
|
||||
]
|
87
MyApi/migrations/0002_assetlibrary.py
Normal file
87
MyApi/migrations/0002_assetlibrary.py
Normal file
@ -0,0 +1,87 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 06:06
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="AssetLibrary",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("qiniu_url", models.URLField(verbose_name="七牛云视频URL")),
|
||||
("original_url", models.URLField(verbose_name="原始URL")),
|
||||
("duration", models.FloatField(verbose_name="时长")),
|
||||
(
|
||||
"category",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("abandoned", "废弃"),
|
||||
("abstract_sculpture", "抽象"),
|
||||
("advertising", "广告"),
|
||||
("anime", "动漫"),
|
||||
("cine_lens", "电影镜头"),
|
||||
("cinematic", "电影"),
|
||||
("concept_art", "艺术"),
|
||||
("forestpunk", "赛博朋克"),
|
||||
("frost", "雪"),
|
||||
("graphite", "石墨"),
|
||||
("macro_photography", "宏观"),
|
||||
("pixel_art", "像素艺术"),
|
||||
("retro_photography", "复古"),
|
||||
("sci_fi_art", "科幻"),
|
||||
("thriller", "惊悚"),
|
||||
("35mm", "35mm"),
|
||||
("vector", "矢量"),
|
||||
("watercolor", "水彩"),
|
||||
],
|
||||
max_length=50,
|
||||
verbose_name="分类",
|
||||
),
|
||||
),
|
||||
(
|
||||
"description",
|
||||
models.TextField(blank=True, null=True, verbose_name="视频描述"),
|
||||
),
|
||||
(
|
||||
"generated_at",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="生成时间"
|
||||
),
|
||||
),
|
||||
("download_count", models.IntegerField(default=0, verbose_name="下载次数")),
|
||||
(
|
||||
"is_approved",
|
||||
models.BooleanField(default=False, verbose_name="是否审核"),
|
||||
),
|
||||
(
|
||||
"generated_by",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="MyApi.user",
|
||||
verbose_name="生成用户",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "素材库",
|
||||
"verbose_name_plural": "素材库",
|
||||
"db_table": "asset_library",
|
||||
"ordering": ["-generated_at"],
|
||||
},
|
||||
),
|
||||
]
|
17
MyApi/migrations/0003_alter_assetlibrary_generated_by.py
Normal file
17
MyApi/migrations/0003_alter_assetlibrary_generated_by.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 06:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0002_assetlibrary"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="generated_by",
|
||||
field=models.CharField(max_length=50, null=True, verbose_name="生成用户"),
|
||||
),
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 07:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0003_alter_assetlibrary_generated_by"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="original_url",
|
||||
field=models.TextField(verbose_name="原始URL"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="qiniu_url",
|
||||
field=models.TextField(verbose_name="七牛云视频URL"),
|
||||
),
|
||||
]
|
37
MyApi/migrations/0005_transcriptiontask.py
Normal file
37
MyApi/migrations/0005_transcriptiontask.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 07:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0004_alter_assetlibrary_original_url_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="TranscriptionTask",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("uuid", models.CharField(max_length=255)),
|
||||
("video_url", models.URLField()),
|
||||
("task_id", models.CharField(max_length=255)),
|
||||
("result", models.TextField(blank=True, null=True)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "文案提取任务",
|
||||
"verbose_name_plural": "文案提取任务",
|
||||
"db_table": "TranscriptionTask",
|
||||
},
|
||||
),
|
||||
]
|
17
MyApi/migrations/0006_alter_transcriptiontask_video_url.py
Normal file
17
MyApi/migrations/0006_alter_transcriptiontask_video_url.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 08:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0005_transcriptiontask"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="transcriptiontask",
|
||||
name="video_url",
|
||||
field=models.TextField(),
|
||||
),
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 10:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0006_alter_transcriptiontask_video_url"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="assetlibrary",
|
||||
name="description_zh",
|
||||
field=models.TextField(blank=True, null=True, verbose_name="中文描述"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="videotask",
|
||||
name="description_zh",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
17
MyApi/migrations/0008_videotask_is_processing.py
Normal file
17
MyApi/migrations/0008_videotask_is_processing.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 12:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0007_assetlibrary_description_zh_videotask_description_zh"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videotask",
|
||||
name="is_processing",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
@ -0,0 +1,91 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 20:52
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0008_videotask_is_processing"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="membershiptype",
|
||||
name="daily_video_quota",
|
||||
field=models.IntegerField(default=0, verbose_name="每日生成视频次数"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="category",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("abandoned", "废弃"),
|
||||
("abstract_sculpture", "抽象"),
|
||||
("advertising", "广告"),
|
||||
("anime", "动漫"),
|
||||
("cine_lens", "电影镜头"),
|
||||
("cinematic", "电影"),
|
||||
("concept_art", "艺术"),
|
||||
("forestpunk", "赛博朋克"),
|
||||
("frost", "雪"),
|
||||
("graphite", "石墨"),
|
||||
("macro_photography", "宏观"),
|
||||
("pixel_art", "像素艺术"),
|
||||
("retro_photography", "复古"),
|
||||
("sci_fi_art", "科幻"),
|
||||
("thriller", "惊悚"),
|
||||
("35mm", "35mm"),
|
||||
("vector", "矢量"),
|
||||
("watercolor", "水彩"),
|
||||
],
|
||||
max_length=50,
|
||||
null=True,
|
||||
verbose_name="分类",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="download_count",
|
||||
field=models.IntegerField(default=0, null=True, verbose_name="下载次数"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="generated_at",
|
||||
field=models.DateTimeField(
|
||||
default=django.utils.timezone.now, null=True, verbose_name="生成时间"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="is_approved",
|
||||
field=models.BooleanField(default=False, null=True, verbose_name="是否审核"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="original_url",
|
||||
field=models.TextField(null=True, verbose_name="原始URL"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="assetlibrary",
|
||||
name="qiniu_url",
|
||||
field=models.TextField(null=True, verbose_name="七牛云视频URL"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transactionlog",
|
||||
name="remark",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("day", "一天会员充值"),
|
||||
("week", "一周会员充值"),
|
||||
("month", "一月会员充值"),
|
||||
("season", "三月会员充值"),
|
||||
("year", "一年会员充值"),
|
||||
("lifetime", "永久会员充值"),
|
||||
],
|
||||
null=True,
|
||||
verbose_name="备注",
|
||||
),
|
||||
),
|
||||
]
|
24
MyApi/migrations/0010_user_membership_type.py
Normal file
24
MyApi/migrations/0010_user_membership_type.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-01 23:24
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0009_membershiptype_daily_video_quota_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="membership_type",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="MyApi.membershiptype",
|
||||
verbose_name="会员卡类型",
|
||||
),
|
||||
),
|
||||
]
|
@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-04 01:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0010_user_membership_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="membershiptype",
|
||||
name="daily_video_quota",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="membershiptype",
|
||||
name="coins",
|
||||
field=models.IntegerField(default=0, verbose_name="赠送金币数量"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="membershiptype",
|
||||
name="is_quota",
|
||||
field=models.BooleanField(default=False, verbose_name="是否为额度充值"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="coins",
|
||||
field=models.IntegerField(default=3, verbose_name="金币数量"),
|
||||
),
|
||||
]
|
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-04 02:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0011_remove_membershiptype_daily_video_quota_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="daily_video_quota",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="membership_type",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transactionlog",
|
||||
name="remark",
|
||||
field=models.TextField(blank=True, null=True, verbose_name="备注"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transactionlog",
|
||||
name="transaction_type",
|
||||
field=models.CharField(max_length=50, verbose_name="交易类型"),
|
||||
),
|
||||
]
|
17
MyApi/migrations/0013_videotask_gif_url.py
Normal file
17
MyApi/migrations/0013_videotask_gif_url.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-04 05:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0012_remove_user_daily_video_quota_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="videotask",
|
||||
name="gif_url",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-04 06:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0013_videotask_gif_url"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="assetlibrary",
|
||||
name="qiniu_url",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="videotask",
|
||||
name="qiniu_url",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="assetlibrary",
|
||||
name="gif_url",
|
||||
field=models.TextField(blank=True, null=True, verbose_name="GIF图片URL"),
|
||||
),
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-04 06:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0014_remove_assetlibrary_qiniu_url_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="assetlibrary",
|
||||
name="qiniu_url",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="videotask",
|
||||
name="qiniu_url",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
26
MyApi/migrations/0016_alter_videotask_status.py
Normal file
26
MyApi/migrations/0016_alter_videotask_status.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-04 11:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("MyApi", "0015_assetlibrary_qiniu_url_videotask_qiniu_url"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="videotask",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("SUBMITTED", "已提交"),
|
||||
("IN_PROGRESS", "处理中"),
|
||||
("SUCCESS", "成功"),
|
||||
("FAILURE", "失败"),
|
||||
],
|
||||
default="PENDING",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
]
|
0
MyApi/migrations/__init__.py
Normal file
0
MyApi/migrations/__init__.py
Normal file
351
MyApi/models.py
Normal file
351
MyApi/models.py
Normal file
@ -0,0 +1,351 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
from django.contrib.auth.hashers import make_password,is_password_usable
|
||||
from django.utils import timezone
|
||||
|
||||
# 管理员模型
|
||||
class Administrator(models.Model):
|
||||
username = models.CharField(max_length=150, unique=True) # 用户名,确保唯一性
|
||||
password = models.CharField(max_length=128) # 密码,将会加密存储
|
||||
role = models.CharField(max_length=100) # 角色
|
||||
is_active = models.BooleanField(default=True) # 状态,启用/禁用,默认启用
|
||||
last_login = models.DateTimeField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 创建时间,自动设置为当前时间
|
||||
# 保存模型之前对密码进行加密
|
||||
def save(self, *args, **kwargs):
|
||||
if not is_password_usable(self.password):
|
||||
self.password = make_password(self.password)
|
||||
super().save(*args, **kwargs)
|
||||
class Meta:
|
||||
db_table = "Administrator"
|
||||
|
||||
from django.db import models
|
||||
|
||||
class MembershipType(models.Model):
|
||||
type = models.CharField(max_length=50, unique=True, verbose_name="卡类型") # 卡类型,如天卡、周卡等 coins 额度卡
|
||||
title = models.CharField(max_length=100, verbose_name="标题") # 卡的标题
|
||||
description = models.TextField(verbose_name="描述") # 卡的描述
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格") # 卡的价格
|
||||
duration_days = models.IntegerField(verbose_name="有效天数") # 卡的有效期,以天为单位
|
||||
coins = models.IntegerField(default=0, verbose_name="赠送金币数量") # 赠送金币的数量
|
||||
is_quota = models.BooleanField(default=False, verbose_name="是否为额度充值") # 是否为额度充值
|
||||
|
||||
class Meta:
|
||||
db_table = "membership_type" # 设置数据库表名
|
||||
verbose_name = "会员卡类型"
|
||||
verbose_name_plural = "会员卡类型"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
# 用户模型
|
||||
class User(models.Model):
|
||||
openid = models.CharField(max_length=150, unique=True) # 用户唯一标识
|
||||
wxid = models.CharField(max_length=150, blank=True, null=True) # 微信ID
|
||||
wechat_number = models.CharField(max_length=150, blank=True, null=True, verbose_name='微信号') # 微信号
|
||||
nickname = models.CharField(max_length=150, blank=True, null=True) # 用户标识
|
||||
gender = models.CharField(max_length=10, blank=True, null=True) # 性别
|
||||
region = models.CharField(max_length=150, blank=True, null=True) # 地区
|
||||
email = models.EmailField(blank=True, null=True) # 邮箱
|
||||
phone = models.CharField(max_length=20, blank=True, null=True) # 号码
|
||||
password = models.CharField(max_length=128, blank=True, null=True) # 密码
|
||||
scene = models.CharField(max_length=100, blank=True, null=True) # 用户来源
|
||||
is_member = models.BooleanField(default=False) # 是否为会员
|
||||
member_start_time = models.BigIntegerField(blank=True, null=True) # 存储会员开始时间的时间戳
|
||||
member_end_time = models.BigIntegerField(blank=True, null=True) # 存储会员到期时间的时间戳
|
||||
is_active = models.BooleanField(default=True) # 状态,启用/禁用,默认启用
|
||||
usage_count = models.IntegerField(default=0, blank=True, null=True) # 用户使用次数
|
||||
coins = models.IntegerField(default=3, verbose_name='金币数量') # 用户金币数量
|
||||
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='用户余额') # 用户余额
|
||||
inviter_nickname = models.CharField(max_length=150, blank=True, null=True, verbose_name='邀请人昵称') # 用户邀请人昵称
|
||||
invitees_count = models.IntegerField(default=0, verbose_name='邀请人数量') # 邀请人数量
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 用户创建时间
|
||||
class Meta:
|
||||
db_table = "User"
|
||||
|
||||
#添加好友表
|
||||
class FriendRequest(models.Model):
|
||||
fromusername = models.CharField(max_length=255, verbose_name='微信ID') # 发起请求的微信ID
|
||||
alias = models.CharField(max_length=255, blank=True, null=True, verbose_name='微信号') # 微信号
|
||||
fromnickname = models.CharField(max_length=255, blank=True, null=True, verbose_name='昵称') # 昵称
|
||||
country = models.CharField(max_length=255, blank=True, null=True, verbose_name='国家') # 国家
|
||||
province = models.CharField(max_length=255, blank=True, null=True, verbose_name='省份') # 省份
|
||||
city = models.CharField(max_length=255, blank=True, null=True, verbose_name='城市') # 城市
|
||||
sex = models.CharField(max_length=1, blank=True, null=True, verbose_name='性别') # 性别
|
||||
scene = models.CharField(max_length=255, blank=True, null=True, verbose_name='来源') # 来源
|
||||
time = models.DateTimeField(verbose_name='申请时间') # 申请时间
|
||||
towxid = models.CharField(max_length=255, verbose_name='目标微信ID') # 接收好友请求的微信ID
|
||||
|
||||
class Meta:
|
||||
db_table = "FriendRequest"
|
||||
verbose_name = '添加好友请求'
|
||||
verbose_name_plural = '添加好友请求'
|
||||
|
||||
def __str__(self):
|
||||
return self.fromnickname or self.fromusername
|
||||
|
||||
#文案库
|
||||
from django.db import models
|
||||
|
||||
class Copywriting(models.Model):
|
||||
openid = models.CharField(max_length=150, blank=True, null=True, verbose_name="openid")
|
||||
nickname = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户标识")
|
||||
text_content = models.TextField(verbose_name="文本内容")
|
||||
source = models.CharField(max_length=255, verbose_name="来源")
|
||||
tag = models.CharField(max_length=255, default=None, verbose_name="标签")
|
||||
popularity = models.IntegerField(default=0, verbose_name="热度")
|
||||
added_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
|
||||
is_approved = models.BooleanField(default=False, verbose_name="审核通过") # 默认未审核
|
||||
|
||||
class Meta:
|
||||
db_table = "Copywriting"
|
||||
verbose_name = "文案库"
|
||||
verbose_name_plural = "文案库"
|
||||
|
||||
def __str__(self):
|
||||
return self.text_content[:50] # 返回文案的前50个字符作为字符串表示
|
||||
|
||||
|
||||
#视频提取记录
|
||||
class VideoExtractionRecord(models.Model):
|
||||
extraction_link = models.URLField(verbose_name="提取链接", max_length=2048)
|
||||
source = models.CharField(max_length=100, choices=[('index', 'Index'), ('weixin', 'Weixin')], verbose_name="来源")
|
||||
openid = models.CharField(max_length=150, blank=True, null=True, verbose_name="openid")
|
||||
nickname = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户标识")
|
||||
video_title = models.TextField(blank=True, null=True, verbose_name="视频标题")
|
||||
wxid = models.CharField(max_length=255, blank=True, null=True, verbose_name="微信id")
|
||||
wechat_alias = models.CharField(max_length=255, blank=True, null=True, verbose_name="微信号")
|
||||
is_successful = models.CharField(max_length=255,blank=True, null=True, verbose_name="是否提取成功")
|
||||
exception_reason = models.TextField(blank=True, null=True, verbose_name="异常原因")
|
||||
extraction_time = models.DateTimeField(auto_now_add=True, verbose_name="提取时间")
|
||||
|
||||
class Meta:
|
||||
db_table = "VideoExtractionRecord"
|
||||
verbose_name = "视频提取记录"
|
||||
verbose_name_plural = "视频提取记录"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user}'s extraction record"
|
||||
|
||||
#文案提取任务
|
||||
class TranscriptionTask(models.Model):
|
||||
uuid = models.CharField(max_length=255)
|
||||
video_url = models.TextField()
|
||||
task_id = models.CharField(max_length=255)
|
||||
result = models.TextField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
class Meta:
|
||||
db_table = "TranscriptionTask"
|
||||
verbose_name = "文案提取任务"
|
||||
verbose_name_plural = "文案提取任务"
|
||||
def __str__(self):
|
||||
return self.task_id
|
||||
|
||||
#api调用日志
|
||||
class ApiCallLog(models.Model):
|
||||
# 用户来源,如'index', 'weixin'等
|
||||
source = models.CharField(max_length=100, blank=True, null=True, choices=[('index', 'Index'), ('weixin', 'Weixin')], verbose_name="来源")
|
||||
|
||||
# 用户的OpenID
|
||||
openid = models.CharField(max_length=150, blank=True, null=True, verbose_name="openid")
|
||||
|
||||
# 用户昵称
|
||||
nickname = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户标识")
|
||||
|
||||
# 微信ID
|
||||
wxid = models.CharField(max_length=255, blank=True, null=True, verbose_name="微信id")
|
||||
|
||||
# 微信号
|
||||
wechat_alias = models.CharField(max_length=255, blank=True, null=True, verbose_name="微信号")
|
||||
|
||||
# 调用的API接口名称或路径
|
||||
api_name = models.CharField(max_length=255, verbose_name="调用接口")
|
||||
|
||||
# 调用结果,使用BooleanField表示成功或失败
|
||||
is_successful = models.BooleanField(default=False, verbose_name="是否成功")
|
||||
|
||||
# 备注信息,可以用来记录失败原因或其他注释信息
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name="备注")
|
||||
|
||||
# 调用时间,使用自动设置为当前时间的DateTimeField
|
||||
call_time = models.DateTimeField(auto_now_add=True, verbose_name="调用时间")
|
||||
|
||||
class Meta:
|
||||
db_table = "api_call_log"
|
||||
verbose_name = "API调用日志"
|
||||
verbose_name_plural = "API调用日志"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.nickname} - {self.api_name} at {self.call_time}"
|
||||
|
||||
#卡密记录
|
||||
class RedemptionCard(models.Model):
|
||||
code = models.CharField(max_length=100, unique=True, verbose_name="卡密") # 卡密
|
||||
card_type = models.CharField(max_length=50, choices=(('member', '会员卡'), ('quota', '额度卡')), verbose_name="卡类型") # 卡类型
|
||||
created_date = models.DateTimeField(auto_now_add=True, verbose_name="生成日期") # 卡密生成日期
|
||||
expiry_date = models.DateTimeField(verbose_name="到期时间") # 卡密到期时间
|
||||
is_used = models.BooleanField(default=False, verbose_name="是否已使用") # 是否已被使用
|
||||
used_by_openid = models.CharField(max_length=150, blank=True, null=True, verbose_name="使用者OpenID") # 使用者的OpenID
|
||||
used_by_nickname = models.CharField(max_length=150, blank=True, null=True, verbose_name="使用者昵称") # 使用者的昵称
|
||||
card_creation_time = models.DateTimeField(auto_now_add=True, verbose_name="卡密创建时间") # 卡密创建时间
|
||||
validity_period = models.PositiveIntegerField(default=0, verbose_name="有效期") # 有效期天数或额度数量
|
||||
|
||||
class Meta:
|
||||
db_table = "redemption_card" # 设置数据库表名
|
||||
verbose_name = "兑换卡"
|
||||
verbose_name_plural = "兑换卡"
|
||||
|
||||
|
||||
# 充值列表
|
||||
|
||||
|
||||
# 订单记录
|
||||
class TransactionLog(models.Model):
|
||||
TRANSACTION_STATUS_CHOICES = [
|
||||
('pending', '待处理'),
|
||||
('completed', '完成'),
|
||||
('failed', '失败')
|
||||
]
|
||||
|
||||
TRANSACTION_TYPE_CHOICES = [
|
||||
('member', '会员购买'),
|
||||
('quota', '续费会员')
|
||||
]
|
||||
|
||||
|
||||
transaction_no = models.CharField(max_length=255, unique=True, verbose_name="交易编号")
|
||||
transaction_status = models.CharField(max_length=50, default='pending', choices=TRANSACTION_STATUS_CHOICES, verbose_name="交易状态")
|
||||
user_openid = models.CharField(max_length=150, verbose_name="用户OpenID")
|
||||
transaction_type = models.CharField(max_length=50, verbose_name="交易类型")
|
||||
transaction_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="交易金额")
|
||||
remark = models.TextField(blank=True, null=True, verbose_name="备注")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
||||
|
||||
class Meta:
|
||||
db_table = "transaction_log"
|
||||
verbose_name = "交易记录"
|
||||
verbose_name_plural = "交易记录"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.transaction_no} - {self.transaction_status}"
|
||||
|
||||
|
||||
class IDCounter(models.Model):
|
||||
count = models.IntegerField(default=10000) # 初始化为0或其他你想开始的数字
|
||||
|
||||
@classmethod
|
||||
def get_next_id(cls):
|
||||
# 这个方法获取下一个ID,并且自增存储的计数
|
||||
counter, _ = cls.objects.get_or_create(pk=1) # 假设我们只用一个计数器,其ID为1
|
||||
counter.count += 1
|
||||
counter.save()
|
||||
return counter.count
|
||||
|
||||
class Meta:
|
||||
db_table = "IDCounter"
|
||||
|
||||
class ChatRecord(models.Model):
|
||||
openid = models.CharField(max_length=150,default=None) # 用户唯一标识
|
||||
nickname = models.CharField(max_length=150, verbose_name='用户昵称') # 用户昵称
|
||||
conversation_id = models.CharField(max_length=100, verbose_name='会话ID') # 会话ID
|
||||
role = models.CharField(max_length=100, verbose_name='角色') # 角色
|
||||
message_content = models.TextField(verbose_name='消息内容') # 消息内容
|
||||
is_response = models.BooleanField(default=False, verbose_name='是否为AI回复') # 是否为AI回复
|
||||
timestamp = models.DateTimeField(default=timezone.now, verbose_name='时间戳') # 时间戳
|
||||
|
||||
class Meta:
|
||||
db_table = "chat_record" # 数据库表名
|
||||
ordering = ['-timestamp'] # 默认排序
|
||||
verbose_name = '聊天记录'
|
||||
verbose_name_plural = '聊天记录'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.nickname}: {self.message_content[:20]}"
|
||||
|
||||
class VideoTask(models.Model):
|
||||
TASK_STATUS_CHOICES = [
|
||||
('pending','待处理'),
|
||||
('failed', '已提交'),
|
||||
('running','运行中'),
|
||||
('success', '成功'),
|
||||
('failed', '失败'),
|
||||
]
|
||||
|
||||
openid = models.CharField(max_length=150) # 用户唯一标识
|
||||
nickname = models.CharField(max_length=150) # 用户昵称
|
||||
task_id = models.CharField(max_length=255, unique=True) # 任务ID
|
||||
task_type = models.CharField(max_length=50) # 任务类型,例如 'gen2'
|
||||
status = models.CharField(max_length=20, choices=TASK_STATUS_CHOICES, default='pending') # 任务状态
|
||||
created_at = models.DateTimeField(auto_now_add=True) # 任务创建时间
|
||||
updated_at = models.DateTimeField(auto_now=True) # 任务更新时间
|
||||
text_prompt = models.TextField(null=True) # 文本提示
|
||||
width = models.IntegerField(null=True) # 视频宽度
|
||||
height = models.IntegerField(null=True) # 视频高度
|
||||
motion_score = models.IntegerField(null=True) # 动态评分
|
||||
style = models.CharField(max_length=255, blank=True, null=True) # 风格
|
||||
seconds = models.IntegerField(null=True) # 视频时长
|
||||
image_url = models.TextField(blank=True, null=True) # 图生视频的图像URL
|
||||
result_url = models.TextField(blank=True, null=True) # 视频生成结果URL
|
||||
qiniu_url = models.TextField(blank=True, null=True) # 七牛云储存url
|
||||
gif_url = models.TextField(blank=True, null=True) # GIF图片链接
|
||||
progress = models.FloatField(default=0.0) # 进度百分比
|
||||
error_message = models.TextField(blank=True, null=True) # 错误信息
|
||||
description_zh = models.TextField(blank=True, null=True) # 中文描述
|
||||
is_processing = models.BooleanField(default=False) # 正在处理标志
|
||||
|
||||
class Meta:
|
||||
db_table = "video_task"
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return self.task_id
|
||||
|
||||
|
||||
class AssetLibrary(models.Model):
|
||||
STYLE_CHOICES = [
|
||||
('abandoned', '废弃'),
|
||||
('abstract_sculpture', '抽象'),
|
||||
('advertising', '广告'),
|
||||
('anime', '动漫'),
|
||||
('cine_lens', '电影镜头'),
|
||||
('cinematic', '电影'),
|
||||
('concept_art', '艺术'),
|
||||
('forestpunk', '赛博朋克'),
|
||||
('frost', '雪'),
|
||||
('graphite', '石墨'),
|
||||
('macro_photography', '宏观'),
|
||||
('pixel_art', '像素艺术'),
|
||||
('retro_photography', '复古'),
|
||||
('sci_fi_art', '科幻'),
|
||||
('thriller', '惊悚'),
|
||||
('35mm', '35mm'),
|
||||
('vector', '矢量'),
|
||||
('watercolor', '水彩'),
|
||||
]
|
||||
|
||||
original_url = models.TextField(verbose_name='原始URL', null=True) # 原始URL
|
||||
qiniu_url = models.TextField(blank=True, null=True) # 七牛云储存url
|
||||
duration = models.FloatField(verbose_name='时长') # 视频时长,以秒为单位
|
||||
category = models.CharField(max_length=50, choices=STYLE_CHOICES, verbose_name='分类', null=True) # 视频分类
|
||||
description = models.TextField(verbose_name='视频描述', blank=True, null=True) # 视频描述
|
||||
description_zh = models.TextField(verbose_name='中文描述', blank=True, null=True) # 中文描述
|
||||
generated_by = models.CharField(null=True, verbose_name='生成用户', max_length=50) # 生成用户
|
||||
generated_at = models.DateTimeField(default=timezone.now, verbose_name='生成时间', null=True) # 生成时间
|
||||
download_count = models.IntegerField(default=0, verbose_name='下载次数', null=True) # 下载次数
|
||||
is_approved = models.BooleanField(default=False, verbose_name='是否审核', null=True) # 是否审核
|
||||
gif_url = models.TextField(verbose_name='GIF图片URL', blank=True, null=True) # GIF图片URL
|
||||
|
||||
class Meta:
|
||||
db_table = "asset_library" # 数据库表名
|
||||
verbose_name = '素材库'
|
||||
verbose_name_plural = '素材库'
|
||||
ordering = ['-generated_at'] # 默认排序
|
||||
|
||||
def __str__(self):
|
||||
return self.original_url
|
170
MyApi/sparkAPI.py
Normal file
170
MyApi/sparkAPI.py
Normal file
@ -0,0 +1,170 @@
|
||||
# coding: utf-8
|
||||
import _thread as thread
|
||||
import os
|
||||
import time
|
||||
import base64
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
import ssl
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
from urllib.parse import urlencode
|
||||
from wsgiref.handlers import format_date_time
|
||||
|
||||
import websocket
|
||||
|
||||
class Ws_Param(object):
|
||||
# 初始化
|
||||
def __init__(self, APPID, APIKey, APISecret, gpt_url):
|
||||
self.APPID = APPID
|
||||
self.APIKey = APIKey
|
||||
self.APISecret = APISecret
|
||||
self.host = urlparse(gpt_url).netloc
|
||||
self.path = urlparse(gpt_url).path
|
||||
self.gpt_url = gpt_url
|
||||
|
||||
# 生成url
|
||||
def create_url(self):
|
||||
# 生成RFC1123格式的时间戳
|
||||
now = datetime.now()
|
||||
date = format_date_time(mktime(now.timetuple()))
|
||||
|
||||
# 拼接字符串
|
||||
signature_origin = "host: " + self.host + "\n"
|
||||
signature_origin += "date: " + date + "\n"
|
||||
signature_origin += "GET " + self.path + " HTTP/1.1"
|
||||
|
||||
# 进行hmac-sha256进行加密
|
||||
signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
|
||||
digestmod=hashlib.sha256).digest()
|
||||
|
||||
signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8')
|
||||
|
||||
authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"'
|
||||
|
||||
authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
|
||||
|
||||
# 将请求的鉴权参数组合为字典
|
||||
v = {
|
||||
"authorization": authorization,
|
||||
"date": date,
|
||||
"host": self.host
|
||||
}
|
||||
# 拼接鉴权参数,生成url
|
||||
url = self.gpt_url + '?' + urlencode(v)
|
||||
# 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致
|
||||
return url
|
||||
|
||||
# import openpyxl
|
||||
# from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
# import os
|
||||
|
||||
|
||||
class SparkUtil(object):
|
||||
# 初始化
|
||||
def __init__(self):
|
||||
self.isClose = False
|
||||
self.data = ''
|
||||
|
||||
appid = "cb0bb4de"
|
||||
api_secret = "MDI5MGNhOThlNTQxNTBhY2ZjODNkM2Fh"
|
||||
api_key = "dfd17a540cf52db34c98aac216a0473c"
|
||||
gpt_url = "wss://spark-api.xf-yun.com/v3.5/chat"
|
||||
domain = "generalv3.5"
|
||||
|
||||
wsParam = Ws_Param(appid, api_key, api_secret, gpt_url)
|
||||
websocket.enableTrace(False)
|
||||
wsUrl = wsParam.create_url()
|
||||
|
||||
self.ws = websocket.WebSocketApp(wsUrl, on_message=self.on_message, on_error=self.on_error,
|
||||
on_close=self.on_close,
|
||||
on_open=self.on_open)
|
||||
self.ws.appid = appid
|
||||
self.ws.domain = domain
|
||||
|
||||
# 收到websocket错误的处理
|
||||
def on_error(self, ws, error):
|
||||
self.isClose = True
|
||||
print("###--- error:", error)
|
||||
|
||||
# 收到websocket关闭的处理
|
||||
def on_close(self, ws,close_status_code, close_msg):
|
||||
self.isClose = True
|
||||
print("### closed ###------")
|
||||
|
||||
# 获取数据
|
||||
def query_and_get_data(self, query):
|
||||
self.ws.query = query
|
||||
self.ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
|
||||
while True:
|
||||
if self.isClose:
|
||||
self.isClose = False
|
||||
result = self.data
|
||||
self.data = ''
|
||||
return result
|
||||
|
||||
# 收到websocket连接建立的处理
|
||||
def on_open(self, ws):
|
||||
thread.start_new_thread(self.run, (ws,))
|
||||
|
||||
def run(self, ws, *args):
|
||||
data = json.dumps(self.gen_params(appid=ws.appid, query=ws.query, domain=ws.domain))
|
||||
ws.send(data)
|
||||
|
||||
# 收到websocket消息的处理
|
||||
def on_message(self, ws, message):
|
||||
# print(message)
|
||||
data = json.loads(message)
|
||||
code = data['header']['code']
|
||||
if code != 0:
|
||||
print(f'请求错误: {code}, {data}')
|
||||
self.isClose = True
|
||||
ws.close()
|
||||
else:
|
||||
choices = data["payload"]["choices"]
|
||||
status = choices["status"]
|
||||
content = choices["text"][0]["content"]
|
||||
self.data += content
|
||||
print(content, end='')
|
||||
if status == 2:
|
||||
print("#### 关闭会话")
|
||||
self.isClose = True
|
||||
ws.close()
|
||||
|
||||
|
||||
def gen_params(self, appid, query, domain):
|
||||
"""
|
||||
通过appid和用户的提问来生成请参数
|
||||
"""
|
||||
|
||||
data = {
|
||||
"header": {
|
||||
"app_id": appid,
|
||||
"uid": "文案助手",
|
||||
# "patch_id": [] #接入微调模型,对应服务发布后的resourceid
|
||||
},
|
||||
"parameter": {
|
||||
"chat": {
|
||||
"domain": domain,
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 4096,
|
||||
"auditing": "default",
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"message": {
|
||||
"text": [{"role": "user", "content": query}]
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
data = SparkUtil().query_and_get_data("帮我写一篇一百字的作文")
|
||||
print(data)
|
3
MyApi/tests.py
Normal file
3
MyApi/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
40
MyApi/urls.py
Normal file
40
MyApi/urls.py
Normal file
@ -0,0 +1,40 @@
|
||||
# myapp/urls.py
|
||||
from django.urls import path
|
||||
from . import views
|
||||
from . import video_text
|
||||
from . import wxpay
|
||||
from . import AiMssage
|
||||
urlpatterns = [
|
||||
path('login/', views.miniapp_login, name='miniapp_login'),
|
||||
path('video/', views.video_extraction, name='video_extraction'),
|
||||
path('add_friend/', views.add_friend_request, name='add_friend_request'),
|
||||
path('total/', views.total, name='total'),
|
||||
# path('record_extraction', views.record_extraction, name='record_extraction'),
|
||||
# path('video_transcribe/', views.video_transcribe, name='video_transcribe'),
|
||||
path('rewrite_text/', views.rewrite_text, name='rewrite_text'),
|
||||
path('get_copywriting_list_with_filters/', views.get_copywriting_list_with_filters,name='get_copywriting_list_with_filters'),
|
||||
# path('create_copywriting/', views.create_copywriting_entry, name='create_copywriting'),
|
||||
path('userinfo/', views.userinfo, name='userinfo'),
|
||||
path('redeem_card/', views.redeem_card, name='redeem_card'),
|
||||
path('reward_invitation', views.reward_invitation, name='reward_invitation'),
|
||||
path('video_to_text/', video_text.transcribe_audio, name='video_to_text'),
|
||||
path('query_task/', video_text.query_task_status, name='query_task'),
|
||||
path('wx_pay/', wxpay.wx_pay, name='wx_pay'),
|
||||
path('wx_pay_notify/', wxpay.wx_pay_notify, name='wx_pay_notify'),
|
||||
path('upload_image/', views.UploadImageView.as_view(), name='upload_image'),
|
||||
path('generate_video/', views.GenerateVideoView.as_view(), name='generate_video'),
|
||||
path('task_status/<str:task_id>/', views.TaskStatusView.as_view(), name='task_status'),
|
||||
path('extend_video/', views.ExtendVideoView.as_view(), name='extend_video'),
|
||||
path('generate_image_video/', views.GenerateImageVideoView.as_view(), name='generate_image_video'),
|
||||
path('user/<int:user_id>/video_tasks/', views.UserVideoTaskListView.as_view(), name='user_video_tasks'),
|
||||
path('user/<int:user_id>/delete_task/<str:task_id>/', views.DeleteVideoTaskView.as_view(), name='delete_task'),
|
||||
path('call_bailian_app/', AiMssage.call_bailian_app, name='call_bailian_app'),
|
||||
path('get_recent_chat_records/', AiMssage.get_recent_chat_records, name='get_recent_chat_records'),
|
||||
path('asset_library/', views.AssetLibraryView.as_view(), name='asset_library'),
|
||||
path('update_pending_tasks/',views.UpdatePendingTasksView.as_view(), name='update_pending_tasks'),
|
||||
path('increment-download-count/', views.increment_download_count, name='increment_download_count'),
|
||||
path('reset-daily-quota/', views.DailyCoinsBonusView.as_view(), name='reset_daily_quota'),
|
||||
path('video-generation-callback/', views.VideoGenerationCallbackView.as_view(), name='VideoGenerationCallbackView'),
|
||||
|
||||
]
|
||||
|
177
MyApi/video_text.py
Normal file
177
MyApi/video_text.py
Normal file
@ -0,0 +1,177 @@
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
|
||||
from tencentcloud.asr.v20190614 import asr_client, models
|
||||
from django.http import JsonResponse
|
||||
from Crypto.Cipher import AES
|
||||
import base64
|
||||
|
||||
from .API_Log import get_logger
|
||||
from .models import TranscriptionTask
|
||||
|
||||
from .views import update_usage_count
|
||||
# 使用示例
|
||||
log_file = '文案提取日志.log'
|
||||
logger = get_logger('文案提取日志', log_file, when='midnight', backup_count=7)
|
||||
|
||||
def decrypt_param(x, t):
|
||||
# 创建AES解密器对象
|
||||
key = b'qw5w6SFE2D1jmxyd'
|
||||
iv = b'345GDFED433223DF'
|
||||
|
||||
# 检查 x 和 t 是否为空
|
||||
if not x or not t:
|
||||
return False
|
||||
|
||||
try:
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
# 将密文进行Base64解码
|
||||
ciphertext_bytes = base64.b64decode(x)
|
||||
# 使用解密器解密密文
|
||||
plaintext_bytes = cipher.decrypt(ciphertext_bytes)
|
||||
# 删除填充字符
|
||||
plaintext = plaintext_bytes.rstrip(b'\0').decode('utf-8')
|
||||
# 比较解密后的明文和 t
|
||||
if plaintext.rstrip('\x03') == t.rstrip('\x03'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"解密过程出现错误: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# 数据库操作函数
|
||||
def get_task_from_db(video_url):
|
||||
try:
|
||||
return TranscriptionTask.objects.get(video_url=video_url)
|
||||
except TranscriptionTask.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def save_task_to_db(uuid, video_url, task_id, result=None):
|
||||
task = TranscriptionTask(uuid=uuid, video_url=video_url, task_id=task_id, result=result)
|
||||
task.save()
|
||||
|
||||
|
||||
def get_task_result_from_db(task_id):
|
||||
try:
|
||||
task = TranscriptionTask.objects.get(task_id=task_id)
|
||||
return task.result
|
||||
except TranscriptionTask.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def update_task_result_in_db(task_id, result):
|
||||
try:
|
||||
task = TranscriptionTask.objects.get(task_id=task_id)
|
||||
task.result = result
|
||||
task.save()
|
||||
except TranscriptionTask.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def transcribe_audio(request):
|
||||
data = json.loads(request.body)
|
||||
video_url = data.get('url')
|
||||
uuid = data.get('uuid')
|
||||
openid = data.get('openid')
|
||||
x = request.headers.get('X', '')
|
||||
t = request.headers.get('T', '')
|
||||
|
||||
if not uuid or not video_url:
|
||||
logger.error(
|
||||
f'API 文案提取: 【缺少必要参数】\n headers={request.headers}\n data={data} \n ---------------------------------')
|
||||
return JsonResponse({'error': '缺少必要参数'})
|
||||
|
||||
if decrypt_param(x, t):
|
||||
existing_task = get_task_from_db(video_url)
|
||||
if existing_task:
|
||||
if existing_task.result:
|
||||
return JsonResponse({'result': existing_task.result})
|
||||
else:
|
||||
return JsonResponse({'task_id': existing_task.task_id})
|
||||
else:
|
||||
increment = -1 # 假设每次生成视频需要消耗一次使用次数
|
||||
function_type = 'transcribe_audio'
|
||||
result = update_usage_count(openid, increment, function_type)
|
||||
if not result['success']:
|
||||
return JsonResponse(result)
|
||||
|
||||
cred = credential.Credential("AKIDb6YsAMQL4hBjVGybN6gzLeu7FUegcins", "MpYQQbp8UxQ2pbIKXrCX5d2hjx9Th2Or")
|
||||
http_profile = HttpProfile()
|
||||
http_profile.endpoint = "asr.tencentcloudapi.com"
|
||||
client_profile = ClientProfile()
|
||||
client_profile.httpProfile = http_profile
|
||||
client = asr_client.AsrClient(cred, "ap-guangzhou", client_profile)
|
||||
req = models.CreateRecTaskRequest()
|
||||
req.EngineModelType = "16k_zh"
|
||||
req.ChannelNum = 1
|
||||
req.ResTextFormat = 0
|
||||
req.SourceType = 0
|
||||
req.Url = video_url
|
||||
resp = client.CreateRecTask(req)
|
||||
resp_dict = json.loads(resp.to_json_string())
|
||||
|
||||
task_id = resp_dict['Data']['TaskId']
|
||||
save_task_to_db(uuid, video_url, task_id)
|
||||
|
||||
logger.info(
|
||||
f'API 文案提取: 【{uuid}--{video_url} 提交任务成功】\n 返回结果={resp}\n---------------------------------')
|
||||
return JsonResponse(resp_dict, safe=False)
|
||||
else:
|
||||
logger.error(
|
||||
f'API 文案提取: 【非法参数】\n headers={request.headers}\n data={data} \n ---------------------------------')
|
||||
return JsonResponse({'error': '非法参数'})
|
||||
|
||||
def query_task_status(request):
|
||||
data = json.loads(request.body)
|
||||
logger.info(
|
||||
f'API 文案提取任务查询: {data} \n ---------------------------------')
|
||||
task_id = data.get('task_id')
|
||||
uuid = data.get('uuid')
|
||||
x = request.headers.get('X', '')
|
||||
t = request.headers.get('T', '')
|
||||
|
||||
if not uuid or not task_id:
|
||||
logger.error(
|
||||
f'API 文案提取任务查询: 【缺少必要参数】\n headers={request.headers}\n data={data} \n ---------------------------------')
|
||||
return JsonResponse({'error': '缺少必要参数'})
|
||||
|
||||
if decrypt_param(x, t):
|
||||
cached_result = get_task_result_from_db(task_id)
|
||||
if cached_result:
|
||||
return JsonResponse({'result': cached_result})
|
||||
|
||||
cred = credential.Credential("AKIDb6YsAMQL4hBjVGybN6gzLeu7FUegcins", "MpYQQbp8UxQ2pbIKXrCX5d2hjx9Th2Or")
|
||||
http_profile = HttpProfile()
|
||||
http_profile.endpoint = "asr.tencentcloudapi.com"
|
||||
clientProfile = ClientProfile()
|
||||
clientProfile.httpProfile = http_profile
|
||||
client = asr_client.AsrClient(cred, "ap-guangzhou", clientProfile)
|
||||
req = models.DescribeTaskStatusRequest()
|
||||
params = {
|
||||
"TaskId": int(task_id)
|
||||
}
|
||||
req.from_json_string(json.dumps(params))
|
||||
|
||||
resp = client.DescribeTaskStatus(req)
|
||||
resp_dict = json.loads(resp.to_json_string())
|
||||
|
||||
if resp_dict['Data']['Status'] == 2:
|
||||
result = resp_dict['Data']['Result']
|
||||
update_task_result_in_db(task_id, result)
|
||||
return JsonResponse({'result': result})
|
||||
|
||||
logger.info(
|
||||
f'API 文案提取任务查询: 【{uuid}--{task_id} 进度查询】\n 返回结果={resp}\n---------------------------------')
|
||||
return JsonResponse(resp_dict, safe=False)
|
||||
else:
|
||||
logger.error(
|
||||
f'API 文案提取任务查询: 【非法参数】\n headers={request.headers}\n data={data} \n ---------------------------------')
|
||||
return JsonResponse({'error': '非法参数'})
|
1260
MyApi/views.py
Normal file
1260
MyApi/views.py
Normal file
File diff suppressed because it is too large
Load Diff
310
MyApi/wxpay.py
Normal file
310
MyApi/wxpay.py
Normal file
@ -0,0 +1,310 @@
|
||||
import base64
|
||||
import json
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
import requests
|
||||
import time
|
||||
import random
|
||||
from xml.etree import ElementTree as ET
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import pkcs1_15
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.conf import settings
|
||||
from .models import TransactionLog, MembershipType,User
|
||||
from .API_Log import get_logger
|
||||
log_file = '支付日志.log'
|
||||
logger = get_logger('支付日志', log_file, when='midnight', backup_count=7)
|
||||
|
||||
|
||||
def wx_pay(request):
|
||||
request_data = json.loads(request.body)
|
||||
type = request_data.get('type', 'default')
|
||||
wx_price = request_data['total_fee'] # 从请求获取订单金额
|
||||
openid = request_data['openid'] # 从请求获取用户openid
|
||||
transaction_type = request_data['transaction_type'] # 续费或者开通
|
||||
up_time = datetime.now()
|
||||
|
||||
# 从数据库中动态获取 body_map
|
||||
membership_types = MembershipType.objects.all()
|
||||
body_map = {membership_type.type: membership_type.title for membership_type in membership_types}
|
||||
|
||||
body_description = body_map.get(type)
|
||||
|
||||
# 以交易日期生成交易号
|
||||
transactionNo = str(up_time).replace('.', '').replace('-', '').replace(':', '').replace(' ', '')
|
||||
|
||||
membership_type = MembershipType.objects.get(type=type)
|
||||
price = membership_type.price
|
||||
newTransaction = TransactionLog.objects.create(
|
||||
transaction_no=transactionNo,
|
||||
transaction_status='pending',
|
||||
user_openid=openid,
|
||||
transaction_type=transaction_type,
|
||||
transaction_amount=price,
|
||||
remark=type,
|
||||
created_at=up_time
|
||||
)
|
||||
|
||||
# 打印和记录日志
|
||||
logger.info("生成订单:")
|
||||
logger.info(f"商户订单号:{transactionNo}")
|
||||
logger.info(f"用户OpenID:{openid}")
|
||||
logger.info(f"type:{type}")
|
||||
logger.info(f"transaction_type:{transaction_type}")
|
||||
logger.info(f"成功时间:{up_time}")
|
||||
logger.info(f"金额(前端):{wx_price}")
|
||||
logger.info(f"金额(后端):{price}")
|
||||
|
||||
# 生成统一下单的报文body
|
||||
url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi'
|
||||
body = {
|
||||
"appid": settings.WX_APP_ID,
|
||||
"mchid": settings.WX_MCH_ID,
|
||||
"description": body_description,
|
||||
"out_trade_no": transactionNo,
|
||||
"notify_url": settings.WX_NOTIFY_URL, # 后端接收回调通知的接口
|
||||
"amount": {"total": int(float(price) * 100), "currency": "CNY"}, # 微信金额单位为分
|
||||
"payer": {"openid": openid},
|
||||
"attach": json.dumps({"type": type, "transaction_type": transaction_type})
|
||||
}
|
||||
data = json.dumps(body)
|
||||
|
||||
# 定义生成签名的函数
|
||||
def get_sign(sign_str):
|
||||
rsa_key = RSA.importKey(open(settings.WX_KEY_PATH).read())
|
||||
signer = pkcs1_15.new(rsa_key)
|
||||
digest = SHA256.new(sign_str.encode('utf8'))
|
||||
sign = base64.b64encode(signer.sign(digest)).decode('utf-8')
|
||||
return sign
|
||||
|
||||
# 生成请求随机串
|
||||
random_str = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
|
||||
|
||||
# 生成请求时间戳
|
||||
time_stamps = str(int(time.time()))
|
||||
|
||||
# 生成签名串
|
||||
sign_str = f"POST\n/v3/pay/transactions/jsapi\n{time_stamps}\n{random_str}\n{data}\n"
|
||||
|
||||
# 生成签名
|
||||
sign = get_sign(sign_str)
|
||||
|
||||
# 生成HTTP请求头
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': '*/*',
|
||||
'Authorization': 'WECHATPAY2-SHA256-RSA2048 ' + f'mchid="{settings.WX_MCH_ID}",nonce_str="{random_str}",signature="{sign}",timestamp="{time_stamps}",serial_no="{settings.WX_SERIAL_NO}"',
|
||||
'Wechatpay-Serial': '66DB35F836EFD4CBEC66F3815D283A2892310324'
|
||||
}
|
||||
|
||||
# 发送请求获得prepay_id
|
||||
response = requests.post(url, data=data, headers=headers) # 获取预支付交易会话标识(prepay_id)
|
||||
|
||||
# 应答签名验证
|
||||
wechatpaySerial = response.headers['Wechatpay-Serial'] # 获取HTTP头部中包括回调报文的证书序列号
|
||||
wechatpaySignature = response.headers['Wechatpay-Signature'] # 获取HTTP头部中包括回调报文的签名
|
||||
wechatpayTimestamp = response.headers['Wechatpay-Timestamp'] # 获取HTTP头部中包括回调报文的时间戳
|
||||
wechatpayNonce = response.headers['Wechatpay-Nonce'] # 获取HTTP头部中包括回调报文的随机串
|
||||
|
||||
# 获取微信平台证书
|
||||
url2 = "https://api.mch.weixin.qq.com/v3/certificates"
|
||||
|
||||
# 生成证书请求随机串
|
||||
random_str2 = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
|
||||
|
||||
# 生成证书请求时间戳
|
||||
time_stamps2 = str(int(time.time()))
|
||||
|
||||
# 生成请求证书的签名串
|
||||
data2 = ""
|
||||
sign_str2 = f"GET\n/v3/certificates\n{time_stamps2}\n{random_str2}\n{data2}\n"
|
||||
|
||||
# 生成签名
|
||||
sign2 = get_sign(sign_str2)
|
||||
|
||||
# 生成HTTP请求头
|
||||
headers2 = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'WECHATPAY2-SHA256-RSA2048 ' + f'mchid="{settings.WX_MCH_ID}",nonce_str="{random_str2}",signature="{sign2}",timestamp="{time_stamps2}",serial_no="{settings.WX_SERIAL_NO}"'
|
||||
}
|
||||
|
||||
# 发送请求获得证书
|
||||
response2 = requests.get(url2, headers=headers2) # 只需要请求头
|
||||
cert = response2.json()
|
||||
|
||||
# 证书解密
|
||||
def decrypt(nonce, ciphertext, associated_data):
|
||||
key = settings.WX_API_KEY
|
||||
key_bytes = str.encode(key)
|
||||
nonce_bytes = str.encode(nonce)
|
||||
ad_bytes = str.encode(associated_data)
|
||||
data = base64.b64decode(ciphertext)
|
||||
aesgcm = AESGCM(key_bytes)
|
||||
return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
|
||||
|
||||
nonce = cert["data"][0]['encrypt_certificate']['nonce']
|
||||
ciphertext = cert["data"][0]['encrypt_certificate']['ciphertext']
|
||||
associated_data = cert["data"][0]['encrypt_certificate']['associated_data']
|
||||
serial_no = cert["data"][0]['serial_no']
|
||||
certificate = decrypt(nonce, ciphertext, associated_data)
|
||||
|
||||
# 签名验证
|
||||
if wechatpaySerial == serial_no: # 应答签名中的序列号同证书序列号应相同
|
||||
print('serial_no match')
|
||||
|
||||
def verify(data, signature): # 验签函数
|
||||
key = RSA.importKey(certificate) # 直接使用解密后的证书
|
||||
verifier = pkcs1_15.new(key)
|
||||
hash_obj = SHA256.new(data.encode('utf8'))
|
||||
return verifier.verify(hash_obj, base64.b64decode(signature))
|
||||
|
||||
data3 = f"{wechatpayTimestamp}\n{wechatpayNonce}\n{response.text}\n"
|
||||
try:
|
||||
verify(data3, wechatpaySignature)
|
||||
|
||||
# 生成调起支付API需要的参数并返回前端
|
||||
res = {
|
||||
'timeStamp': time_stamps,
|
||||
'nonceStr': random_str,
|
||||
'package': 'prepay_id=' + response.json()['prepay_id'],
|
||||
'paySign': get_sign(
|
||||
f"{settings.WX_APP_ID}\n{time_stamps}\n{random_str}\n{'prepay_id=' + response.json()['prepay_id']}\n"),
|
||||
}
|
||||
print(f'签名有效: {res}')
|
||||
logger.info(f'签名有效: {res}')
|
||||
return HttpResponse(json.dumps(res), content_type='application/json')
|
||||
except (ValueError, TypeError):
|
||||
logger.info(f'签名无效')
|
||||
print('签名无效')
|
||||
return HttpResponse(json.dumps({'msg': "支付失败"}), content_type='application/json')
|
||||
|
||||
def wx_resource(nonce, ciphertext, associated_data):
|
||||
key_bytes = str.encode(settings.WX_API_KEY) # APIv3_key(商户平台设置)
|
||||
nonce_bytes = str.encode(nonce)
|
||||
ad_bytes = str.encode(associated_data)
|
||||
data = base64.b64decode(ciphertext)
|
||||
aesgcm = AESGCM(key_bytes)
|
||||
plaintext = aesgcm.decrypt(nonce_bytes, data, ad_bytes)
|
||||
plaintext_str = bytes.decode(plaintext)
|
||||
|
||||
return eval(plaintext_str)
|
||||
|
||||
def update_user_membership_or_quota(openid, membership_type):
|
||||
current_time = int(time.time()) # 获取当前时间戳
|
||||
|
||||
try:
|
||||
user = User.objects.get(openid=openid)
|
||||
|
||||
if membership_type.is_quota:
|
||||
# 如果是额度充值,更新用户的金币数量
|
||||
user.coins += membership_type.coins
|
||||
logger.info(f'用户 {user.id} (openid: {openid}) 充值额度卡,增加金币 {membership_type.coins}')
|
||||
else:
|
||||
# 如果是会员充值,更新会员状态和时间
|
||||
user.is_member = True
|
||||
|
||||
if user.member_start_time is None: # 如果用户是第一次开会员
|
||||
user.member_start_time = current_time
|
||||
logger.info(f'第一次开会员:{current_time} -- {current_time + membership_type.duration_days * 24 * 3600}')
|
||||
elif user.member_end_time is None or user.member_end_time < current_time: # 如果会员已经过期
|
||||
logger.info(f'过期:{current_time} -- {current_time + membership_type.duration_days * 24 * 3600}')
|
||||
user.member_start_time = current_time
|
||||
|
||||
# 计算会员到期时间
|
||||
if user.member_end_time is None or user.member_end_time < current_time:
|
||||
logger.info(f'第一次开会员2:{user.member_end_time} -- {current_time + membership_type.duration_days * 24 * 3600}')
|
||||
user.member_end_time = current_time + membership_type.duration_days * 24 * 3600 # 将天数转换为秒
|
||||
else: # 如果会员尚未过期,则在现有会员结束时间上添加续费的天数
|
||||
logger.info(f'续费:{user.member_end_time} -- {user.member_end_time + membership_type.duration_days * 24 * 3600}')
|
||||
user.member_end_time += membership_type.duration_days * 24 * 3600
|
||||
|
||||
# 会员充值赠送金币
|
||||
user.coins += membership_type.coins
|
||||
|
||||
user.save()
|
||||
return True
|
||||
except User.DoesNotExist:
|
||||
# 处理用户不存在的情况
|
||||
logger.error(f'用户 {openid} 不存在')
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f'更新会员或额度充值时出错: {str(e)}')
|
||||
return False
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def wx_pay_notify(request):
|
||||
webData = json.loads(request.body)
|
||||
logger.info(f'回调返回信息:{webData}')
|
||||
ciphertext = webData['resource']['ciphertext']
|
||||
nonce = webData['resource']['nonce']
|
||||
associated_data = webData['resource']['associated_data']
|
||||
try:
|
||||
callback_data = wx_resource(nonce, ciphertext, associated_data)
|
||||
logger.info(f'回调返回信息:{callback_data}')
|
||||
mchid = callback_data.get('mchid')
|
||||
appid = callback_data.get('appid')
|
||||
out_trade_no = callback_data.get('out_trade_no')
|
||||
transaction_id = callback_data.get('transaction_id')
|
||||
trade_state = callback_data.get('trade_state')
|
||||
openid = callback_data.get('payer').get('openid')
|
||||
attach = callback_data.get('attach')
|
||||
attach = attach.replace("'", "\"")
|
||||
attach = json.loads(attach)
|
||||
type = attach.get('type')
|
||||
transaction_type = attach.get('transaction_type')
|
||||
success_time_str = callback_data.get('success_time')
|
||||
amount_total = callback_data.get('amount').get('total')
|
||||
success_time = datetime.strptime(success_time_str, "%Y-%m-%dT%H:%M:%S%z")
|
||||
logger.info("交易结果:")
|
||||
logger.info(f"商户号:{mchid}")
|
||||
logger.info(f"AppID:{appid}")
|
||||
logger.info(f"商户订单号:{out_trade_no}")
|
||||
logger.info(f"微信订单号:{transaction_id}")
|
||||
logger.info(f"交易状态:{trade_state}")
|
||||
logger.info(f"用户OpenID:{openid}")
|
||||
logger.info(f"type:{type}")
|
||||
logger.info(f"transaction_type:{transaction_type}")
|
||||
logger.info(f"成功时间:{success_time}")
|
||||
logger.info(f"金额(分):{amount_total}")
|
||||
transaction_log = TransactionLog.objects.get(transaction_no=out_trade_no)
|
||||
membership_type = MembershipType.objects.get(type=type) # 获取会员类型对象
|
||||
|
||||
# 验证订单信息一致性
|
||||
if (transaction_log.user_openid == openid) and (transaction_log.transaction_amount * 100 == amount_total) and (
|
||||
membership_type.price * 100 == amount_total):
|
||||
# 更新交易状态为完成
|
||||
transaction_log.transaction_status = 'completed'
|
||||
transaction_log.save()
|
||||
logger.info(f'交易记录更新成功:{transaction_log}')
|
||||
|
||||
# 更新用户的会员或额度
|
||||
success = update_user_membership_or_quota(openid, membership_type)
|
||||
|
||||
# 给邀请人返利
|
||||
user = User.objects.get(openid=openid)
|
||||
if user.inviter_nickname:
|
||||
inviter = User.objects.filter(nickname=user.inviter_nickname).first()
|
||||
if inviter:
|
||||
rebate_amount = round(Decimal(amount_total) * Decimal(0.35) / Decimal(100), 2) # 保留两位小数,单位为元
|
||||
inviter.balance += rebate_amount
|
||||
inviter.save()
|
||||
logger.info(f'邀请人{inviter.nickname}返利{rebate_amount}元成功')
|
||||
return HttpResponse(json.dumps({"code": "SUCCESS", "message": "成功"}))
|
||||
else:
|
||||
logger.warning('回调数据与订单记录不一致')
|
||||
return HttpResponse(json.dumps({"code": "FAIL", "message": "回调数据与订单记录不一致"}))
|
||||
except TransactionLog.DoesNotExist:
|
||||
logger.error(f'交易记录不存在')
|
||||
return HttpResponse(json.dumps({"code": "FAIL", "message": "交易记录不存在"}))
|
||||
except Exception as e:
|
||||
logger.error(f'处理回调时出错:{str(e)}')
|
||||
return HttpResponse(json.dumps({"code": "FAIL", "message": "处理回调时出错"}))
|
||||
|
25
MyApi/证书/apiclient_cert.pem
Normal file
25
MyApi/证书/apiclient_cert.pem
Normal file
@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIENzCCAx+gAwIBAgIUZts1+Dbv1MvsZvOBXSg6KJIxAyQwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjQwNDIwMDk0OTU5WhcNMjkwNDE5MDk0OTU5WjCBkDETMBEGA1UEAwwK
|
||||
MTY3NDAxMjkzMjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTwwOgYDVQQL
|
||||
DDPnpo/lt57luILpvJPmpbzljLrlpKnov5zkuablupfvvIjkuKrkvZPlt6XllYbm
|
||||
iLfvvIkxCzAJBgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAK3ueF4X+IVGCSajTvZycizthPIoxspbCTHu
|
||||
5XzLRDAiNL92w6sJIb488MKIhDJtdxw4RJjtf6W9qjv1D2wI2EldehTJfJkyTFCW
|
||||
xQlPh615dMDNayg/L4w6cIZLcjvbIl/RGAoZ7ldr5Z6+O+tFpejg9Sh4V7pU0Vhl
|
||||
ZlHolaCqVbuqcRIlybF9BpPpWJRu0jPFrMG5rDpzE6HnbNiAKpdc+8mZriHgeJI7
|
||||
unfd3zQj/Qy7j3cuASjhlXZQdA/IZvy/El7n46VDk4hZCfNHu5h1CJ/S89h+BSlS
|
||||
WBvCiZZAiKZS7ELjlHsA7bZf50LYokPyvDgx628dVszsVy0+GsECAwEAAaOBuTCB
|
||||
tjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGH
|
||||
hoGEaHR0cDovL2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0x
|
||||
QkQ0MjIwRTUwREJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0
|
||||
NzFCNjU0MjJFMTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQCvG+nV62rfX5kVxO8Uc8AL9UbpXIWi+6qL0+tBfR5YQWsYDY9n
|
||||
wMLoYYAwcQ+xTurQcsUA4WOFrhgkrelcyWu6jhBKhVBLEed2jL5pKLwt+CvCvTYk
|
||||
4Q1MW6f7P1Gs+/OMAleu55YGMCe1PqvtTypljT+mx8rHXtgLSNDtCO65jXRbbehs
|
||||
kxQalLrzGEeUaLk4UknDZJInALNZHeqT9k8NXBZrlIlM9V4oPb+Ai/FCXkO/kqHS
|
||||
b0OORRBgi74sLogXyQSma4uHhyNDGrmGSTBHu3If6l9QhEHSZjTQeXIK7wbBGgAw
|
||||
LDd8xm4cEWxi6xyssl0s/7qs+pG/eG47MP9J
|
||||
-----END CERTIFICATE-----
|
28
MyApi/证书/apiclient_key.pem
Normal file
28
MyApi/证书/apiclient_key.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCt7nheF/iFRgkm
|
||||
o072cnIs7YTyKMbKWwkx7uV8y0QwIjS/dsOrCSG+PPDCiIQybXccOESY7X+lvao7
|
||||
9Q9sCNhJXXoUyXyZMkxQlsUJT4eteXTAzWsoPy+MOnCGS3I72yJf0RgKGe5Xa+We
|
||||
vjvrRaXo4PUoeFe6VNFYZWZR6JWgqlW7qnESJcmxfQaT6ViUbtIzxazBuaw6cxOh
|
||||
52zYgCqXXPvJma4h4HiSO7p33d80I/0Mu493LgEo4ZV2UHQPyGb8vxJe5+OlQ5OI
|
||||
WQnzR7uYdQif0vPYfgUpUlgbwomWQIimUuxC45R7AO22X+dC2KJD8rw4MetvHVbM
|
||||
7FctPhrBAgMBAAECggEAOgEtYzPbTZbttlUAIHBKY3FSxO+UXCfACUcCgXvIYcUG
|
||||
klOpLYD+H9Ny921PqQGYl3Csb9PEniGChDxVyFGqz8y8yfHn+68qhDXDwDclqFS4
|
||||
+xOGiQWJddqHbEH89rk93XZ97eB3+++fxDDtCqlPizp6h+SaXSmsJy6p0Ocf/1RC
|
||||
IFXUz3J3DsKAHySklq4GyovSOWd6o/KzJiKfRDwn85Jc5uCXyM7rzVfXUWx1HOsr
|
||||
tPwXTAxE8opUsIP7BnAErgrGRZJc6gQunfbye5JQYxEeg1Hxkc+HbJHhbjueWMDW
|
||||
cMzappDQH4NL0QeYDRfhkeV4/glQHJhDegVmyAPlgQKBgQDcttEMVlNgWzKTY7Wv
|
||||
BoqUxH6ZMpvNDRehCqpIqoXYNodCKT66vYBXEfAGxIdIyDE+bEWKpSwSNX4l9Hay
|
||||
d52eKv73f7Go0Uo54SwMlsNBKA3xZ1terskJlsDwtvT7N387GUyZpv6Ej/qJLIIk
|
||||
WsuNgLKH8PQklaN8afnIIy2OvwKBgQDJvPqBKG8CP4CfxbKue063tkZdVjffyv+4
|
||||
w/Y81lMCu5tvfebF7drUVSYqaLSXD71ramViDe++PQxksqmk/H0dLQZVJuNdunk/
|
||||
w/iyjk/dI+wh2dILL7DwX6vaFiA+S6YjXWJkv148tHL0+tVotUOh9ETzBN8oO5R2
|
||||
2Ik5zHg2fwKBgGD7X74ZDIfRatbCwGmI6UnXUX2FdFpUf2Z+5jYJ38gSpbpXnz20
|
||||
2PjpY9vFbWB4vtKcBq6WR1g08xpAYgunbbW7VM9x56GzXPczzwQ1FtTBgebqMIbZ
|
||||
LHMvL9ZVvi3Iw/o28qatbzbWgRLXYNQSpVCnFUuQf+a3bd/UPTJI6tLHAoGBAKbi
|
||||
I8AQeLQ9X+PLoWJhMt8ac97mYdZJkX2A7gOpsIXEOHLkjbEScEHSJn5Sm4GTTbi8
|
||||
DR4uNTMEQWpTua54B+/IvUJXpyJ0DbVIIkVjIIHpI39lNzJ1w0M1gW4pe4bNAaSj
|
||||
ra919zj0dVxe24eA22/wJ9F5KDmZ/9jIi8w6bv91AoGAefCgs6tA9cA3dag9lWP+
|
||||
8+S4rpXSR7qCTmSfxCvWkq13vh4jlIFhChBpu+re91n9vyIUWgrlCcJUwft2WBqF
|
||||
60Huafsy3hQTuclm15sYtEkeuh9OKuzUKJ3mfTXYef6F8RKOOseuFtXnH5xmZEJl
|
||||
AifcseiIEMcIlBIlIjRZlSs=
|
||||
-----END PRIVATE KEY-----
|
Loading…
Reference in New Issue
Block a user