first commit

This commit is contained in:
root 2024-09-20 04:29:09 +00:00
commit 5383007f49
459 changed files with 88755 additions and 0 deletions

1
.htaccess Executable file
View File

@ -0,0 +1 @@
# 请将伪静态规则或自定义Apache配置填写到此处

View File

@ -0,0 +1,499 @@
from django.apps import apps
from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.hashers import make_password
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.db import models
from django.db.models.manager import EmptyManager
from django.utils import timezone
from django.utils.itercompat import is_iterable
from django.utils.translation import gettext_lazy as _
from .validators import UnicodeUsernameValidator
def update_last_login(sender, user, **kwargs):
"""
A signal receiver which updates the last_login date for
the user logging in.
"""
user.last_login = timezone.now()
user.save(update_fields=["last_login"])
class PermissionManager(models.Manager):
use_in_migrations = True
def get_by_natural_key(self, codename, app_label, model):
return self.get(
codename=codename,
content_type=ContentType.objects.db_manager(self.db).get_by_natural_key(
app_label, model
),
)
class Permission(models.Model):
"""
The permissions system provides a way to assign permissions to specific
users and groups of users.
The permission system is used by the Django admin site, but may also be
useful in your own code. The Django admin site uses permissions as follows:
- The "add" permission limits the user's ability to view the "add" form
and add an object.
- The "change" permission limits a user's ability to view the change
list, view the "change" form and change an object.
- The "delete" permission limits the ability to delete an object.
- The "view" permission limits the ability to view an object.
Permissions are set globally per type of object, not per specific object
instance. It is possible to say "Mary may change news stories," but it's
not currently possible to say "Mary may change news stories, but only the
ones she created herself" or "Mary may only change news stories that have a
certain status or publication date."
The permissions listed above are automatically created for each model.
"""
name = models.CharField(_("name"), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_("content type"),
)
codename = models.CharField(_("codename"), max_length=100)
objects = PermissionManager()
class Meta:
verbose_name = _("permission")
verbose_name_plural = _("permissions")
unique_together = [["content_type", "codename"]]
ordering = ["content_type__app_label", "content_type__model", "codename"]
def __str__(self):
return "%s | %s" % (self.content_type, self.name)
def natural_key(self):
return (self.codename,) + self.content_type.natural_key()
natural_key.dependencies = ["contenttypes.contenttype"]
class GroupManager(models.Manager):
"""
The manager for the auth's Group model.
"""
use_in_migrations = True
def get_by_natural_key(self, name):
return self.get(name=name)
class Group(models.Model):
"""
Groups are a generic way of categorizing users to apply permissions, or
some other label, to those users. A user can belong to any number of
groups.
A user in a group automatically has all the permissions granted to that
group. For example, if the group 'Site editors' has the permission
can_edit_home_page, any user in that group will have that permission.
Beyond permissions, groups are a convenient way to categorize users to
apply some label, or extended functionality, to them. For example, you
could create a group 'Special users', and you could write code that would
do special things to those users -- such as giving them access to a
members-only portion of your site, or sending them members-only email
messages.
"""
name = models.CharField(_("name"), max_length=150, unique=True)
permissions = models.ManyToManyField(
Permission,
verbose_name=_("permissions"),
blank=True,
)
objects = GroupManager()
class Meta:
verbose_name = _("group")
verbose_name_plural = _("groups")
def __str__(self):
return self.name
def natural_key(self):
return (self.name,)
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError("The given username must be set")
email = self.normalize_email(email)
# Lookup the real model class from the global app registry so this
# manager method can be used in migrations. This is fine because
# managers are by definition working on the real model.
GlobalUserModel = apps.get_model(
self.model._meta.app_label, self.model._meta.object_name
)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(username, email, password, **extra_fields)
def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
return self._create_user(username, email, password, **extra_fields)
def with_perm(
self, perm, is_active=True, include_superusers=True, backend=None, obj=None
):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
"You have multiple authentication backends configured and "
"therefore must provide the `backend` argument."
)
elif not isinstance(backend, str):
raise TypeError(
"backend must be a dotted import path string (got %r)." % backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, "with_perm"):
return backend.with_perm(
perm,
is_active=is_active,
include_superusers=include_superusers,
obj=obj,
)
return self.none()
# A few helper functions for common logic between User and AnonymousUser.
def _user_get_permissions(user, obj, from_name):
permissions = set()
name = "get_%s_permissions" % from_name
for backend in auth.get_backends():
if hasattr(backend, name):
permissions.update(getattr(backend, name)(user, obj))
return permissions
def _user_has_perm(user, perm, obj):
"""
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, "has_perm"):
continue
try:
if backend.has_perm(user, perm, obj):
return True
except PermissionDenied:
return False
return False
def _user_has_module_perms(user, app_label):
"""
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, "has_module_perms"):
continue
try:
if backend.has_module_perms(user, app_label):
return True
except PermissionDenied:
return False
return False
class PermissionsMixin(models.Model):
"""
Add the fields and methods necessary to support the Group and Permission
models using the ModelBackend.
"""
is_superuser = models.BooleanField(
_("superuser status"),
default=False,
help_text=_(
"Designates that this user has all permissions without "
"explicitly assigning them."
),
)
groups = models.ManyToManyField(
Group,
verbose_name=_("groups"),
blank=True,
help_text=_(
"The groups this user belongs to. A user will get all permissions "
"granted to each of their groups."
),
related_name="user_set",
related_query_name="user",
)
user_permissions = models.ManyToManyField(
Permission,
verbose_name=_("user permissions"),
blank=True,
help_text=_("Specific permissions for this user."),
related_name="user_set",
related_query_name="user",
)
class Meta:
abstract = True
def get_user_permissions(self, obj=None):
"""
Return a list of permission strings that this user has directly.
Query all available auth backends. If an object is passed in,
return only permissions matching this object.
"""
return _user_get_permissions(self, obj, "user")
def get_group_permissions(self, obj=None):
"""
Return a list of permission strings that this user has through their
groups. Query all available auth backends. If an object is passed in,
return only permissions matching this object.
"""
return _user_get_permissions(self, obj, "group")
def get_all_permissions(self, obj=None):
return _user_get_permissions(self, obj, "all")
def has_perm(self, perm, obj=None):
"""
Return True if the user has the specified permission. Query all
available auth backends, but return immediately if any backend returns
True. Thus, a user who has permission from a single auth backend is
assumed to have permission in general. If an object is provided, check
permissions for that object.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return _user_has_perm(self, perm, obj)
def has_perms(self, perm_list, obj=None):
"""
Return True if the user has each of the specified permissions. If
object is passed, check if the user has all required perms for it.
"""
if not is_iterable(perm_list) or isinstance(perm_list, str):
raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list)
def has_module_perms(self, app_label):
"""
Return True if the user has any permissions in the given app label.
Use similar logic as has_perm(), above.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
return _user_has_module_perms(self, app_label)
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = "email"
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = "%s %s" % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = "AUTH_USER_MODEL"
class AnonymousUser:
id = None
pk = None
username = ""
is_staff = False
is_active = False
is_superuser = False
_groups = EmptyManager(Group)
_user_permissions = EmptyManager(Permission)
def __str__(self):
return "AnonymousUser"
def __eq__(self, other):
return isinstance(other, self.__class__)
def __hash__(self):
return 1 # instances always return the same hash value
def __int__(self):
raise TypeError(
"Cannot cast AnonymousUser to int. Are you trying to use it in place of "
"User?"
)
def save(self):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
def delete(self):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
def set_password(self, raw_password):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
def check_password(self, raw_password):
raise NotImplementedError(
"Django doesn't provide a DB representation for AnonymousUser."
)
@property
def groups(self):
return self._groups
@property
def user_permissions(self):
return self._user_permissions
def get_user_permissions(self, obj=None):
return _user_get_permissions(self, obj, "user")
def get_group_permissions(self, obj=None):
return set()
def get_all_permissions(self, obj=None):
return _user_get_permissions(self, obj, "all")
def has_perm(self, perm, obj=None):
return _user_has_perm(self, perm, obj=obj)
def has_perms(self, perm_list, obj=None):
if not is_iterable(perm_list) or isinstance(perm_list, str):
raise ValueError("perm_list must be an iterable of permissions.")
return all(self.has_perm(perm, obj) for perm in perm_list)
def has_module_perms(self, module):
return _user_has_module_perms(self, module)
@property
def is_anonymous(self):
return True
@property
def is_authenticated(self):
return False
def get_username(self):
return self.username

7280
API日志.log Executable file

File diff suppressed because one or more lines are too long

34459
API日志.log.2024-08-29 Executable file

File diff suppressed because one or more lines are too long

1
API日志.log.2024-08-30 Executable file
View File

@ -0,0 +1 @@
2024-09-04 13:54:21,364 - API日志 - INFO - {'frameRate': 60, 'resolution': '720p', 'frameDurationMultiplier': 18, 'webhook': 'https://www.typeframes.cc/api/webhook/?userid=10&videoid=1725429259_10', 'creationParams': {'mediaType': 'stockVideo', 'origin': '/create', 'inputText': '宇宙的故事始于138亿年前的一场大爆炸从这一点爆发出无尽的能量和物质逐渐形成了今天我们所见的银河系、恒星和行星。在这一过程中重力将物质聚集成团恒星点燃行星诞生生命在地球上萌芽。然而这一壮丽的宇宙图景可能终将走向衰亡', 'flowType': 'text-to-video', 'slug': 'create-tiktok-video', 'disableCaptions': True, 'hasToGenerateVoice': False, 'hasToTranscript': False, 'hasToSearchMedia': True, 'hasAvatar': False, 'hasWebsiteRecorder': False, 'hasTextSmallAtBottom': False, 'captionPresetName': 'Wrap 1', 'captionPositionName': 'bottom', 'disableAudio': True, 'ratio': '16 / 9', 'selectedAudio': 'Bladerunner 2049', 'selectedVoice': 'zh-CN-YunxiNeural', 'sourceType': 'contentScraping', 'selectedStoryStyle': {'value': 'custom', 'label': 'Custom'}, 'hasToGenerateVideos': False, 'selectedRecording': 'https://www.typeframes.cc/media/63508bd2-3d2a-418b-900b-4de3457f243c.mp3', 'selectedRecordingType': 'audio', 'generationPreset': 'LEONARDO', 'audioUrl': 'https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3'}}

9336
API日志.log.2024-09-04 Executable file

File diff suppressed because one or more lines are too long

1811
API日志.log.2024-09-09 Executable file

File diff suppressed because one or more lines are too long

3809
API日志.log.2024-09-11 Executable file

File diff suppressed because one or more lines are too long

3
API日志.log.2024-09-13 Executable file
View File

@ -0,0 +1,3 @@
2024-09-15 00:21:22,512 - API日志 - ERROR - Error fetching video list: invalid literal for int() with base 10: '10?url=/api/video-list/?page=2&page_size=10'
2024-09-15 00:21:23,716 - API日志 - ERROR - Error fetching video list: invalid literal for int() with base 10: '10?url=/api/video-list/?page=2&page_size=10'
2024-09-15 00:21:35,863 - API日志 - ERROR - Error fetching video list: invalid literal for int() with base 10: '10?url=/api/video-list/?page=2&page_size=10'

20335
API日志.log.2024-09-15 Executable file

File diff suppressed because one or more lines are too long

23
Dockerfile Executable file
View File

@ -0,0 +1,23 @@
# 使用 Python 3.11.0 作为基础镜像
FROM python:3.11.0
# 设置工作目录
WORKDIR /app
# 复制 requirements 文件以便安装依赖
COPY requirements.txt .
# 安装依赖
RUN pip install -r requirements.txt
# 安装 uwsgi 并检查是否成功安装
RUN pip install --no-cache-dir uwsgi
# 复制项目文件到工作目录
COPY . .
# 暴露应用的端口
EXPOSE 3001
# 使用 uWSGI 启动 Django 服务
CMD ["uwsgi", "--http", "0.0.0.0:3001", "--module", "WebSite.wsgi:application", "--master", "--processes", "4", "--threads", "2", "--harakiri", "20", "--max-requests", "5000", "--vacuum", "--touch-reload=/app"]

69
WebAdmin/AccessLog.py Executable file
View File

@ -0,0 +1,69 @@
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from datetime import datetime
from .models import WebsiteAccessLog, WebsiteInfo
from .utils import (
get_client_ip,
get_browser_language,
get_referrer,
get_user_agent,
get_device_type,
get_beijing_time,
)
@csrf_exempt
def website_info_view(request):
"""
提供网站配置信息并记录访问日志
"""
print("当前 UTC 时间:", datetime.now())
try:
# 获取访问信息
client_ip = get_client_ip(request)
browser_language = get_browser_language(request)
referrer = get_referrer(request)
request_path = request.path
request_method = request.method
user_agent = get_user_agent(request)
device_type = get_device_type(user_agent)
# 获取当前 UTC 时间并转换为北京时间
access_time_bj = datetime.now()
access_date = datetime.now()
# 检查同一 IP 当天是否已经记录过
if not WebsiteAccessLog.objects.filter(ip_address=client_ip, access_date=access_date).exists():
# 记录访问日志
WebsiteAccessLog.objects.create(
ip_address=client_ip,
browser_language=browser_language,
referrer=referrer,
request_path=request_path,
request_method=request_method,
user_agent=user_agent,
device_type=device_type,
access_time_bj=access_time_bj,
access_date=access_date,
)
# 获取网站配置信息
website_info = WebsiteInfo.objects.first()
if not website_info:
return JsonResponse({'code': 404, 'message': '未找到网站配置信息'})
# 返回网站配置信息
data = {
'domain_en': website_info.domain_en,
'title_en': website_info.title_en,
'keywords_en': website_info.keywords_en,
'description_en': website_info.description_en,
'domain_zh': website_info.domain_zh,
'title_zh': website_info.title_zh,
'keywords_zh': website_info.keywords_zh,
'description_zh': website_info.description_zh,
}
return JsonResponse({'code': 200, 'message': 'ok', 'data': data})
except Exception as e:
return JsonResponse({'code': 500, 'message': f'服务器内部错误: {str(e)}'})

0
WebAdmin/__init__.py Executable file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
WebAdmin/admin.py Executable file
View File

@ -0,0 +1,13 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
class CustomUserAdmin(UserAdmin):
model = User
fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('phone', 'google_id', 'is_member', 'points', 'invited_by', 'referral_code', 'commission_rate', 'login_count', 'last_login_ip')}),
)
list_display = ('id', 'username', 'email', 'phone', 'is_member', 'points', 'referral_code', 'commission_rate', 'login_count', 'last_login_ip') # 显示 id 字段
search_fields = ('username', 'email', 'phone', 'referral_code')
admin.site.register(User, CustomUserAdmin)

37
WebAdmin/admin_views.py Executable file
View File

@ -0,0 +1,37 @@
import json
import re
from django.contrib.auth import authenticate, login as auth_login
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from allauth.account.auth_backends import AuthenticationBackend
from .models import User
@csrf_exempt
def admin_login_view(request):
if request.user.is_authenticated:
return redirect('admin_home')
if request.method == 'POST':
try:
body = json.loads(request.body)
username = body.get('username')
password = body.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
user.backend = 'allauth.account.auth_backends.AuthenticationBackend'
auth_login(request, user)
return JsonResponse({'code': 200, 'message': '登录成功'})
else:
return JsonResponse({'code': 400, 'message': '用户名或密码错误'})
except json.JSONDecodeError:
return JsonResponse({'code': 400, 'message': '无效的请求数据'})
except Exception as e:
return JsonResponse({'code': 500, 'message': f'服务器内部错误: {str(e)}'})
return render(request, 'admin/admin_login.html')
@login_required
def admin_home_view(request):
return render(request, 'admin/home.html')

331
WebAdmin/ali_pay.py Executable file
View File

@ -0,0 +1,331 @@
import json
import traceback
from decimal import Decimal
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModel
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
from alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequest
from .models import Order, User, Plan
from datetime import datetime
from alipay.aop.api.domain.AlipayTradeWapPayModel import AlipayTradeWapPayModel
from alipay.aop.api.request.AlipayTradeWapPayRequest import AlipayTradeWapPayRequest
import requests
from decimal import Decimal, getcontext
# 设置 Decimal 的精度
getcontext().prec = 28
def get_usd_to_cny_rate():
a = 7.2
return a
def get_alipay_client_config(client_type='pc'):
"""
根据环境和客户端类型返回支付宝配置
"""
alipay_client_config = AlipayClientConfig()
if settings.ALIPAY_DEBUG:
print('请求沙箱支付')
alipay_client_config.server_url = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
if client_type == 'pc':
alipay_client_config.app_id = settings.ALIPAY_APP_ID_SANDBOX_PC
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_SANDBOX_PC
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_SANDBOX_PC
elif client_type == 'mobile':
alipay_client_config.app_id = settings.ALIPAY_APP_ID_SANDBOX_MOBILE
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_SANDBOX_MOBILE
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_SANDBOX_MOBILE
else:
print('请求生产支付')
alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'
if client_type == 'pc':
alipay_client_config.app_id = settings.ALIPAY_APP_ID_PRODUCTION_PC
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_PRODUCTION_PC
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_PRODUCTION_PC
elif client_type == 'mobile':
alipay_client_config.app_id = settings.ALIPAY_APP_ID_PRODUCTION_MOBILE
alipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY_PRODUCTION_MOBILE
alipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY_PRODUCTION_MOBILE
return alipay_client_config
def convert_cny_to_usd(price_cny):
"""
将人民币价格转换为美元价格
:param price_cny: 人民币价格
:return: 美元价格保留两位小数
"""
# 获取美元对人民币的汇率
usd_to_cny_rate = get_usd_to_cny_rate()
print("原价:", price_cny)
print("汇率:", usd_to_cny_rate)
return (Decimal(price_cny) * Decimal(usd_to_cny_rate)).quantize(Decimal('0.00'))
@csrf_exempt
def create_alipay_order(request):
"""
创建支付宝订单并生成支付链接电脑端
"""
try:
# 检查用户是否已登录
if not request.user.is_authenticated:
print("用户未登录")
return JsonResponse({"code": 401, "message": "用户未登录"})
# 获取前端传递的数据
data = json.loads(request.body)
print(f"接收到的请求数据: {data}")
plan_title = data.get('title')
price = Decimal(data.get('price')) # 将价格转换为 Decimal 类型
plan_id = data.get('id')
# 从数据库获取对应的套餐信息
plan = Plan.objects.get(id=plan_id)
print(f"数据库中获取的套餐信息: {plan}")
if not plan:
print("套餐信息不存在")
return JsonResponse({"code": 400, "message": "套餐信息不存在"})
# 检查汇率是否有效
# 获取美元对人民币的汇率
usd_to_cny_rate = get_usd_to_cny_rate()
if not usd_to_cny_rate:
print("无法进行价格转换,汇率不可用")
return JsonResponse({"code": 500, "message": "汇率不可用,无法创建订单"})
# 将套餐的人民币价格转换为美元价格
plan_price_usd = convert_cny_to_usd(plan.price)
# 将人民币价格转换为美元价格
price_usd = convert_cny_to_usd(price)
# 比较转换后的美元价格
if not price_usd or plan_price_usd != price_usd:
print(f"{plan_price_usd}套餐信息不匹配或价格不正确{price_usd}")
return JsonResponse({"code": 400, "message": "套餐信息不匹配或价格不正确"})
final_price = price_usd
# 获取当前用户信息
user = request.user
# 生成订单
order_id = f"order_{datetime.now().strftime('%Y%m%d%H%M%S')}_{user.id}"
order = Order.objects.create(
order_id=order_id,
user=user,
plan=plan,
username=user.username,
amount=final_price,
payment_method='alipay',
status='pending'
)
print(f"创建的订单信息: {order}")
# 获取支付宝电脑端客户端配置
alipay_client_config = get_alipay_client_config(client_type='pc')
client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
# 构造支付宝支付请求对象
model = AlipayTradePagePayModel()
model.out_trade_no = order_id # 订单ID
model.total_amount = str(final_price) # 订单金额
model.subject = plan_title # 商品名称
model.product_code = "FAST_INSTANT_TRADE_PAY" # 产品码
print(f"生成订单请求体: {model}")
request = AlipayTradePagePayRequest(biz_model=model)
request.notify_url = settings.ALIPAY_NOTIFY_URL # 异步通知URL
request.return_url = settings.ALIPAY_RETURN_URL # 同步返回URL
print(f"回调地址: {settings.ALIPAY_NOTIFY_URL}")
response = client.page_execute(request, http_method="GET")
print(f"支付宝请求响应: {response}")
# 返回支付URL
return JsonResponse({"code": 200, "message": "订单创建成功", "data": {'alipay_url': response}})
except Exception as e:
print(f"创建订单时出错: {traceback.format_exc()}")
return JsonResponse({"code": 500, "message": f"创建订单时出错: {str(e)}"})
@csrf_exempt
def create_alipay_h5_order(request):
"""
创建支付宝H5订单并生成支付链接
"""
try:
# 检查用户是否已登录
if not request.user.is_authenticated:
return JsonResponse({"code": 401, "message": "用户未登录"})
# 获取前端传递的数据
data = json.loads(request.body)
plan_id = data.get('id')
plan_title = data.get('title')
price = Decimal(data.get('price')) # 将价格转换为 Decimal 类型
# 获取对应套餐信息
plan = Plan.objects.get(id=plan_id)
if not plan:
return JsonResponse({"code": 400, "message": "套餐信息不存在"})
# 检查汇率是否有效
# 获取美元对人民币的汇率
usd_to_cny_rate = get_usd_to_cny_rate()
if not usd_to_cny_rate:
print("无法进行价格转换,汇率不可用")
return JsonResponse({"code": 500, "message": "汇率不可用,无法创建订单"})
# 将套餐的人民币价格转换为美元价格
plan_price_usd = convert_cny_to_usd(plan.price)
# 将人民币价格转换为美元价格
price_usd = convert_cny_to_usd(price)
# 比较转换后的美元价格
if not price_usd or plan_price_usd != price_usd:
return JsonResponse({"code": 400, "message": "套餐信息不匹配或价格不正确"})
final_price = price_usd
# 创建订单
user = request.user
order_id = f"order_{datetime.now().strftime('%Y%m%d%H%M%S')}_{user.id}"
order = Order.objects.create(
order_id=order_id,
user=user,
plan=plan,
username=user.username,
amount=final_price,
payment_method='alipay',
status='pending'
)
# 获取支付宝H5客户端配置
alipay_client_config = get_alipay_client_config(client_type='mobile')
client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
# 构造支付宝H5支付请求对象
model = AlipayTradeWapPayModel()
model.out_trade_no = order_id
model.total_amount = str(final_price)
model.subject = plan_title
model.product_code = "QUICK_WAP_WAY"
request = AlipayTradeWapPayRequest(biz_model=model)
request.notify_url = settings.ALIPAY_NOTIFY_URL # 异步通知URL
request.return_url = settings.ALIPAY_RETURN_URL # 同步返回URL
# 执行请求获取支付宝H5支付跳转链接
response = client.page_execute(request, http_method="GET")
return JsonResponse({"code": 200, "message": "订单创建成功", "data": {'alipay_url': response}})
except Exception as e:
return JsonResponse({"code": 500, "message": f"创建H5订单时出错: {str(e)}"})
def update_user_membership(order):
"""
更新用户积分信息
"""
try:
# 验证订单状态是否为成功
if order.status not in ("completed", "TRADE_SUCCESS", "TRADE_FINISHED"):
print("订单未成功")
return False
# 从订单中获取关联的套餐信息
plan = order.plan
# 检查汇率是否有效
# 获取美元对人民币的汇率
usd_to_cny_rate = get_usd_to_cny_rate()
if not usd_to_cny_rate:
print("无法进行价格转换,汇率不可用")
return False
# 将订单金额从美元转换回人民币
order_amount_cny = order.amount
# 确保订单的套餐与实际套餐详情匹配
if not plan or convert_cny_to_usd(plan.price) != order_amount_cny:
print(f"订单的套餐信息与实际套餐不匹配plan.price{plan.price}---")
return False
# 获取用户信息
user = order.user
# 更新用户的积分
user.points += plan.credits_per_month
user.save()
print(f"用户 {user.username} 的积分已更新")
return True
except Exception as e:
print(f"更新用户积分信息时出错: {e}")
return False
@csrf_exempt
def alipay_notify(request):
"""
支付宝异步通知处理验证支付结果
"""
try:
data = request.POST.dict()
print(f"接收到的异步通知数据: {data}")
# 直接使用返回的数据处理逻辑
order_id = data.get('out_trade_no')
trade_status = data.get('trade_status')
print(f"订单ID: {order_id}, 支付状态: {trade_status}")
try:
order = Order.objects.get(order_id=order_id)
except Order.DoesNotExist:
print(f"订单 {order_id} 不存在")
return JsonResponse({"code": 400, "message": "订单不存在"})
if trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
order.status = 'completed'
order.save()
print(f"订单 {order_id} 支付成功")
update_user_membership(order)
return JsonResponse({"code": 200, "message": "支付成功"})
else:
order.status = 'failed'
order.save()
print(f"订单 {order_id} 支付失败或未成功")
return JsonResponse({"code": 400, "message": "支付未成功或失败"})
except Exception as e:
print(f"处理异步通知时出错: {traceback.format_exc()}")
return JsonResponse({"code": 500, "message": f"处理异步通知时出错: {str(e)}"})
@csrf_exempt
def alipay_return(request):
"""
支付宝同步返回处理用于用户支付后的页面跳转
"""
try:
data = request.GET.dict()
print(f"接收到的同步返回数据: {data}")
# 直接使用返回的数据处理逻辑
order_id = data.get('out_trade_no')
print(f"订单 {order_id} 支付成功")
return JsonResponse({"code": 200, "message": f"支付成功,订单号:{order_id}"})
except Exception as e:
print(f"处理同步返回时出错: {traceback.format_exc()}")
return JsonResponse({"code": 500, "message": f"处理同步返回时出错: {str(e)}"})

63
WebAdmin/aliyun_sms.py Executable file
View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
import os
import json
import hashlib
import hmac
import time
from datetime import datetime, timedelta
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
from django.utils.crypto import get_random_string
from WebSite import settings
from .models import SMSVerificationCode
def create_client() -> Dysmsapi20170525Client:
config = open_api_models.Config(
access_key_id=settings.ALIBABA_CLOUD_ACCESS_KEY_ID,
access_key_secret=settings.ALIBABA_CLOUD_ACCESS_KEY_SECRET
)
config.endpoint = 'dysmsapi.aliyuncs.com'
return Dysmsapi20170525Client(config)
def send_verification_sms(phone_number: str, sign_name: str, template_code: str) -> dict:
client = create_client()
code = get_random_string(length=6, allowed_chars='0123456789')
template_param = json.dumps({"code": code})
send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
phone_numbers=phone_number,
sign_name=sign_name,
template_code=template_code,
template_param=template_param
)
runtime = util_models.RuntimeOptions()
try:
response = client.send_sms_with_options(send_sms_request, runtime)
response_data = response.body.to_map()
if response_data.get("Code") == "OK":
SMSVerificationCode.objects.create(phone_number=phone_number, code=code, created_at=datetime.now())
return {'code': 200, 'message': '验证码发送成功'}
else:
return {'code': 400, 'message': f'发送短信失败: {response_data.get("Message")}'}
except Exception as error:
return {'code': 500, 'message': f'发送短信异常: {str(error)}'}
def verify_sms_code(phone_number: str, code: str) -> dict:
try:
sms_code = SMSVerificationCode.objects.filter(phone_number=phone_number, code=code, is_used=False).latest('created_at')
if sms_code is None:
return {'code': 400, 'message': '请先获取验证码'}
if sms_code.created_at < datetime.now() - timedelta(minutes=5):
return {'code': 400, 'message': '验证码已过期'}
sms_code.is_used = True
sms_code.save()
return {'code': 200, 'message': '验证码验证成功'}
except SMSVerificationCode.DoesNotExist:
return {'code': 400, 'message': '验证码无效或已过期'}

500
WebAdmin/api.py Executable file
View File

@ -0,0 +1,500 @@
import time
import requests
import json
import uuid
import os
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from datetime import datetime
from .create_tiktok_video import create_video
from .create_avatar_video import create_avatar_video_payload
from .create_music_video import create_music_video_payload
from .models import VideoGeneration, User, Plan, Order, WebsiteInfo
from .base import logger,synthesize_speech,upload_avatar,deduct_points
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
def generate_video_id(user_id):
timestamp = int(time.time())
video_id = f"{timestamp}_{user_id}"
return video_id
@csrf_exempt
@require_http_methods(["POST"])
def handle_generate_tiktok_video_request(request):
"""
处理生成 create-tiktok-video 视频请求的接口
:param request: HTTP 请求对象
:return: 包含生成状态或错误信息的JSON响应
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
data = json.loads(request.body)
text = data.get('text', '')
voice_name = data.get('voice', '')
style = data.get('style', 'general')
rate = data.get('rate', 0)
media_type = data.get('mediaType', '')
ratio = data.get('ratio', '')
slug = data.get('slug', 'create-tiktok-video')
caption_preset = data.get('captionPreset', 'Wrap 1')
caption_position = data.get('captionPosition', 'bottom')
disableCaptions = data.get('disableCaptions',True)
generationPreset = data.get('generationPreset','LEONARDO') #ANIME
if not all([text, voice_name, media_type, ratio, slug, caption_preset, caption_position]):
return JsonResponse({'code': 400, 'message': '缺少必要参数'})
user = User.objects.get(id=request.user.id)
# 计算所需积分
result = deduct_points(user, "create-tiktok-video", text=text)
if result is not True:
return result # 返回积分不足的响应
video_gen = VideoGeneration.objects.create(
user=user,
text=text,
voice_name=voice_name,
style=style,
rate=rate,
media_type=media_type,
ratio=ratio,
slug=slug,
video_id=generate_video_id(user.id)
)
audio_result = synthesize_speech(text, voice_name, style, rate)
if audio_result['code'] != 200:
video_gen.status = 'failed'
video_gen.save()
return JsonResponse(audio_result)
audio_url = audio_result['audio_url']
video_gen.audio_url = audio_url
video_gen.status = 'in_progress'
video_gen.save()
video_result = create_video(
audio_url, generationPreset,media_type, text, ratio, voice_name, user.id, slug,disableCaptions, caption_preset, caption_position, video_id=video_gen.video_id
)
if video_result['code'] == 200:
video_gen.status = 'Pending'
video_gen.pid = video_result['data']['pid']
video_gen.save()
return JsonResponse(video_result)
else:
video_gen.status = 'Failed'
video_gen.save()
return JsonResponse({"code": 405, "message": "生成失败"})
except json.JSONDecodeError:
return JsonResponse({"code": 400, "message": "无效的JSON数据"})
except Exception as e:
logger.error(f"处理视频生成请求时出错: {str(e)}")
return JsonResponse({"code": 500, "message": str(e)})
@csrf_exempt
@require_http_methods(["POST"])
def handle_generate_avatar_video_request(request):
"""
处理生成 create-avatar-video 视频请求的接口
:param request: HTTP 请求对象
:return: 包含生成状态或错误信息的JSON响应
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
data = json.loads(request.body)
text = data.get('text', '')
voice_name = data.get('voice', '')
style = data.get('style', 'general')
rate = data.get('rate', 0)
media_type = data.get('mediaType', '')
ratio = data.get('ratio', '9 / 16')
slug = 'create-avatar-video'
caption_preset = data.get('captionPreset', 'Wrap 1')
caption_position = data.get('captionPosition', 'bottom')
selected_avatar = data.get('selectedAvatar', '')
disableCaptions = data.get('disableCaptions',True)
generationPreset = data.get('generationPreset','LEONARDO') #ANIME
if not all([text, voice_name, media_type, ratio, slug, caption_preset, caption_position, selected_avatar]):
return JsonResponse({'code': 400, 'message': '缺少必要参数'})
user = User.objects.get(id=request.user.id)
# 计算所需积分
result = deduct_points(user, "create-avatar-video", text=text)
if result is not True:
return result # 返回积分不足的响应
video_gen = VideoGeneration.objects.create(
user=user,
text=text,
voice_name=voice_name,
style=style,
rate=rate,
media_type=media_type,
ratio=ratio,
slug=slug,
video_id=generate_video_id(user.id)
)
audio_result = synthesize_speech(text, voice_name, style, rate)
if audio_result['code'] != 200:
video_gen.status = 'failed'
video_gen.save()
return JsonResponse(audio_result)
audio_url = audio_result['audio_url']
video_gen.audio_url = audio_url
video_gen.status = 'in_progress'
video_gen.save()
video_result = create_avatar_video_payload(
audio_url,generationPreset, media_type, text, ratio, voice_name, user.id, slug,disableCaptions, caption_preset, caption_position, selected_avatar, video_id=video_gen.video_id
)
if video_result['code'] == 200:
video_gen.status = 'Pending'
video_gen.pid = video_result['data']['pid']
video_gen.save()
return JsonResponse(video_result)
else:
video_gen.status = 'Failed'
video_gen.save()
return JsonResponse({"code": 405, "message": "生成失败"})
except json.JSONDecodeError:
return JsonResponse({"code": 400, "message": "无效的JSON数据"})
except Exception as e:
logger.error(f"处理头像视频生成请求时出错: {str(e)}")
return JsonResponse({"code": 500, "message": str(e)})
@csrf_exempt
@require_http_methods(["POST"])
def handle_generate_music_video_request(request):
"""
处理生成 create-music-video 视频请求的接口
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
data = json.loads(request.body)
audio_url = data.get('audioUrl', '')
media_type = data.get('mediaType', 'stockVideo')
ratio = data.get('ratio', '9 / 16')
slug = data.get('slug', 'create-music-video')
caption_preset = data.get('captionPreset', 'Wrap 1')
caption_position = data.get('captionPosition', 'bottom')
disableCaptions = data.get('disableCaptions',True)
generationPreset = data.get('generationPreset','LEONARDO') #ANIME
if not all([audio_url, media_type, ratio, slug, caption_preset, caption_position]):
return JsonResponse({'code': 400, 'message': '缺少必要参数'})
user = User.objects.get(id=request.user.id)
result = deduct_points(user, "create-music-video")
if result is not True:
return result # 返回积分不足的响应
video_gen = VideoGeneration.objects.create(
user=user,
text='',
voice_name='',
style='',
rate=0,
media_type=media_type,
ratio=ratio,
slug=slug,
video_id=generate_video_id(user.id)
)
video_result = create_music_video_payload(
audio_url=audio_url,
generationPreset=generationPreset,
media_type=media_type,
text='',
ratio=ratio,
voice_name='',
style='',
rate=0,
user_id=user.id,
slug=slug,
disableCaptions=disableCaptions,
caption_preset=caption_preset,
caption_position=caption_position,
video_id=video_gen.video_id
)
print(video_result)
if video_result['code'] == 200:
video_gen.status = 'Pending'
video_gen.pid = video_result['data']['pid']
video_gen.save()
return JsonResponse(video_result)
else:
video_gen.status = 'Failed'
video_gen.save()
return JsonResponse({"code": 405, "message": "生成失败"})
except json.JSONDecodeError:
return JsonResponse({"code": 400, "message": "无效的JSON数据"})
except Exception as e:
logger.error(f"处理音乐视频生成请求时出错: {str(e)}")
return JsonResponse({"code": 500, "message": str(e)})
@csrf_exempt
def webhook(request):
"""
处理生成视频回调的函数
"""
if request.method == 'POST':
try:
# 获取原始的 POST 数据
raw_data = request.body
logger.info("Received raw data:\n%s\n", raw_data)
# 解析 JSON 数据
data = json.loads(raw_data)
if not data:
return HttpResponse(status=400) # Bad Request
# 记录完整的接收到的数据
logger.info("Received complete callback data:\n%s\n", json.dumps(data, indent=4))
# 提取基本信息
video_url = data.get('videoUrl', '') # 编辑视频 URL
video_id = data.get('videoId', '') # 视频 ID
user_id = request.GET.get('userid', '')
if user_id and video_id:
print('回调更新状态')
try:
user = User.objects.get(id=user_id)
video_gen = VideoGeneration.objects.filter(user=user, video_id=video_id).first()
if video_gen:
if video_url:
video_gen.video_url = video_url
video_gen.status = 'completed'
else:
video_gen.status = 'failed'
video_gen.save()
# 构建输出数据
output = {
'videoUrl': video_url,
'status': video_gen.status
}
# 记录基本信息
logger.info("Updated video generation status and video URL:\n%s\n", json.dumps(output, indent=4))
else:
logger.warning(f"No video generation found for user_id: {user_id} and video_id: {video_id}")
except ObjectDoesNotExist:
logger.error(f"User with id {user_id} does not exist")
else:
logger.error("User ID or Video ID not provided in the webhook URL or callback data")
return HttpResponse(status=200)
except json.JSONDecodeError as e:
logger.error("Invalid JSON data: %s", str(e))
return HttpResponse(status=400) # Bad Request
except Exception as e:
logger.error("Error: %s", str(e))
return HttpResponse(status=500) # Internal Server Error
else:
return HttpResponse(status=405) # Method Not Allowed
#获取视频列表
@csrf_exempt
@require_http_methods(["GET"])
@login_required
def get_video_list(request):
"""
获取视频列表的函数支持分页
"""
try:
user = request.user
# 检查是否有用户ID参数
user_id = request.GET.get('user_id', None)
# 获取分页参数
page = request.GET.get('page', 1)
page_size = request.GET.get('page_size', 10)
# 如果有user_id参数并且当前用户是管理员则获取该用户的视频列表
if user_id and user.is_staff:
try:
specific_user = User.objects.get(id=user_id)
video_list = VideoGeneration.objects.filter(user=specific_user).order_by('-created_at')
except ObjectDoesNotExist:
return JsonResponse({'code': 404, 'message': '用户不存在'}, status=404)
# 如果没有user_id参数或者当前用户不是管理员则获取当前用户的视频列表
elif not user.is_staff:
video_list = VideoGeneration.objects.filter(user=user).order_by('-created_at')
else:
# 管理员返回所有记录
video_list = VideoGeneration.objects.all().order_by('-created_at')
# 使用Paginator进行分页
paginator = Paginator(video_list, page_size)
try:
videos = paginator.page(page)
except PageNotAnInteger:
videos = paginator.page(1)
except EmptyPage:
videos = paginator.page(paginator.num_pages)
video_data = []
for video in videos:
video_data.append({
'id': video.id,
'text': video.text,
'voice_name': video.voice_name,
'style': video.style,
'rate': video.rate,
'media_type': video.media_type,
'ratio': video.ratio,
'audio_url': video.audio_url,
'video_url': video.video_url,
'status': video.status,
'created_at': video.created_at,
'updated_at': video.updated_at,
'pid':video.pid,
'video_id':video.video_id,
'slug': video.slug,
})
response_data = {
'code': 200,
'data': {"list":video_data,'page': videos.number,
'page_size': paginator.per_page,
'total_pages': paginator.num_pages,
'total_videos': paginator.count}
}
return JsonResponse(response_data)
except Exception as e:
logger.error(f"Error fetching video list: {str(e)}")
return JsonResponse({"code": 500, "message": str(e)}, status=500)
@require_http_methods(["GET"])
def get_plans(request):
"""
获取所有套餐的API接口包含code和message字段
"""
try:
# 查询所有套餐
plans = Plan.objects.all()
# 构建响应数据
plans_data = [
{ "id": plan.id,
"title": plan.title,
"description": plan.description,
"price": float(plan.price), # 将Decimal类型转换为float类型
"credits_per_month": plan.credits_per_month,
"created_at": plan.created_at,
"updated_at": plan.updated_at,
"is_promotional": plan.is_promotional,
"unlimited_exports": plan.unlimited_exports,
"smart_music_sync": plan.smart_music_sync
}
for plan in plans
]
# 返回包含code和message的JSON响应
return JsonResponse({"code": 200, "message": "获取成功", "data": plans_data}, status=200)
except Exception as e:
# 处理异常并返回错误信息
return JsonResponse({"code": 500, "message": f"服务器错误: {str(e)}"}, status=500)
@csrf_exempt
@require_http_methods(["GET"])
def get_orders(request):
"""
获取订单列表支持分页状态筛选和排序
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
status = request.GET.get('status', None)
page = request.GET.get('page', 1)
page_size = request.GET.get('page_size', 10)
# 过滤订单状态
if status:
orders = Order.objects.filter(status=status).order_by('-created_at')
else:
orders = Order.objects.all().order_by('-created_at')
# 分页
paginator = Paginator(orders, page_size)
try:
orders_page = paginator.page(page)
except PageNotAnInteger:
orders_page = paginator.page(1)
except EmptyPage:
orders_page = paginator.page(paginator.num_pages)
# 构建响应数据
order_list = [
{
'order_id': order.order_id,
'user_id': order.user.id,
'username': order.user.username,
'amount': float(order.amount),
'payment_method': order.payment_method,
'status': order.status,
'created_at': order.created_at.strftime('%Y-%m-%d %H:%M:%S'),
'updated_at': order.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
}
for order in orders_page
]
return JsonResponse({
'code': 200,
'message': '获取成功',
'data': order_list,
'page': orders_page.number,
'total_pages': paginator.num_pages,
'total_orders': paginator.count,
})
except Exception as e:
return JsonResponse({'code': 500, 'message': str(e)})
@csrf_exempt
@require_http_methods(["DELETE"])
def delete_order(request, order_id):
"""
删除指定订单
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
order = Order.objects.get(order_id=order_id)
order.delete()
return JsonResponse({'code': 200, 'message': '订单删除成功'})
except Order.DoesNotExist:
return JsonResponse({'code': 404, 'message': '订单不存在'})
except Exception as e:
return JsonResponse({'code': 500, 'message': str(e)})

8
WebAdmin/apps.py Executable file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
class WebadminConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "WebAdmin"
def ready(self):
import WebAdmin.signals # 确保在应用启动时加载信号处理程序

1236
WebAdmin/audio.py Executable file

File diff suppressed because it is too large Load Diff

239
WebAdmin/base.py Executable file
View File

@ -0,0 +1,239 @@
import os
import uuid
import json
import requests
from django.conf import settings
from django.http import JsonResponse
from django.core.exceptions import ObjectDoesNotExist
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from azure.storage.blob import BlobServiceClient, BlobClient
from .models import VideoGeneration, User, TransactionHistory
from .utils import get_logger
# 配置日志记录
log_file = 'API日志.log'
logger = get_logger('API日志', log_file, when='midnight', backup_count=7)
# API 配置
API_KEY = 'be13f9fbf56446bdbaa16e986bb0771b'
REGION = 'eastus'
ENDPOINT = f'https://{REGION}.tts.speech.microsoft.com/cognitiveservices/v1'
@csrf_exempt
def synthesize_speech(text, voice_name, style='general', rate=0):
"""
生成语音文件并返回音频URL
"""
try:
# 提取语言信息
language = voice_name.split('-')[0] + '-' + voice_name.split('-')[1]
rate_str = f"{rate}%"
output_format = 'audio-16khz-32kbitrate-mono-mp3'
# 设置请求头
headers = {
'Ocp-Apim-Subscription-Key': API_KEY,
'Content-Type': 'application/ssml+xml',
'X-Microsoft-OutputFormat': output_format,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0;'
}
# 构建SSML请求体
ssml = f"""
<speak version='1.0' xmlns:mstts='http://www.w3.org/2001/mstts' xml:lang='{language}'>
<voice xml:lang='{language}' xml:gender='Female' name='{voice_name}'>
<mstts:express-as style='{style}'>
<prosody rate='{rate_str}'>{text}</prosody>
</mstts:express-as>
</voice>
</speak>
"""
# 发送请求到 Microsoft 语音合成API
response = requests.post(ENDPOINT, headers=headers, data=ssml)
if response.status_code == 200:
# 保存音频文件
audio_filename = f"{uuid.uuid4()}.mp3"
audio_path = os.path.join(settings.MEDIA_ROOT, audio_filename)
with open(audio_path, 'wb') as audio_file:
audio_file.write(response.content)
audio_url = f"{settings.DOMAIN}{settings.MEDIA_URL}{audio_filename}"
return {"code": 200, "audio_url": audio_url}
else:
return {"code": response.status_code, "message": "无法合成语音"}
except Exception as e:
return {"code": 500, "message": str(e)}
# 上传图片的接口
@csrf_exempt
@require_http_methods(["POST"])
def upload_avatar(request):
"""
上传图片的接口返回图片URL
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
if 'avatar' not in request.FILES:
return JsonResponse({'code': 400, 'message': '没有上传图片'})
avatar_file = request.FILES['avatar']
# 保存图片到指定目录 avatars/
avatar_filename = f"{uuid.uuid4()}.{avatar_file.name.split('.')[-1]}"
avatar_path = os.path.join(settings.MEDIA_ROOT, 'avatars', avatar_filename)
with open(avatar_path, 'wb') as f:
for chunk in avatar_file.chunks():
f.write(chunk)
avatar_url = f"{settings.DOMAIN}{settings.MEDIA_URL}avatars/{avatar_filename}"
return JsonResponse({'code': 200, "message": "上传成功",'data':{'avatar_url': avatar_url} })
except Exception as e:
logger.error(f"上传头像时出错: {str(e)}")
return JsonResponse({"code": 500, "message": str(e)})
@csrf_exempt
@require_http_methods(["POST"])
def upload_audio(request):
"""
上传音乐或视频文件的接口返回文件URL支持 .m4v, .mp4, .mp3, .wav 格式
"""
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
if 'audio' not in request.FILES:
return JsonResponse({'code': 400, 'message': '没有上传音频或视频文件'})
audio_file = request.FILES['audio']
file_extension = audio_file.name.split('.')[-1].lower()
# 允许的文件格式
allowed_extensions = ['m4v', 'mp4', 'mp3', 'wav']
# 检查文件格式是否被允许
if file_extension not in allowed_extensions:
return JsonResponse({'code': 400, 'message': '只支持 .m4v, .mp4, .mp3, .wav 格式的文件'})
# 生成唯一的文件名
audio_filename = f"{uuid.uuid4()}.{file_extension}"
# 创建 Azure BlobServiceClient 实例
blob_service_client = BlobServiceClient.from_connection_string(settings.AZURE_CONNECTION_STRING)
container_client = blob_service_client.get_container_client(settings.AZURE_CONTAINER_NAME)
# 上传文件到 Azure Blob Storage
blob_client = container_client.get_blob_client(audio_filename)
# 使用 File 的 read 方法直接上传文件
blob_client.upload_blob(audio_file, overwrite=True)
# 获取文件的 URL
audio_url = blob_client.url
return JsonResponse({'code': 200, 'message': '上传成功', 'data': {'audio_url': audio_url}})
except Exception as e:
logger.error(f"上传文件时出错: {str(e)}")
return JsonResponse({"code": 500, "message": str(e)})
import re
# 功能费用配置
FEATURE_COSTS = {
"text-to-video": {"base_cost": 10}, # 按字符收费
"img-to-video": {"base_cost": 10}, # 固定收费
"create-tiktok-video": {"base_cost": 20, "additional_cost_per_1000_chars": 10}, # 按字符收费
"create-music-video": {"base_cost": 20}, # 固定收费
"create-avatar-video": {"base_cost": 30, "additional_cost_per_1000_chars": 10}, # 按字符收费
}
# 计算文本生成类功能的所需积分
def calculate_points_for_text(text, base_cost, additional_cost_per_1000_chars=0):
"""
根据文本内容和定价策略计算所需积分
"""
# 计算中文字符和非中文字符数量
chinese_chars = re.findall(r'[\u4e00-\u9fff]', text)
non_chinese_chars = re.findall(r'[^\u4e00-\u9fff]', text)
chinese_char_count = len(chinese_chars)
non_chinese_char_count = len(non_chinese_chars)
# 中文字符算2个普通字符
total_char_count = chinese_char_count * 2 + non_chinese_char_count
# 计算积分
total_points = base_cost + (total_char_count // 1000) * additional_cost_per_1000_chars
return total_points
# 扣费逻辑
def deduct_points(user, feature, text=None):
"""
根据不同功能类型扣费返回 True JsonResponse 提示积分不足
:param user: 用户对象
:param feature: 功能类型 (: text_to_video, img_to_video)
:param text: 可选的文本参数
:return: True 或者返回 JsonResponse
"""
# 检查功能类型是否在费用配置中
if feature not in FEATURE_COSTS:
return JsonResponse({'code': 400, 'message': f'无效的功能类型: {feature}', 'data': {}})
# 获取费用配置
feature_config = FEATURE_COSTS[feature]
base_cost = feature_config.get("base_cost", 0)
additional_cost_per_1000_chars = feature_config.get("additional_cost_per_1000_chars", 0)
# 如果有文本传入,计算所需积分和字符数量
char_count = 0
if text:
# 计算中文字符和非中文字符数量
chinese_chars = re.findall(r'[\u4e00-\u9fff]', text)
non_chinese_chars = re.findall(r'[^\u4e00-\u9fff]', text)
chinese_char_count = len(chinese_chars)
non_chinese_char_count = len(non_chinese_chars)
# 中文字符算2个普通字符
total_char_count = chinese_char_count * 2 + non_chinese_char_count
char_count = total_char_count
# 计算积分
required_points = calculate_points_for_text(text, base_cost, additional_cost_per_1000_chars)
else:
# 固定收费的功能
required_points = base_cost
# 检查用户积分是否足够
if user.points < required_points:
return JsonResponse({'code': 402, 'message': '积分不足', 'data': {}})
# 扣费前记录用户积分
previous_points_balance = user.points
# 扣除积分并保存
user.points -= required_points
user.save()
# 记录消费明细到数据库
TransactionHistory.objects.create(
user=user,
feature=feature,
points_spent=required_points,
char_count=char_count if text else None, # 记录字符数量
previous_points_balance=previous_points_balance,
new_points_balance=user.points,
description=f'扣费 {required_points} 积分用于 {feature}',
success=True
)
# 返回成功扣费
return True

71
WebAdmin/create_avatar_video.py Executable file
View File

@ -0,0 +1,71 @@
from .base import logger, synthesize_speech
import requests
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
def create_avatar_video_payload(audio_url,generationPreset, media_type, text, ratio, voice_name, user_id, slug,disableCaptions, caption_preset, caption_position, selected_avatar, video_id):
"""
生成视频的函数用于 create-avatar-video
"""
try:
video_headers = {
'Content-Type': 'application/json',
'key': VIDEO_API_KEY
}
hasToGenerateVideos = media_type == "movingImage"
# 配置 webhook URL包含 video_id
webhook_url = f"{WEBHOOK_URL}?userid={user_id}&videoid={video_id}"
# 构建请求体
payload = {
"frameRate": 60,
"resolution": "1080p",
"frameDurationMultiplier": 18,
"webhook": webhook_url,
"creationParams": {
"mediaType": media_type,
"origin": "/create",
"inputText": text,
"flowType": "text-to-video",
"slug": slug,
"hasToGenerateVoice": True,
"hasToTranscript": False,
"hasToSearchMedia": True,
"hasAvatar": True,
"hasWebsiteRecorder": False,
"hasTextSmallAtBottom": False,
"captionPresetName": caption_preset,
"captionPositionName": caption_position,
"disableAudio": True,
"useQualityTranscript":False,
"ratio": ratio,
"selectedAudio": "Bladerunner 2049",
"selectedVoice": voice_name,
"selectedAvatar": selected_avatar,
"selectedAvatarType": "image/png",
"sourceType": "contentScraping",
"disableCaptions": disableCaptions,
"selectedStoryStyle": {
"value": "custom",
"label": "Custom"
},
"hasToGenerateVideos": hasToGenerateVideos,
"selectedRecording": audio_url,
"selectedRecordingType": "audio",
"generationPreset": generationPreset,
"audioUrl": "https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3"
}
}
logger.info(payload)
response = requests.post(VIDEO_API_URL, headers=video_headers, json=payload)
if response.status_code == 200:
return {"code": 200, "data": response.json()}
else:
return {"code": response.status_code, "message": response.text}
except Exception as e:
return {"code": 500, "message": str(e)}

71
WebAdmin/create_music_video.py Executable file
View File

@ -0,0 +1,71 @@
import os
import requests
from .base import logger
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
def create_music_video_payload(audio_url,generationPreset, media_type, text, ratio, voice_name, style, rate, user_id, slug, disableCaptions,caption_preset, caption_position, video_id):
"""
构建生成视频的请求体用于 create-music-video
"""
try:
video_headers = {
'Content-Type': 'application/json',
'key': VIDEO_API_KEY
}
# 配置 webhook URL包含 video_id
webhook_url = f"{WEBHOOK_URL}?userid={user_id}&videoid={video_id}"
hasToGenerateVideos = media_type == "movingImage"
# 构建请求体
payload = {
"frameRate": 60,
"resolution": "1080p",
"frameDurationMultiplier": 18,
"webhook": webhook_url,
"creationParams": {
"mediaType": media_type,
"hasSoundWave": False,
"origin": "/create",
"flowType": "file-to-video",
"slug": slug,
"hasToGenerateVoice": False,
"hasToTranscript": False,
"hasToSearchMedia": True,
"hasAvatar": False,
"hasWebsiteRecorder": False,
"hasTextSmallAtBottom": False,
"ratio": ratio,
"selectedAudio": "Stomp",
"disableCaptions": disableCaptions,
"selectedVoice": voice_name,
"captionPresetName": caption_preset,
"captionPositionName": caption_position,
"sourceType": "contentScraping",
"selectedStoryStyle": {
"value": "custom",
"label": "Custom"
},
"generationPreset": generationPreset,
"selectedRecording": audio_url,
"selectedRecordingType": "audio",
"useQualityTranscript": False,
"hasEnhancedGeneration": False,
"disableAudio": False,
"hasToGenerateVideos": hasToGenerateVideos,
"audioUrl": "https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3"
}
}
logger.info(payload)
response = requests.post(VIDEO_API_URL, headers=video_headers, json=payload)
if response.status_code == 200:
return {"code": 200, "data": response.json()}
else:
return {"code": response.status_code, "message": response.text}
except Exception as e:
return {"code": 500, "message": str(e)}

332
WebAdmin/create_text_img_video.py Executable file
View File

@ -0,0 +1,332 @@
import json
import time
import requests
from django.http import JsonResponse
from .models import VideoGeneration
from django.views.decorators.csrf import csrf_exempt
from .base import logger,deduct_points
def generate_video_id(user_id):
"""
根据用户ID和当前时间戳生成唯一的视频ID
"""
timestamp = int(time.time())
video_id = f"{timestamp}_{user_id}"
print(f"生成的视频ID: {video_id}")
return video_id
def get_dimensions(model, width=None, height=None):
"""
根据模型返回相应的宽高分辨率:
gen3: 最大1280x768
gen2: 可传入前端指定的宽高默认最大1920x1080
"""
if model == 'gen3':
return 1280, 768
elif model == 'gen2':
# 如果前端提供了 width 和 height则使用传入的值否则使用默认值
if width and height:
return width, height
return 1920, 1080
else:
raise ValueError(f"不支持的模型: {model}")
def get_headers():
"""
返回统一的请求头
"""
return {
"accept": "application/json",
"content-type": "application/json",
"Authorization": "12e76710fad2047db8c0cc6b25987e2a2" # 替换为你的真实授权密钥
}
@csrf_exempt
def text_to_video(request):
"""
文本生成视频接口
"""
if request.method == "POST":
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
user = request.user
data = json.loads(request.body)
# 获取参数
text_prompt = data.get('text_prompt')
style = data.get('style', '') # 选择的风格,默认是 'general'
model = data.get('model', 'gen3') # 选择模型,默认是 'gen3'
time_duration = int(data.get('time', 5)) # 视频时长
motion = int(data.get('motion', 5)) # 视频丰富度参数
width = data.get('width') # 由前端传入的宽度仅限gen2
height = data.get('height') # 由前端传入的高度仅限gen2
print(f"生成模型: {model}, 时长: {time_duration}, 文本: {text_prompt}, 宽高: {width}x{height}, 画面丰富度: {motion}")
result = deduct_points(user, "text-to-video")
if result is not True:
return result # 返回积分不足的响应
# 验证模型和时长限制
if model == 'gen3' and time_duration not in [5, 10]:
return JsonResponse({'code': 400, 'message': 'gen3 模型只支持 5s 或 10s 的时长'})
if model == 'gen2' and time_duration != 4:
return JsonResponse({'code': 400, 'message': 'gen2 模型只支持 4s 的时长'})
# 生成视频ID
video_id = generate_video_id(user.id)
# 根据模型获取宽高
width, height = get_dimensions(model, width, height)
print(f"生成的视频分辨率: {width}x{height}")
# 准备POST请求数据
payload = {
"text_prompt": f"{text_prompt}. Style: {style}",
"model": model,
"width": width,
"height": height,
"motion": motion, # 视频画面丰富度
"seed": 0,
"upscale": False,
"interpolate": False,
"callback_url": "https://www.typeframes.ai/api/callback/",
"time": time_duration # 视频时长
}
print(f'text_to_video 请求体: {payload}')
# 发送请求到API
response = requests.post('https://api.aivideoapi.com/runway/generate/text', json=payload, headers=get_headers())
print(f"API响应状态码: {response.status_code}")
print(f"API响应内容: {response.text}")
# 解析响应数据
if response.status_code == 200:
res_data = response.json()
uuid = res_data.get('uuid')
# 保存生成记录到数据库
video_generation = VideoGeneration.objects.create(
video_id=video_id,
user=user,
text=text_prompt,
status='in_progress',
pid=uuid,
media_type=f'{model}_text_to_video',
slug=f'text-to-video',
ratio=f"{width}:{height}"
)
video_generation.save()
# 返回成功响应
return JsonResponse({
'code': 200,
'message': '请求成功,视频正在生成中',
'data': {
'video_id': video_generation.video_id,
'status': video_generation.status,
'pid': uuid
}
})
else:
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
except Exception as e:
print(f"发生错误: {str(e)}")
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
@csrf_exempt
def image_to_video(request):
"""
图片生成视频接口图片宽高自适应
"""
if request.method == "POST":
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
user = request.user
data = json.loads(request.body)
# 获取参数
text_prompt = data.get('text_prompt')
image_url = data.get('image_url')
model = data.get('model', 'gen3') # 选择模型,默认是 'gen3'
time_duration = int(data.get('time', 5)) # 视频时长
motion = int(data.get('motion', 5)) # 视频丰富度
print(f"生成模型: {model}, 图片地址: {image_url}, 时长: {time_duration}, 画面丰富度: {motion}")
result = deduct_points(user, "img-to-video")
if result is not True:
return result # 返回积分不足的响应
# 验证模型和时长限制
if model == 'gen3' and time_duration not in [5, 10]:
return JsonResponse({'code': 400, 'message': 'gen3 模型只支持 5s 或 10s 的时长'})
if model == 'gen2' and time_duration != 4:
return JsonResponse({'code': 400, 'message': 'gen2 模型只支持 4s 的时长'})
# 生成视频ID
video_id = generate_video_id(user.id)
# 准备POST请求数据宽高不需要传入图片自适应
payload = {
"text_prompt": text_prompt,
"model": model,
"img_prompt": image_url,
"image_as_end_frame": False, # 图片不作为最后一帧
"motion": motion, # 视频丰富度
"seed": 0,
"upscale": False,
"interpolate": False,
"callback_url": "https://www.typeframes.ai/api/callback/",
"time": time_duration # 视频时长
}
print(f'img_to_video 请求体: {payload}')
# 请求头
headers = get_headers()
# 发送请求到API
response = requests.post('https://api.aivideoapi.com/runway/generate/imageDescription', json=payload, headers=headers)
print(f"API响应状态码: {response.status_code}")
print(f"API响应内容: {response.text}")
# 解析响应数据
if response.status_code == 200:
res_data = response.json()
uuid = res_data.get('uuid')
# 保存生成记录到数据库
video_generation = VideoGeneration.objects.create(
video_id=video_id,
user=user,
text=text_prompt,
status='in_progress',
pid=uuid,
media_type=f'{model}_img_to_video',
slug=f'img-to-video',
ratio="auto" # 图片自适应宽高
)
video_generation.save()
# 返回成功响应
return JsonResponse({
'code': 200,
'message': '请求成功,视频正在生成中',
'data': {
'video_id': video_generation.video_id,
'status': video_generation.status,
'pid': uuid
}
})
else:
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
except Exception as e:
print(f"发生错误: {str(e)}")
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
@csrf_exempt
def extend_video(request):
"""
延长视频的函数发送请求到 /runway/extend
"""
if request.method == "POST":
try:
if not request.user.is_authenticated:
return JsonResponse({'code': 401, 'message': '用户未登录'})
user = request.user
data = json.loads(request.body)
# 获取参数
pid = data.get('pid')
motion = int(data.get('motion', 5)) # 视频丰富度
# 获取当前视频生成记录
video_generation = VideoGeneration.objects.get(pid=pid)
# 通过 media_type 判断模型是 gen3 还是 gen2
if 'gen3' in video_generation.media_type:
model = 'gen3'
# 验证 gen3 的延长逻辑
if video_generation.extension_count >= 1 or video_generation.time_duration >= 10:
return JsonResponse({'code': 400, 'message': 'gen3 最大时长为 10s无法继续延长'})
time_extension = 5 # 每次延长 5 秒
elif 'gen2' in video_generation.media_type:
model = 'gen2'
# 验证 gen2 的延长逻辑
if video_generation.extension_count >= 2 or video_generation.time_duration >= 12:
return JsonResponse({'code': 400, 'message': 'gen2 最大时长为 12s无法继续延长'})
time_extension = 4 # 每次延长 4 秒
else:
return JsonResponse({'code': 400, 'message': '未知的模型类型'})
result = deduct_points(user, video_generation.slug)
if result is not True:
return result # 返回积分不足的响应
# 生成新的视频ID
new_video_id = generate_video_id(user.id)
# 默认请求体
payload = {
"uuid": pid, # 视频的 UUID
"motion": motion, # 视频丰富度
"seed": 0, # 默认 seed 为 0
"upscale": False, # 默认开启 upscale
"interpolate": False, # 默认开启 interpolate
"callback_url": "https://www.typeframes.ai/api/callback/"
}
print(f"extend_video 请求体: {payload}")
headers = get_headers()
# 发送 POST 请求
response = requests.post('https://api.aivideoapi.com/runway/extend', json=payload, headers=headers)
print(f"API响应状态码: {response.status_code}")
print(f"API响应内容: {response.text}")
# 解析响应内容
if response.status_code == 200:
res_data = response.json()
new_pid = res_data.get('uuid')
# 创建新的延长视频记录,复制上一条视频的参数
new_video_generation = VideoGeneration.objects.create(
video_id=new_video_id,
user=user,
text=video_generation.text,
voice_name=video_generation.voice_name,
style=video_generation.style,
rate=video_generation.rate,
media_type=video_generation.media_type,
ratio=video_generation.ratio,
status='in_progress',
pid=new_pid,
time_duration=video_generation.time_duration + time_extension, # 增加对应模型的时长
extension_count=video_generation.extension_count + 1, # 增加延长次数
slug=video_generation.slug # slug 使用中划线
)
print(f"延长视频成功: {res_data}")
return JsonResponse({'code': 200, 'message': '视频延长请求成功', 'data': {'new_pid': new_pid}})
else:
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
except Exception as e:
print(f"发生错误: {str(e)}")
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})

142
WebAdmin/create_text_video.py Executable file
View File

@ -0,0 +1,142 @@
import requests
from django.http import JsonResponse
from .models import VideoGeneration
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt, name='dispatch')
def text_to_video(request):
"""
接口根据文本描述生成视频
"""
if request.method == "POST":
try:
user = request.user
data = request.POST
# 生成视频ID
video_id = generate_video_id(user.id)
# 获取参数
text_prompt = data.get('text_prompt')
model = data.get('model', 'gen3')
width = int(data.get('width', 640))
height = int(data.get('height', 480))
motion = int(data.get('motion', 5))
seed = int(data.get('seed', 0))
upscale = data.get('upscale', 'false') == 'true'
interpolate = data.get('interpolate', 'false') == 'true'
callback_url = data.get('callback_url', '')
time_duration = int(data.get('time', 5))
# 准备POST请求数据
payload = {
"text_prompt": text_prompt,
"model": model,
"width": width,
"height": height,
"motion": motion,
"seed": seed,
"upscale": upscale,
"interpolate": interpolate,
"callback_url": callback_url,
"time": time_duration
}
# 发送请求到API
response = requests.post('https://api.aivideoapi.com/runway/generate/text', json=payload)
# 解析响应数据
if response.status_code == 200:
res_data = response.json()
uuid = res_data.get('uuid')
# 保存生成记录到数据库
video_generation = VideoGeneration.objects.create(
video_id=video_id,
user=user,
text=text_prompt,
status='in_progress',
pid=uuid,
ratio=f"{width}:{height}"
)
video_generation.save()
return JsonResponse({'code': 200, 'message': '请求成功', 'data': {'pid': uuid}})
else:
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
except Exception as e:
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})
@method_decorator(csrf_exempt, name='dispatch')
def image_to_video(request):
"""
接口根据图片生成视频
"""
if request.method == "POST":
try:
user = request.user
data = request.POST
# 生成视频ID
video_id = generate_video_id(user.id)
# 获取参数
text_prompt = data.get('text_prompt')
model = data.get('model', 'gen3')
img_prompt = data.get('img_prompt')
image_as_end_frame = data.get('image_as_end_frame', 'false') == 'true'
motion = int(data.get('motion', 5))
seed = int(data.get('seed', 0))
upscale = data.get('upscale', 'false') == 'true'
interpolate = data.get('interpolate', 'false') == 'true'
callback_url = data.get('callback_url', '')
time_duration = int(data.get('time', 5))
# 准备POST请求数据
payload = {
"text_prompt": text_prompt,
"model": model,
"img_prompt": img_prompt,
"image_as_end_frame": image_as_end_frame,
"motion": motion,
"seed": seed,
"upscale": upscale,
"interpolate": interpolate,
"callback_url": callback_url,
"time": time_duration
}
# 发送请求到API
response = requests.post('https://api.aivideoapi.com/runway/generate/imageDescription', json=payload)
# 解析响应数据
if response.status_code == 200:
res_data = response.json()
uuid = res_data.get('uuid')
# 保存生成记录到数据库
video_generation = VideoGeneration.objects.create(
video_id=video_id,
user=user,
text=text_prompt,
status='in_progress',
pid=uuid,
ratio="auto"
)
video_generation.save()
return JsonResponse({'code': 200, 'message': '请求成功', 'data': {'pid': uuid}})
else:
return JsonResponse({'code': response.status_code, 'message': 'API请求失败', 'data': {}})
except Exception as e:
return JsonResponse({'code': 500, 'message': f'内部错误: {str(e)}', 'data': {}})
return JsonResponse({'code': 405, 'message': '请求方法错误', 'data': {}})

70
WebAdmin/create_tiktok_video.py Executable file
View File

@ -0,0 +1,70 @@
from .base import logger, synthesize_speech
import requests
VIDEO_API_KEY = 'c4c0f516-33c0-4f5c-9d29-3b1d4e6a93c3'
VIDEO_API_URL = 'https://www.typeframes.com/api/public/v2/render'
WEBHOOK_URL = 'https://www.typeframes.cc/api/webhook/'
def create_video(audio_url,generationPreset, media_type, text, ratio, voice_name, user_id, slug,disableCaptions, caption_preset, caption_position, video_id):
"""
生成视频的函数用于 create-tiktok-video
"""
try:
video_headers = {
'Content-Type': 'application/json',
'key': VIDEO_API_KEY
}
# 判断是否需要生成视频
hasToGenerateVideos = media_type == "movingImage"
# 配置 webhook URL包含 video_id
webhook_url = f"{WEBHOOK_URL}?userid={user_id}&videoid={video_id}"
# 构建请求体
payload = {
"frameRate": 60,
"resolution": "1080p",
"frameDurationMultiplier": 5,
"webhook": webhook_url,
"creationParams": {
"mediaType": media_type,
"origin": "/create",
"inputText": text,
"flowType": "text-to-video",
"slug": slug,
"disableCaptions": disableCaptions,
"hasToGenerateVoice": False,
"hasToTranscript": False,
"hasToSearchMedia": True,
"useQualityTranscript":False,
"hasAvatar": False,
"hasWebsiteRecorder": False,
"hasTextSmallAtBottom": False,
"captionPresetName": caption_preset,
"captionPositionName": caption_position,
"disableAudio": True,
"ratio": ratio,
"selectedAudio": "Bladerunner 2049",
"selectedVoice": voice_name,
"sourceType": "contentScraping",
"selectedStoryStyle": {
"value": "custom",
"label": "Custom"
},
"hasToGenerateVideos": hasToGenerateVideos,
"selectedRecording": audio_url,
"selectedRecordingType": "audio",
"generationPreset": generationPreset,
"audioUrl": "https://cdn.tfrv.xyz/audio/_bladerunner-2049.mp3"
}
}
logger.info(payload)
response = requests.post(VIDEO_API_URL, headers=video_headers, json=payload)
if response.status_code == 200:
return {"code": 200, "data": response.json()}
else:
return {"code": response.status_code, "message": response.text}
except Exception as e:
return {"code": 500, "message": str(e)}

595
WebAdmin/data.json Executable file
View File

@ -0,0 +1,595 @@
[
{
"国家": "中国",
"语言参数": "zh-CN",
"语言": "中文(普通话,简体)",
"讲话人": [
{
"讲话人中文名": "晓晓",
"讲话人英文参数": "zh-CN-XiaoxiaoNeural",
"风格列表": [],
},
{
"讲话人中文名": "云希",
"讲话人英文参数": "zh-CN-YunxiNeural",
"风格列表": [],
},
{
"讲话人中文名": "云健",
"讲话人英文参数": "zh-CN-YunjianNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓艺",
"讲话人英文参数": "zh-CN-XiaoyiNeural",
"风格列表": [],
},
{
"讲话人中文名": "云阳",
"讲话人英文参数": "zh-CN-YunyangNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓晨",
"讲话人英文参数": "zh-CN-XiaochenNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓涵",
"讲话人英文参数": "zh-CN-XiaohanNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓萌",
"讲话人英文参数": "zh-CN-XiaomengNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓陌",
"讲话人英文参数": "zh-CN-XiaomoNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓秋",
"讲话人英文参数": "zh-CN-XiaoqiuNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓蕊",
"讲话人英文参数": "zh-CN-XiaoruiNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓双",
"讲话人英文参数": "zh-CN-XiaoshuangNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓妍",
"讲话人英文参数": "zh-CN-XiaoyanNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓优",
"讲话人英文参数": "zh-CN-XiaoyouNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓真",
"讲话人英文参数": "zh-CN-XiaozhenNeural",
"风格列表": [],
},
{
"讲话人中文名": "云峰",
"讲话人英文参数": "zh-CN-YunfengNeural",
"风格列表": [],
},
{
"讲话人中文名": "云浩",
"讲话人英文参数": "zh-CN-YunhaoNeural",
"风格列表": [],
},
{
"讲话人中文名": "云霞",
"讲话人英文参数": "zh-CN-YunxiaNeural",
"风格列表": [],
},
{
"讲话人中文名": "云烨",
"讲话人英文参数": "zh-CN-YunyeNeural",
"风格列表": [],
},
{
"讲话人中文名": "云泽",
"讲话人英文参数": "zh-CN-YunzeNeural",
"风格列表": [],
}
]
},
{
"国家": "香港特别行政区",
"语言参数": "zh-HK",
"语言": "中文(粤语,繁体)",
"讲话人": [
{
"讲话人中文名": "晓文",
"讲话人英文参数": "zh-HK-HiuMaanNeural",
"风格列表": [],
},
{
"讲话人中文名": "云龙",
"讲话人英文参数": "zh-HK-WanLungNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓佳",
"讲话人英文参数": "zh-HK-HiuGaaiNeural",
"风格列表": [],
}
]
},
{
"国家": "台湾",
"语言参数": "zh-TW",
"语言": "中文(台湾普通话,繁体)",
"讲话人": [
{
"讲话人中文名": "晓晨",
"讲话人英文参数": "zh-TW-HsiaoChenNeural",
"风格列表": [],
},
{
"讲话人中文名": "云哲",
"讲话人英文参数": "zh-TW-YunJheNeural",
"风格列表": [],
},
{
"讲话人中文名": "晓语",
"讲话人英文参数": "zh-TW-HsiaoYuNeural",
"风格列表": [],
}
]
},
{
"国家": "美国",
"语言参数": "en-US",
"语言": "英语(美国)",
"讲话人": [
{
"讲话人中文名": "Ava",
"讲话人英文参数": "en-US-AvaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Andrew",
"讲话人英文参数": "en-US-AndrewNeural",
"风格列表": [],
},
{
"讲话人中文名": "Emma",
"讲话人英文参数": "en-US-EmmaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Brian",
"讲话人英文参数": "en-US-BrianNeural",
"风格列表": [],
},
{
"讲话人中文名": "Jenny",
"讲话人英文参数": "en-US-JennyNeural",
"风格列表": [],
},
{
"讲话人中文名": "Guy",
"讲话人英文参数": "en-US-GuyNeural",
"风格列表": [],
},
{
"讲话人中文名": "Aria",
"讲话人英文参数": "en-US-AriaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Davis",
"讲话人英文参数": "en-US-DavisNeural",
"风格列表": [],
},
{
"讲话人中文名": "Jane",
"讲话人英文参数": "en-US-JaneNeural",
"风格列表": [],
},
{
"讲话人中文名": "Jason",
"讲话人英文参数": "en-US-JasonNeural",
"风格列表": [],
},
{
"讲话人中文名": "Sara",
"讲话人英文参数": "en-US-SaraNeural",
"风格列表": [],
},
{
"讲话人中文名": "Tony",
"讲话人英文参数": "en-US-TonyNeural",
"风格列表": [],
},
{
"讲话人中文名": "Nancy",
"讲话人英文参数": "en-US-NancyNeural",
"风格列表": [],
},
{
"讲话人中文名": "Amber",
"讲话人英文参数": "en-US-AmberNeural",
"风格列表": [],
},
{
"讲话人中文名": "Ana",
"讲话人英文参数": "en-US-AnaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Ashley",
"讲话人英文参数": "en-US-AshleyNeural",
"风格列表": [],
},
{
"讲话人中文名": "Brandon",
"讲话人英文参数": "en-US-BrandonNeural",
"风格列表": [],
},
{
"讲话人中文名": "Christopher",
"讲话人英文参数": "en-US-ChristopherNeural",
"风格列表": [],
},
{
"讲话人中文名": "Cora",
"讲话人英文参数": "en-US-CoraNeural",
"风格列表": [],
},
{
"讲话人中文名": "Elizabeth",
"讲话人英文参数": "en-US-ElizabethNeural",
"风格列表": [],
},
{
"讲话人中文名": "Eric",
"讲话人英文参数": "en-US-EricNeural",
"风格列表": [],
},
{
"讲话人中文名": "Jacob",
"讲话人英文参数": "en-US-JacobNeural",
"风格列表": [],
},
{
"讲话人中文名": "Jenny",
"讲话人英文参数": "en-US-JennyMultilingualNeural3",
"风格列表": [],
},
{
"讲话人中文名": "Michelle",
"讲话人英文参数": "en-US-MichelleNeural",
"风格列表": [],
},
{
"讲话人中文名": "Monica",
"讲话人英文参数": "en-US-MonicaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Roger",
"讲话人英文参数": "en-US-RogerNeural",
"风格列表": [],
},
{
"讲话人中文名": "Ryan",
"讲话人英文参数": "en-US-RyanMultilingualNeural3",
"风格列表": [],
},
{
"讲话人中文名": "Steffan",
"讲话人英文参数": "en-US-SteffanNeural",
"风格列表": [],
},
{
"讲话人中文名": "AIGenerate1",
"讲话人英文参数": "en-US-AIGenerate1Neural1",
"风格列表": [],
},
{
"讲话人中文名": "AIGenerate2",
"讲话人英文参数": "en-US-AIGenerate2Neural1",
"风格列表": [],
},
{
"讲话人中文名": "Andrew",
"讲话人英文参数": "en-US-AndrewMultilingualNeural3",
"风格列表": [],
},
{
"讲话人中文名": "Ava",
"讲话人英文参数": "en-US-AvaMultilingualNeural3",
"风格列表": [],
},
{
"讲话人中文名": "Blue",
"讲话人英文参数": "en-US-BlueNeural1",
"风格列表": [],
},
{
"讲话人中文名": "Kai",
"讲话人英文参数": "en-US-KaiNeural1",
"风格列表": [],
},
{
"讲话人中文名": "Luna",
"讲话人英文参数": "en-US-LunaNeural1",
"风格列表": [],
},
{
"讲话人中文名": "Brian",
"讲话人英文参数": "en-US-BrianMultilingualNeural3",
"风格列表": [],
},
{
"讲话人中文名": "Emma",
"讲话人英文参数": "en-US-EmmaMultilingualNeural3",
"风格列表": [],
},
{
"讲话人中文名": "Alloy",
"讲话人英文参数": "en-US-AlloyMultilingualNeural4",
"风格列表": [],
},
{
"讲话人中文名": "Echo",
"讲话人英文参数": "en-US-EchoMultilingualNeural4",
"风格列表": [],
},
{
"讲话人中文名": "Fable",
"讲话人英文参数": "en-US-FableMultilingualNeural4",
"风格列表": [],
},
{
"讲话人中文名": "Onyx",
"讲话人英文参数": "en-US-OnyxMultilingualNeural4",
"风格列表": [],
},
{
"讲话人中文名": "Nova",
"讲话人英文参数": "en-US-NovaMultilingualNeural4",
"风格列表": [],
},
{
"讲话人中文名": "Shimmer",
"讲话人英文参数": "en-US-ShimmerMultilingualNeural4",
"风格列表": [],
},
{
"讲话人中文名": "Alloy",
"讲话人英文参数": "en-US-AlloyMultilingualNeuralHD4",
"风格列表": [],
},
{
"讲话人中文名": "Echo",
"讲话人英文参数": "en-US-EchoMultilingualNeuralHD4",
"风格列表": [],
},
{
"讲话人中文名": "Fable",
"讲话人英文参数": "en-US-FableMultilingualNeuralHD4",
"风格列表": [],
},
{
"讲话人中文名": "Onyx",
"讲话人英文参数": "en-US-OnyxMultilingualNeuralHD4",
"风格列表": [],
},
{
"讲话人中文名": "Nova",
"讲话人英文参数": "en-US-NovaMultilingualNeuralHD4",
"风格列表": [],
},
{
"讲话人中文名": "Shimmer",
"讲话人英文参数": "en-US-ShimmerMultilingualNeuralHD4",
"风格列表": [],
}
]
},
{
"国家": "西班牙",
"语言参数": "es-ES",
"语言": "西班牙语(西班牙)",
"讲话人": [
{
"讲话人中文名": "Elvira",
"讲话人英文参数": "es-ES-ElviraNeural",
"风格列表": [],
},
{
"讲话人中文名": "Alvaro",
"讲话人英文参数": "es-ES-AlvaroNeural",
"风格列表": [],
},
{
"讲话人中文名": "Abril",
"讲话人英文参数": "es-ES-AbrilNeural",
"风格列表": [],
},
{
"讲话人中文名": "Arnau",
"讲话人英文参数": "es-ES-ArnauNeural",
"风格列表": [],
},
{
"讲话人中文名": "Dario",
"讲话人英文参数": "es-ES-DarioNeural",
"风格列表": [],
},
{
"讲话人中文名": "Elias",
"讲话人英文参数": "es-ES-EliasNeural",
"风格列表": [],
},
{
"讲话人中文名": "Estrella",
"讲话人英文参数": "es-ES-EstrellaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Irene",
"讲话人英文参数": "es-ES-IreneNeural",
"风格列表": [],
},
{
"讲话人中文名": "Laia",
"讲话人英文参数": "es-ES-LaiaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Lia",
"讲话人英文参数": "es-ES-LiaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Nil",
"讲话人英文参数": "es-ES-NilNeural",
"风格列表": [],
},
{
"讲话人中文名": "Saul",
"讲话人英文参数": "es-ES-SaulNeural",
"风格列表": [],
},
{
"讲话人中文名": "Teo",
"讲话人英文参数": "es-ES-TeoNeural",
"风格列表": [],
},
{
"讲话人中文名": "Triana",
"讲话人英文参数": "es-ES-TrianaNeural",
"风格列表": [],
},
{
"讲话人中文名": "Vera",
"讲话人英文参数": "es-ES-VeraNeural",
"风格列表": [],
},
{
"讲话人中文名": "Ximena",
"讲话人英文参数": "es-ES-XimenaNeural1",
"风格列表": [],
},
{
"讲话人中文名": "Arabella",
"讲话人英文参数": "es-ES-ArabellaMultilingualNeural1,3",
"风格列表": [],
},
{
"讲话人中文名": "Isidora",
"讲话人英文参数": "es-ES-IsidoraMultilingualNeural1,3",
"风格列表": [],
}
]
}
]

0
WebAdmin/data.json.py Executable file
View File

35
WebAdmin/data_style.json Executable file
View File

@ -0,0 +1,35 @@
{
"de-DE-ConradNeural1": ["cheerful"],
"en-GB-SoniaNeural": ["cheerful", "sad"],
"en-US-AriaNeural": ["angry", "chat", "cheerful", "customerservice", "empathetic", "excited", "friendly", "hopeful", "narration-professional", "newscast-casual", "newscast-formal", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-DavisNeural": ["angry", "chat", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-GuyNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "newscast", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-JaneNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-JasonNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-JennyNeural": ["angry", "assistant", "chat", "cheerful", "customerservice", "excited", "friendly", "hopeful", "newscast", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-NancyNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-SaraNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"en-US-TonyNeural": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified", "unfriendly", "whispering"],
"es-MX-JorgeNeural": ["chat", "cheerful"],
"fr-FR-DeniseNeural": ["cheerful", "sad"],
"fr-FR-HenriNeural": ["cheerful", "sad"],
"it-IT-IsabellaNeural": ["chat", "cheerful"],
"ja-JP-NanamiNeural": ["chat", "cheerful", "customerservice"],
"pt-BR-FranciscaNeural": ["calm"],
"zh-CN-XiaohanNeural": ["affectionate", "angry", "calm", "cheerful", "disgruntled", "embarrassed", "fearful", "gentle", "sad", "serious"],
"zh-CN-XiaomengNeural": ["chat"],
"zh-CN-XiaomoNeural": ["affectionate", "angry", "calm", "cheerful", "depressed", "disgruntled", "embarrassed", "envious", "fearful", "gentle", "sad", "serious"],
"zh-CN-XiaoruiNeural": ["angry", "calm", "fearful", "sad"],
"zh-CN-XiaoshuangNeural": ["chat"],
"zh-CN-XiaoxiaoNeural": ["affectionate", "angry", "assistant", "calm", "chat", "chat-casual", "cheerful", "customerservice", "disgruntled", "fearful", "friendly", "gentle", "lyrical", "newscast", "poetry-reading", "sad", "serious", "sorry", "whisper"],
"zh-CN-XiaoyiNeural": ["affectionate", "angry", "cheerful", "disgruntled", "embarrassed", "fearful", "gentle", "sad", "serious"],
"zh-CN-XiaozhenNeural": ["angry", "cheerful", "disgruntled", "fearful", "sad", "serious"],
"zh-CN-YunfengNeural": ["angry", "cheerful", "depressed", "disgruntled", "fearful", "sad", "serious"],
"zh-CN-YunhaoNeural2": ["advertisement-upbeat"],
"zh-CN-YunjianNeural3,4": ["angry", "cheerful", "depressed", "disgruntled", "documentary-narration", "narration-relaxed", "sad", "serious", "sports-commentary", "sports-commentary-excited"],
"zh-CN-YunxiaNeural": ["angry", "calm", "cheerful", "fearful", "sad"],
"zh-CN-YunxiNeural": ["angry", "assistant", "chat", "cheerful", "depressed", "disgruntled", "embarrassed", "fearful", "narration-relaxed", "newscast", "sad", "serious"],
"zh-CN-YunyangNeural": ["customerservice", "narration-professional", "newscast-casual"],
"zh-CN-YunyeNeural": ["angry", "calm", "cheerful", "disgruntled", "embarrassed", "fearful", "sad", "serious"],
"zh-CN-YunzeNeural": ["angry", "calm", "cheerful", "depressed", "disgruntled", "documentary-narration", "fearful", "sad", "serious"]
}

76
WebAdmin/mail.py Executable file
View File

@ -0,0 +1,76 @@
import json
from datetime import timedelta
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ses.v20201002 import ses_client, models
from django.utils.crypto import get_random_string
from django.conf import settings
from datetime import datetime
from .models import EmailVerificationCode
def generate_verification_code(length=6):
return get_random_string(length=length, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
def send_email_with_template(secret_id, secret_key, from_email, to_email, subject, template_id, code):
try:
# 实例化一个认证对象
cred = credential.Credential(secret_id, secret_key)
httpProfile = HttpProfile()
httpProfile.endpoint = "ses.tencentcloudapi.com"
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
client = ses_client.SesClient(cred, "ap-hongkong", clientProfile)
# 实例化一个请求对象
req = models.SendEmailRequest()
params = {
"FromEmailAddress":f'typeframes <{from_email}>',
"Destination": [to_email],
"Subject": subject,
"ReplyToAddresses": from_email,
"Template": {
"TemplateID": template_id,
"TemplateData": json.dumps({"name": to_email, "code": code})
}
}
req.from_json_string(json.dumps(params))
# 发送请求并返回响应
resp = client.SendEmail(req)
return resp.to_json_string()
except TencentCloudSDKException as err:
return str(err)
def send_verification_email(email):
code = generate_verification_code()
subject = '您的验证码'
template_id = 125447 # 替换为实际的模板 ID
from_email = settings.EMAIL_HOST_USER
response = send_email_with_template(settings.SECRET_ID, settings.MAIL_SECRET_KEY, from_email, email, subject, template_id, code)
response_json = json.loads(response)
if "Error" in response_json.get("Response", {}):
return {'code': 400, 'message': f'发送邮件失败: {response_json["Response"]["Error"]["Message"]}'}
EmailVerificationCode.objects.create(email=email, code=code, created_at=datetime.now())
return {'code': 200, 'message': '验证码发送成功'}
def verify_code(email, code):
try:
email_code = EmailVerificationCode.objects.filter(email=email, code=code, is_used=False).latest('created_at')
if email_code is None:
return {'code': 400, 'message': '请先获取验证码'}
if email_code.created_at < datetime.now() - timedelta(minutes=5):
return {'code': 400, 'message': '验证码已过期'}
email_code.is_used = True
email_code.save()
return {'code': 200, 'message': '验证码验证成功'}
except EmailVerificationCode.DoesNotExist:
return {'code': 400, 'message': '验证码无效或已过期'}

57
WebAdmin/mail_demo.py Executable file
View File

@ -0,0 +1,57 @@
import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ses.v20201002 import ses_client, models
SECRET_ID = "AKIDjNrBSnBXnAQXg6ythOADLJ2nOuLpFaoo"
MAIL_SECRET_KEY = "ENPZnwADD623HUm15EITdvDhAa8nQxLk"
EMAIL_HOST_USER = 'admin@typeframes.cc'
TO_EMAIL = '1726850085@qq.com'
SUBJECT = '您的验证码'
TEMPLATE_ID = 125447 # 替换为实际的模板 ID
def send_email_with_template(secret_id, secret_key, from_email, to_email, subject, template_id, code):
try:
# 实例化一个认证对象
cred = credential.Credential(secret_id, secret_key)
httpProfile = HttpProfile()
httpProfile.endpoint = "ses.tencentcloudapi.com"
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
client = ses_client.SesClient(cred, "ap-hongkong", clientProfile)
# 实例化一个请求对象
req = models.SendEmailRequest()
params = {
"FromEmailAddress": from_email,
"Destination": [to_email],
"Subject": subject,
"ReplyToAddresses": from_email,
"Template": {
"TemplateID": template_id,
"TemplateData": json.dumps({"name": from_email, "code": code})
}
}
req.from_json_string(json.dumps(params))
# 发送请求并返回响应
resp = client.SendEmail(req)
return resp.to_json_string()
except TencentCloudSDKException as err:
return str(err)
def generate_verification_code(length=6):
import random
import string
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def main():
code = generate_verification_code()
response = send_email_with_template(SECRET_ID, MAIL_SECRET_KEY, EMAIL_HOST_USER, TO_EMAIL, SUBJECT, TEMPLATE_ID, code)
print(response)
if __name__ == "__main__":
main()

57
WebAdmin/membership_rewards.py Executable file
View File

@ -0,0 +1,57 @@
import time
from django.http import JsonResponse
from django.utils.timezone import now
from django.db import connections
from datetime import datetime
# 假设您有一个积分奖励的常量
REWARD_POINTS = 100 # 例如100积分
def reward_points_based_on_membership(openid):
"""
根据openid查询用户判断是否是会员并检查会员是否过期
如果是会员且未过期则奖励积分
:param openid: 用户的openid
:return: JSON响应包含奖励积分或未满足条件的信息
"""
# 假设数据库名为 'external_db',其中有 'User' 表
with connections['external_db'].cursor() as cursor:
# 查询用户是否是会员,以及会员到期时间
query = 'SELECT is_member, member_end_time FROM user WHERE openid = %s'
cursor.execute(query, [openid])
result = cursor.fetchone()
if not result:
return JsonResponse({'code': 404, 'message': '用户未找到', 'data': {}})
is_member, member_end_time = result
# 检查是否为会员且会员未过期
if is_member == 1:
# 将数据库中的时间戳转换为当前时间戳进行比较
current_timestamp = int(time.time()) # 当前时间戳
if member_end_time > current_timestamp:
# 会员未过期,给予积分奖励
# 假设奖励积分逻辑已经实现
reward_user_points(openid, REWARD_POINTS)
return JsonResponse({'code': 200, 'message': '奖励成功', 'data': {'reward_points': REWARD_POINTS}})
else:
return JsonResponse({'code': 400, 'message': '会员已过期', 'data': {}})
else:
return JsonResponse({'code': 400, 'message': '非会员,无积分奖励', 'data': {}})
def reward_user_points(openid, points):
"""
给用户奖励积分的逻辑这里假设直接更新积分字段
:param openid: 用户的openid
:param points: 奖励的积分数量
"""
with connections['external_db'].cursor() as cursor:
# 假设 User 表有一个 'points' 字段表示用户的积分
update_query = """
UPDATE user
SET points = points + %s
WHERE openid = %s
"""
cursor.execute(update_query, [points, openid])

15
WebAdmin/middleware.py Executable file
View File

@ -0,0 +1,15 @@
from django.utils import timezone
from django.contrib.auth import logout
class CheckSessionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 检查会话是否过期
if request.session.get_expiry_age() <= 0:
logout(request) # 注销用户
request.session.flush() # 清除会话数据
# 这里可以重定向到登录页面或显示会话过期提示
response = self.get_response(request)
return response

View File

@ -0,0 +1,124 @@
# Generated by Django 5.0.7 on 2024-07-16 15:57
import django.db.models.deletion
from datetime import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="User",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("username", models.CharField(max_length=150, unique=True)),
(
"email",
models.EmailField(
blank=True, max_length=254, null=True, unique=True
),
),
(
"phone",
models.CharField(blank=True, max_length=20, null=True, unique=True),
),
(
"password_hash",
models.CharField(blank=True, max_length=128, null=True),
),
(
"google_id",
models.CharField(blank=True, max_length=50, null=True, unique=True),
),
("is_active", models.BooleanField(default=True)),
("is_member", models.BooleanField(default=False)),
("points", models.IntegerField(default=0)),
("referral_code", models.CharField(max_length=50, unique=True)),
(
"commission_rate",
models.DecimalField(decimal_places=2, default=0.0, max_digits=5),
),
("created_at", models.DateTimeField(default=datetime.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"invited_by",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="invitees",
to="WebAdmin.user",
),
),
],
),
migrations.CreateModel(
name="Referral",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"commission_amount",
models.DecimalField(decimal_places=2, max_digits=10),
),
("created_at", models.DateTimeField(default=datetime.now)),
(
"referee",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="referrals_received",
to="WebAdmin.user",
),
),
(
"referrer",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="referrals_made",
to="WebAdmin.user",
),
),
],
),
migrations.CreateModel(
name="UserSource",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("source", models.CharField(max_length=50)),
("created_at", models.DateTimeField(default=datetime.now)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="WebAdmin.user"
),
),
],
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.7 on 2024-07-16 17:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="user",
name="last_login_ip",
field=models.GenericIPAddressField(blank=True, null=True),
),
migrations.AddField(
model_name="user",
name="login_count",
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,135 @@
# Generated by Django 5.0.7 on 2024-07-16 18:04
import django.contrib.auth.models
import django.contrib.auth.validators
from datetime import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0002_user_last_login_ip_user_login_count"),
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.AlterModelOptions(
name="user",
options={"verbose_name": "user", "verbose_name_plural": "users"},
),
migrations.AlterModelManagers(
name="user",
managers=[
("objects", django.contrib.auth.models.UserManager()),
],
),
migrations.RemoveField(
model_name="user",
name="password_hash",
),
migrations.AddField(
model_name="user",
name="date_joined",
field=models.DateTimeField(
default=datetime.now, verbose_name="date joined"
),
),
migrations.AddField(
model_name="user",
name="first_name",
field=models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
migrations.AddField(
model_name="user",
name="groups",
field=models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
migrations.AddField(
model_name="user",
name="is_staff",
field=models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
migrations.AddField(
model_name="user",
name="is_superuser",
field=models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
migrations.AddField(
model_name="user",
name="last_login",
field=models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
migrations.AddField(
model_name="user",
name="last_name",
field=models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
migrations.AddField(
model_name="user",
name="password",
field=models.CharField(default=1, max_length=128, verbose_name="password"),
preserve_default=False,
),
migrations.AddField(
model_name="user",
name="user_permissions",
field=models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
migrations.AlterField(
model_name="user",
name="email",
field=models.EmailField(
blank=True, default=1, max_length=254, verbose_name="email address"
),
preserve_default=False,
),
migrations.AlterField(
model_name="user",
name="is_active",
field=models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
migrations.AlterField(
model_name="user",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
]

View File

@ -0,0 +1,48 @@
# Generated by Django 5.0.7 on 2024-07-20 03:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0003_alter_user_options_alter_user_managers_and_more"),
]
operations = [
migrations.CreateModel(
name="EmailVerificationCode",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("email", models.EmailField(max_length=254)),
("code", models.CharField(max_length=6)),
("created_at", models.DateTimeField(auto_now_add=True)),
("is_used", models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name="SMSVerificationCode",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("phone_number", models.CharField(max_length=15)),
("code", models.CharField(max_length=6)),
("created_at", models.DateTimeField(auto_now_add=True)),
("is_used", models.BooleanField(default=False)),
],
),
]

View File

@ -0,0 +1,60 @@
# Generated by Django 5.0.7 on 2024-08-03 10:13
import django.db.models.deletion
from datetime import datetime
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0004_emailverificationcode_smsverificationcode"),
]
operations = [
migrations.CreateModel(
name="VideoGeneration",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("text", models.TextField()),
("voice_name", models.CharField(max_length=50)),
("style", models.CharField(default="general", max_length=50)),
("rate", models.IntegerField(default=0)),
("media_type", models.CharField(max_length=50)),
("ratio", models.CharField(max_length=10)),
("audio_url", models.URLField(blank=True, null=True)),
("video_url", models.URLField(blank=True, null=True)),
(
"status",
models.CharField(
choices=[
("pending", "Pending"),
("in_progress", "In Progress"),
("completed", "Completed"),
("failed", "Failed"),
],
default="pending",
max_length=20,
),
),
("created_at", models.DateTimeField(default=datetime.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-03 14:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0005_videogeneration"),
]
operations = [
migrations.AddField(
model_name="videogeneration",
name="slug",
field=models.CharField(default=1, max_length=50),
preserve_default=False,
),
]

View File

@ -0,0 +1,37 @@
# Generated by Django 5.0.7 on 2024-08-24 04:16
from datetime import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0006_videogeneration_slug"),
]
operations = [
migrations.CreateModel(
name="Plan",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=100, unique=True)),
("description", models.TextField()),
("price", models.DecimalField(decimal_places=2, max_digits=10)),
("credits_per_month", models.IntegerField(default=0)),
("created_at", models.DateTimeField(default=datetime.now)),
("updated_at", models.DateTimeField(auto_now=True)),
("is_promotional", models.BooleanField(default=False)),
("unlimited_exports", models.BooleanField(default=False)),
("smart_music_sync", models.BooleanField(default=False)),
],
),
]

View File

@ -0,0 +1,62 @@
# Generated by Django 5.0.7 on 2024-08-24 12:15
import django.db.models.deletion
from datetime import datetime
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0007_plan"),
]
operations = [
migrations.CreateModel(
name="Order",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("order_id", models.CharField(max_length=100, unique=True)),
("username", models.CharField(max_length=150)),
("amount", models.DecimalField(decimal_places=2, max_digits=10)),
(
"payment_method",
models.CharField(
choices=[("alipay", "Alipay"), ("paypal", "PayPal")],
max_length=10,
),
),
(
"status",
models.CharField(
choices=[
("pending", "Pending"),
("completed", "Completed"),
("canceled", "Canceled"),
("failed", "Failed"),
],
default="pending",
max_length=10,
),
),
("created_at", models.DateTimeField(default=datetime.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.7 on 2024-08-24 13:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0008_order"),
]
operations = [
migrations.AddField(
model_name="user",
name="membership_end",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="user",
name="membership_start",
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 5.0.7 on 2024-08-26 13:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0009_user_membership_end_user_membership_start"),
]
operations = [
migrations.AlterModelOptions(
name="order",
options={
"ordering": ["-created_at"],
"verbose_name": "Order",
"verbose_name_plural": "Orders",
},
),
migrations.AddField(
model_name="order",
name="plan",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="WebAdmin.plan",
),
preserve_default=False,
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-08-26 13:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0010_alter_order_options_order_plan"),
]
operations = [
migrations.AlterField(
model_name="order",
name="amount",
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 5.0.7 on 2024-08-29 14:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0011_alter_order_amount"),
]
operations = [
migrations.AddField(
model_name="videogeneration",
name="video_id",
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-29 14:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0011_alter_order_amount"),
]
operations = [
# 这里不再有任何操作,因为我们手动删除了重复的字段添加
# migrations.AddField(
# model_name="videogeneration",
# name="video_id",
# field=models.CharField(default="", max_length=255, unique=True),
# ),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-08-29 14:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0012_videogeneration_video_id"),
]
operations = [
migrations.AddField(
model_name="videogeneration",
name="video_id",
field=models.CharField(default="", max_length=255, unique=True),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-08-30 02:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0013_videogeneration_video_id"),
]
operations = [
migrations.AddField(
model_name="videogeneration",
name="pid",
field=models.CharField(default="", max_length=50, null=True),
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 5.0.7 on 2024-09-14 10:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0014_videogeneration_pid"),
]
operations = [
migrations.AddField(
model_name="videogeneration",
name="extension_count",
field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name="videogeneration",
name="media_type",
field=models.CharField(max_length=50, null=True),
),
migrations.AlterField(
model_name="videogeneration",
name="rate",
field=models.IntegerField(default=0, null=True),
),
migrations.AlterField(
model_name="videogeneration",
name="style",
field=models.CharField(default="general", max_length=50, null=True),
),
migrations.AlterField(
model_name="videogeneration",
name="text",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="videogeneration",
name="voice_name",
field=models.CharField(max_length=50, null=True),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-09-14 14:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("WebAdmin", "0015_videogeneration_extension_count_and_more"),
]
operations = [
migrations.AddField(
model_name="videogeneration",
name="time_duration",
field=models.IntegerField(default=0),
),
]

Some files were not shown because too many files have changed in this diff Show More