first commit
This commit is contained in:
commit
5383007f49
499
.venv/Lib/site-packages/django/contrib/auth/models.py
Executable file
499
.venv/Lib/site-packages/django/contrib/auth/models.py
Executable 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
7280
API日志.log
Executable file
File diff suppressed because one or more lines are too long
34459
API日志.log.2024-08-29
Executable file
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
1
API日志.log.2024-08-30
Executable 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
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
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
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
3
API日志.log.2024-09-13
Executable 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
20335
API日志.log.2024-09-15
Executable file
File diff suppressed because one or more lines are too long
23
Dockerfile
Executable file
23
Dockerfile
Executable 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
69
WebAdmin/AccessLog.py
Executable 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
0
WebAdmin/__init__.py
Executable file
BIN
WebAdmin/__pycache__/AccessLog.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/AccessLog.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/AccessLog.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/AccessLog.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/__init__.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/__init__.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/admin.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/admin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin_views.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/admin_views.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/admin_views.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/admin_views.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/ali_pay.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/ali_pay.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/ali_pay.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/ali_pay.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/aliyun_sms.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/api.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/api.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/api.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/apps.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/apps.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/apps.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/apps.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/audio.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/audio.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/audio.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/audio.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/base.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/base.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/base.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_avatar_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_music_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_music_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_music_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_music_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_text_img_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/create_tiktok_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/mail.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/mail.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/mail.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/mail.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/middleware.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/middleware.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/models.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/models.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/models.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_payment.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/paypal_payment.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_payment.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/paypal_payment.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/paypal_webhook.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/signals.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/signals.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/signals.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/signals.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/task.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/task.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/task.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/task.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/task_all.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/task_all.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/urls.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/urls.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/urls.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/user.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/user.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/user.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/user.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/utils.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/utils.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/utils.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
BIN
WebAdmin/__pycache__/views.cpython-310.pyc
Executable file
BIN
WebAdmin/__pycache__/views.cpython-310.pyc
Executable file
Binary file not shown.
BIN
WebAdmin/__pycache__/views.cpython-311.pyc
Normal file
BIN
WebAdmin/__pycache__/views.cpython-311.pyc
Normal file
Binary file not shown.
13
WebAdmin/admin.py
Executable file
13
WebAdmin/admin.py
Executable 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
37
WebAdmin/admin_views.py
Executable 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
331
WebAdmin/ali_pay.py
Executable 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
63
WebAdmin/aliyun_sms.py
Executable 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
500
WebAdmin/api.py
Executable 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
8
WebAdmin/apps.py
Executable 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
1236
WebAdmin/audio.py
Executable file
File diff suppressed because it is too large
Load Diff
239
WebAdmin/base.py
Executable file
239
WebAdmin/base.py
Executable 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
71
WebAdmin/create_avatar_video.py
Executable 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
71
WebAdmin/create_music_video.py
Executable 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
332
WebAdmin/create_text_img_video.py
Executable 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
142
WebAdmin/create_text_video.py
Executable 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
70
WebAdmin/create_tiktok_video.py
Executable 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
595
WebAdmin/data.json
Executable 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
0
WebAdmin/data.json.py
Executable file
35
WebAdmin/data_style.json
Executable file
35
WebAdmin/data_style.json
Executable 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
76
WebAdmin/mail.py
Executable 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
57
WebAdmin/mail_demo.py
Executable 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
57
WebAdmin/membership_rewards.py
Executable 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
15
WebAdmin/middleware.py
Executable 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
|
124
WebAdmin/migrations/0001_initial.py
Executable file
124
WebAdmin/migrations/0001_initial.py
Executable 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
22
WebAdmin/migrations/0002_user_last_login_ip_user_login_count.py
Executable file
22
WebAdmin/migrations/0002_user_last_login_ip_user_login_count.py
Executable 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),
|
||||||
|
),
|
||||||
|
]
|
135
WebAdmin/migrations/0003_alter_user_options_alter_user_managers_and_more.py
Executable file
135
WebAdmin/migrations/0003_alter_user_options_alter_user_managers_and_more.py
Executable 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
48
WebAdmin/migrations/0004_emailverificationcode_smsverificationcode.py
Executable file
48
WebAdmin/migrations/0004_emailverificationcode_smsverificationcode.py
Executable 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
60
WebAdmin/migrations/0005_videogeneration.py
Executable file
60
WebAdmin/migrations/0005_videogeneration.py
Executable 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
18
WebAdmin/migrations/0006_videogeneration_slug.py
Executable file
18
WebAdmin/migrations/0006_videogeneration_slug.py
Executable 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,
|
||||||
|
),
|
||||||
|
]
|
37
WebAdmin/migrations/0007_plan.py
Executable file
37
WebAdmin/migrations/0007_plan.py
Executable 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
62
WebAdmin/migrations/0008_order.py
Executable file
62
WebAdmin/migrations/0008_order.py
Executable 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
22
WebAdmin/migrations/0009_user_membership_end_user_membership_start.py
Executable file
22
WebAdmin/migrations/0009_user_membership_end_user_membership_start.py
Executable 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),
|
||||||
|
),
|
||||||
|
]
|
31
WebAdmin/migrations/0010_alter_order_options_order_plan.py
Executable file
31
WebAdmin/migrations/0010_alter_order_options_order_plan.py
Executable 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,
|
||||||
|
),
|
||||||
|
]
|
17
WebAdmin/migrations/0011_alter_order_amount.py
Executable file
17
WebAdmin/migrations/0011_alter_order_amount.py
Executable 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
18
WebAdmin/migrations/0012_videogeneration_video_id.py
Executable file
18
WebAdmin/migrations/0012_videogeneration_video_id.py
Executable 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),
|
||||||
|
# ),
|
||||||
|
]
|
17
WebAdmin/migrations/0013_videogeneration_video_id.py
Executable file
17
WebAdmin/migrations/0013_videogeneration_video_id.py
Executable 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),
|
||||||
|
),
|
||||||
|
]
|
17
WebAdmin/migrations/0014_videogeneration_pid.py
Executable file
17
WebAdmin/migrations/0014_videogeneration_pid.py
Executable 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),
|
||||||
|
),
|
||||||
|
]
|
42
WebAdmin/migrations/0015_videogeneration_extension_count_and_more.py
Executable file
42
WebAdmin/migrations/0015_videogeneration_extension_count_and_more.py
Executable 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),
|
||||||
|
),
|
||||||
|
]
|
17
WebAdmin/migrations/0016_videogeneration_time_duration.py
Executable file
17
WebAdmin/migrations/0016_videogeneration_time_duration.py
Executable 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
Loading…
Reference in New Issue
Block a user