Compare commits

...

2 Commits

Author SHA1 Message Date
jlj
83f8dd931e 合并 2025-05-20 12:40:54 +08:00
jlj
c6dbda5a88 添加部分token 2025-05-20 12:24:53 +08:00
13 changed files with 551 additions and 332 deletions

View File

@ -1373,7 +1373,6 @@ def get_creator_trends(request, creator_id=None):
(datetime.now().date() - timedelta(days=30 - i)).strftime('%Y-%m-%d')
for i in range(31)
]
# 设定随机的起始值和波动
import random
base_gmv = random.uniform(500, 3000)

View File

@ -0,0 +1,29 @@
# Generated by Django 5.1.5 on 2025-05-20 04:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('brands', '0002_alter_campaign_id'),
('daren_detail', '0001_initial'),
('expertproducts', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='negotiation',
name='creator',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='daren_detail.creatorprofile'),
),
migrations.AlterField(
model_name='negotiation',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='brands.product'),
),
migrations.DeleteModel(
name='Creator',
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 5.1.5 on 2025-05-20 04:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('expertproducts', '0002_alter_negotiation_creator_alter_negotiation_product_and_more'),
]
operations = [
migrations.DeleteModel(
name='Product',
),
]

View File

@ -1,28 +1,12 @@
from django.db import models
from django.db.models import JSONField # 用于存储动态谈判条款
from django.utils import timezone # Import timezone for timestamping
from apps.daren_detail.models import CreatorProfile # 导入CreatorProfile模型
from apps.brands.models import Product # 导入Product模型
# Create your models here.
class Product(models.Model):
name = models.CharField(max_length=100) # 商品名称
category = models.CharField(max_length=50) # 商品类目
max_price = models.DecimalField(max_digits=10, decimal_places=2) # 最高价格(公开报价)
min_price = models.DecimalField(max_digits=10, decimal_places=2) # 最低价格(底线)
description = models.TextField(blank=True) # 商品描述(可选)
def __str__(self):
return f"{self.name} ({self.max_price}元)"
class Creator(models.Model):
name = models.CharField(max_length=100) # 达人名称
sex = models.CharField(max_length=10, default='') # 达人性别,设置默认值为未知
age = models.IntegerField(default=18) # 达人年龄设置默认值为18
category = models.CharField(max_length=50) # 达人类别(如带货类)
followers = models.IntegerField() # 粉丝数
class Negotiation(models.Model):
STATUS_CHOICES = [
('brand_review', '品牌回顾'),
@ -34,8 +18,8 @@ class Negotiation(models.Model):
('abandoned', '已放弃'),
]
creator = models.ForeignKey('daren_detail.CreatorProfile', on_delete=models.CASCADE)
product = models.ForeignKey('brands.Product', on_delete=models.CASCADE)
creator = models.ForeignKey(CreatorProfile, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='price_negotiation')
current_round = models.IntegerField(default=1) # 当前谈判轮次
context = models.JSONField(default=dict) # 存储谈判上下文(如当前报价)

View File

@ -2,8 +2,9 @@ from rest_framework import serializers
# from user_management.models import OperatorAccount, PlatformAccount, Video, KnowledgeBase, KnowledgeBaseDocument
import uuid
from django.db.models import Q
from .models import Product, Creator, Negotiation, Message
from apps.daren_detail.models import CreatorProfile
from apps.brands.models import Product
from .models import Negotiation, Message
#
# class OperatorAccountSerializer(serializers.ModelSerializer):
@ -155,16 +156,15 @@ class ProductSerializer(serializers.ModelSerializer):
fields = '__all__'
class CreatorSerializer(serializers.ModelSerializer):
class Meta:
model = Creator
fields = '__all__'
class NegotiationSerializer(serializers.ModelSerializer):
creator_name = serializers.CharField(source='creator.name', read_only=True)
creator_avatar = serializers.CharField(source='creator.avatar_url', read_only=True)
product_name = serializers.CharField(source='product.name', read_only=True)
product_category = serializers.CharField(source='product.category', read_only=True)
class Meta:
model = Negotiation
fields = '__all__'
fields = ['id', 'creator', 'creator_name', 'creator_avatar', 'product', 'product_name', 'product_category', 'status', 'current_round', 'context']
read_only_fields = ('status', 'current_round')

View File

@ -17,15 +17,16 @@ import subprocess
import re
from django.db import connection
from django.core.mail import EmailMessage
from ollama import Client
from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser, FormParser
from .models import Product, Creator, Negotiation, Message
from .serializers import ProductSerializer, CreatorSerializer, NegotiationSerializer
import requests
from ollama import Client
from .models import Product, Negotiation, Message
from .serializers import NegotiationSerializer
from apps.daren_detail.models import CreatorProfile
from apps.brands.models import Product
client = Client(host="http://localhost:11434")
class ContentAnalysisAPI(APIView):
parser_classes = (MultiPartParser, FormParser)
@ -172,7 +173,7 @@ class NegotiationViewSet(viewsets.ModelViewSet):
product = serializer.validated_data['product']
# 检查该用户是否存在
if not Creator.objects.filter(id=creator.id).exists():
if not CreatorProfile.objects.filter(id=creator.id).exists():
return Response({
"code": 404,
"message": "未找到指定的达人",
@ -346,7 +347,7 @@ class NegotiationViewSet(viewsets.ModelViewSet):
})
# 获取所有相关的达人
creators = Creator.objects.filter(negotiation__in=negotiations).distinct()
creators = CreatorProfile.objects.filter(negotiation__in=negotiations).distinct()
if creators.exists():
# 序列化达人数据
creator_data = [{'name': creator.name, 'category': creator.category, 'followers': creator.followers} for
@ -362,6 +363,7 @@ class NegotiationViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['post'])
def offer_status(self, request):
"""获取谈判状态"""
# 获取请求参数
creator_id = request.data.get('creator_id')
product_id = request.data.get('product_id')
@ -374,7 +376,7 @@ class NegotiationViewSet(viewsets.ModelViewSet):
# 查找符合条件的谈判
try:
creator = Creator.objects.get(id=creator_id)
creator = CreatorProfile.objects.get(id=creator_id)
negotiation = Negotiation.objects.get(creator=creator, product_id=product_id)
return Response({
'code': 200,
@ -383,6 +385,12 @@ class NegotiationViewSet(viewsets.ModelViewSet):
'status': negotiation.status
}
})
except CreatorProfile.DoesNotExist:
return Response({
'code': 404,
'message': f'找不到ID为{creator_id}的达人',
'data': None
})
except Negotiation.DoesNotExist:
return Response({
'code': 404,

View File

@ -0,0 +1,42 @@
from rest_framework import authentication
from rest_framework import exceptions
from django.contrib.auth.models import AnonymousUser
from .models import User, UserToken
from django.utils import timezone
class CustomTokenAuthentication(authentication.BaseAuthentication):
keyword = 'Token' # 设置认证头关键字
def authenticate(self, request):
# 从请求头获取token
auth_header = request.META.get('HTTP_AUTHORIZATION')
if not auth_header:
return None
try:
# 提取token
parts = auth_header.split()
if len(parts) != 2 or parts[0] != self.keyword:
raise exceptions.AuthenticationFailed('无效的认证头格式,应为: Token <token>')
token = parts[1]
# 查找token记录并确保token存在且有效
try:
token_obj = UserToken.objects.select_related('user').get(
token=token,
expired_at__gt=timezone.now() # 确保token未过期
)
except UserToken.DoesNotExist:
raise exceptions.AuthenticationFailed('无效的token')
# 检查用户是否激活
if not token_obj.user.is_active:
raise exceptions.AuthenticationFailed('用户已被禁用')
return (token_obj.user, None)
except Exception as e:
raise exceptions.AuthenticationFailed(f'认证失败: {str(e)}')

View File

@ -0,0 +1,27 @@
# Generated by Django 5.1.5 on 2025-05-20 03:49
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0002_remove_user_is_active'),
]
operations = [
migrations.CreateModel(
name='UserToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=40, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('expired_at', models.DateTimeField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to='user.user')),
],
options={
'db_table': 'user_token',
},
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.1.5 on 2025-05-20 03:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0003_usertoken'),
]
operations = [
migrations.AddField(
model_name='user',
name='is_active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='user',
name='is_staff',
field=models.BooleanField(default=False),
),
]

View File

@ -1,7 +1,20 @@
from django.db import models
from django.utils import timezone
from datetime import timedelta
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
# Create your models here.
class User(models.Model):
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('邮箱地址不能为空')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
if password:
user.set_password(password)
user.save(using=self._db)
return user
class User(AbstractBaseUser):
"""用户模型,用于登录和账户管理"""
email = models.EmailField(max_length=255, unique=True, verbose_name="电子邮箱")
password = models.CharField(max_length=255, verbose_name="密码")
@ -9,11 +22,18 @@ class User(models.Model):
name = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户姓名")
is_first_login = models.BooleanField(default=True, verbose_name="是否首次登录")
last_login = models.DateTimeField(blank=True, null=True, verbose_name="最近登录时间")
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
# 时间戳
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = "用户"
verbose_name_plural = verbose_name
@ -21,3 +41,25 @@ class User(models.Model):
def __str__(self):
return self.email
@property
def is_authenticated(self):
return True
class UserToken(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tokens')
token = models.CharField(max_length=40, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
expired_at = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.expired_at:
# 设置token有效期为30天
self.expired_at = timezone.now() + timedelta(days=30)
super().save(*args, **kwargs)
def is_expired(self):
return timezone.now() > self.expired_at
class Meta:
db_table = 'user_token'

View File

@ -11,6 +11,12 @@ import concurrent.futures
import shutil
import dotenv
import random
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated, AllowAny
import hashlib
import time
from django.contrib.auth.hashers import check_password
from django.utils import timezone
dotenv.load_dotenv()
@ -23,14 +29,44 @@ directory_monitoring = {}
monitor_thread = None
is_monitoring = False
def generate_token(user_id):
"""生成简单的token"""
# 使用用户ID和当前时间戳生成token
token_string = f"{user_id}:{time.time()}"
return hashlib.sha1(token_string.encode()).hexdigest()
def create_user_token(user):
"""创建并保存用户token"""
from .models import UserToken
# 删除该用户的所有旧token
UserToken.objects.filter(user=user).delete()
# 生成新token
token = generate_token(user.id)
# 保存到数据库
user_token = UserToken.objects.create(
user=user,
token=token
)
return token
@csrf_exempt
@require_http_methods(["POST"])
@api_view(['POST'])
@permission_classes([AllowAny])
def user_login(request):
"""用户登录接口,首次登录会返回需要填写信息的标志"""
"""
用户登录接口
返回的 token 使用格式
在请求头中添加
Authorization: Token <your_token>
例如
Authorization: Token fa6931ec4cf5bd46d8dc3a671fe9862c467426b3
"""
try:
from .models import User
import json
from django.contrib.auth.hashers import check_password, make_password
from django.contrib.auth.hashers import check_password
from datetime import datetime
data = json.loads(request.body)
@ -51,22 +87,21 @@ def user_login(request):
user = User.objects.get(email=email)
# 验证密码
# 注意:这里假设密码已经进行了哈希存储,实际使用时需要采用适当的密码验证方法
# 如果密码未哈希存储,直接比较原始密码
password_valid = (user.password == password)
if not password_valid:
if not user.check_password(password):
return JsonResponse({
'code': 401,
'message': '用户名或密码错误',
'data': None
}, json_dumps_params={'ensure_ascii': False})
# 生成并保存token
token = create_user_token(user)
# 检查是否首次登录
is_first_login = user.is_first_login
# 更新最后登录时间
user.last_login = datetime.now()
user.last_login = timezone.now()
user.save()
# 构造返回数据
@ -75,7 +110,8 @@ def user_login(request):
'email': user.email,
'is_first_login': is_first_login,
'name': user.name,
'company': user.company
'company': user.company,
'token': token
}
return JsonResponse({
@ -85,24 +121,10 @@ def user_login(request):
}, json_dumps_params={'ensure_ascii': False})
except User.DoesNotExist:
# 用户不存在,创建新用户
new_user = User.objects.create(
email=email,
password=password, # 注意:实际使用时应该哈希存储密码
is_first_login=True,
last_login=datetime.now()
)
return JsonResponse({
'code': 200,
'message': '登录成功',
'data': {
'user_id': new_user.id,
'email': new_user.email,
'is_first_login': True,
'name': None,
'company': None
}
'code': 404,
'message': '用户不存在',
'data': None
}, json_dumps_params={'ensure_ascii': False})
except Exception as e:
@ -117,24 +139,25 @@ def user_login(request):
@csrf_exempt
@require_http_methods(["POST"])
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def update_user_info(request):
"""更新用户信息,首次登录时填写公司和姓名"""
"""更新用户信息,需要认证"""
try:
from .models import User
import json
data = json.loads(request.body)
# 获取参数
user_id = data.get('user_id')
company = data.get('company')
name = data.get('name')
if not user_id:
# 获取当前认证用户
user = request.user
# 如果请求中包含 user_id 且与当前用户不匹配,返回错误
if 'user_id' in data and int(data['user_id']) != user.id:
return JsonResponse({
'code': 400,
'message': '缺少必要参数: user_id',
'code': 403,
'message': '您只能修改自己的信息',
'data': None
}, json_dumps_params={'ensure_ascii': False})
@ -146,34 +169,23 @@ def update_user_info(request):
'data': None
}, json_dumps_params={'ensure_ascii': False})
# 查询用户并更新信息
try:
user = User.objects.get(id=user_id)
# 更新信息
user.company = company
user.name = name
user.is_first_login = False # 更新后不再是首次登录
user.save()
# 更新信息
user.company = company
user.name = name
user.is_first_login = False # 更新后不再是首次登录
user.save()
return JsonResponse({
'code': 200,
'message': '信息更新成功',
'data': {
'user_id': user.id,
'email': user.email,
'is_first_login': False,
'name': user.name,
'company': user.company
}
}, json_dumps_params={'ensure_ascii': False})
except User.DoesNotExist:
return JsonResponse({
'code': 404,
'message': f'找不到ID为 {user_id} 的用户',
'data': None
}, json_dumps_params={'ensure_ascii': False})
return JsonResponse({
'code': 200,
'message': '信息更新成功',
'data': {
'user_id': user.id,
'email': user.email,
'is_first_login': False,
'name': user.name,
'company': user.company
}
}, json_dumps_params={'ensure_ascii': False})
except Exception as e:
logger.error(f"更新用户信息失败: {e}")
@ -187,9 +199,10 @@ def update_user_info(request):
@csrf_exempt
@require_http_methods(["POST"])
@api_view(['POST'])
@permission_classes([AllowAny])
def user_register(request):
"""用户注册接口,允许用户创建新账户,可选填写公司和姓名"""
"""用户注册接口"""
try:
from .models import User
import json
@ -225,13 +238,13 @@ def user_register(request):
is_first_login = not (company and name)
# 创建用户
user = User.objects.create(
user = User.objects.create_user(
email=email,
password=password, # 注意:实际使用时应该哈希存储密码
password=password,
company=company,
name=name,
is_first_login=is_first_login,
last_login=datetime.now()
last_login=timezone.now()
)
# 构造返回数据

View File

@ -41,14 +41,14 @@ INSTALLED_APPS = [
'django_filters',
'django.contrib.staticfiles',
'channels',
'rest_framework',
'apps.user.apps.UserConfig',
"apps.expertproducts.apps.ExpertproductsConfig",
"apps.daren_detail.apps.DarenDetailConfig",
"apps.discovery.apps.DiscoveryConfig",
"apps.template.apps.TemplateConfig",
"apps.brands.apps.BrandsConfig",
'rest_framework',
'rest_framework_simplejwt',
]
MIDDLEWARE = [
@ -196,3 +196,33 @@ LOGGING = {
},
},
}
# 自定义用户模型
AUTH_USER_MODEL = 'user.User'
# REST Framework 设置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'apps.user.authentication.CustomTokenAuthentication',
),
}
# JWT 设置
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'UPDATE_LAST_LOGIN': False, # 不在 token 中记录最后登录时间
'TOKEN_TYPE_CLAIM': None, # 不在 token 中包含 token 类型
'JTI_CLAIM': None, # 不在 token 中包含 JWT ID
}
# JWT配置
JWT_SECRET_KEY = 'your-secret-key-here' # 建议使用更安全的密钥
JWT_EXPIRATION_DELTA = 24 * 60 * 60 # token有效期24小时

View File

@ -1,8 +1,14 @@
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/user/', include('apps.user.urls')),
path('api/daren_detail/', include('apps.daren_detail.urls')),
path('api/operation/', include('apps.expertproducts.urls')),