添加部分token

This commit is contained in:
jlj 2025-05-20 12:24:53 +08:00
parent efafe4c452
commit c6dbda5a88
8 changed files with 477 additions and 294 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') (datetime.now().date() - timedelta(days=30 - i)).strftime('%Y-%m-%d')
for i in range(31) for i in range(31)
] ]
# 设定随机的起始值和波动 # 设定随机的起始值和波动
import random import random
base_gmv = random.uniform(500, 3000) base_gmv = random.uniform(500, 3000)

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,23 +1,65 @@
from django.db import models from django.db import models
from django.utils import timezone
# Create your models here. from datetime import timedelta
class User(models.Model): from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
"""用户模型,用于登录和账户管理"""
email = models.EmailField(max_length=255, unique=True, verbose_name="电子邮箱") class UserManager(BaseUserManager):
password = models.CharField(max_length=255, verbose_name="密码") def create_user(self, email, password=None, **extra_fields):
company = models.CharField(max_length=255, blank=True, null=True, verbose_name="公司名称") if not email:
name = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户姓名") raise ValueError('邮箱地址不能为空')
is_first_login = models.BooleanField(default=True, verbose_name="是否首次登录") email = self.normalize_email(email)
last_login = models.DateTimeField(blank=True, null=True, verbose_name="最近登录时间") user = self.model(email=email, **extra_fields)
if password:
# 时间戳 user.set_password(password)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") user.save(using=self._db)
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") return user
class Meta: class User(AbstractBaseUser):
verbose_name = "用户" """用户模型,用于登录和账户管理"""
verbose_name_plural = verbose_name email = models.EmailField(max_length=255, unique=True, verbose_name="电子邮箱")
db_table = "users" password = models.CharField(max_length=255, verbose_name="密码")
company = models.CharField(max_length=255, blank=True, null=True, verbose_name="公司名称")
def __str__(self): name = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户姓名")
return self.email 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
db_table = "users"
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

@ -1,268 +1,281 @@
from django.http import JsonResponse from django.http import JsonResponse
# from .models import TiktokUserVideos # from .models import TiktokUserVideos
import logging import logging
import os import os
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render from django.shortcuts import render
import json import json
import requests import requests
import concurrent.futures import concurrent.futures
import shutil import shutil
import dotenv import dotenv
import random import random
from rest_framework.decorators import api_view, permission_classes
dotenv.load_dotenv() from rest_framework.permissions import IsAuthenticated, AllowAny
import hashlib
# 添加logger定义 import time
logger = logging.getLogger(__name__) from django.contrib.auth.hashers import check_password
from django.utils import timezone
directory_monitoring = {}
dotenv.load_dotenv()
# 全局变量来控制检测线程
monitor_thread = None # 添加logger定义
is_monitoring = False logger = logging.getLogger(__name__)
@csrf_exempt directory_monitoring = {}
@require_http_methods(["POST"])
def user_login(request): # 全局变量来控制检测线程
"""用户登录接口,首次登录会返回需要填写信息的标志""" monitor_thread = None
try: is_monitoring = False
from .models import User
import json def generate_token(user_id):
from django.contrib.auth.hashers import check_password, make_password """生成简单的token"""
from datetime import datetime # 使用用户ID和当前时间戳生成token
token_string = f"{user_id}:{time.time()}"
data = json.loads(request.body) return hashlib.sha1(token_string.encode()).hexdigest()
# 获取登录参数 def create_user_token(user):
email = data.get('email') """创建并保存用户token"""
password = data.get('password') from .models import UserToken
# 删除该用户的所有旧token
if not email or not password: UserToken.objects.filter(user=user).delete()
return JsonResponse({ # 生成新token
'code': 400, token = generate_token(user.id)
'message': '缺少必要参数: email 或 password', # 保存到数据库
'data': None user_token = UserToken.objects.create(
}, json_dumps_params={'ensure_ascii': False}) user=user,
token=token
# 查询用户 )
try: return token
user = User.objects.get(email=email)
@csrf_exempt
# 验证密码 @api_view(['POST'])
# 注意:这里假设密码已经进行了哈希存储,实际使用时需要采用适当的密码验证方法 @permission_classes([AllowAny])
# 如果密码未哈希存储,直接比较原始密码 def user_login(request):
password_valid = (user.password == password) """
用户登录接口
if not password_valid:
return JsonResponse({ 返回的 token 使用格式
'code': 401, 在请求头中添加
'message': '用户名或密码错误', Authorization: Token <your_token>
'data': None
}, json_dumps_params={'ensure_ascii': False}) 例如
Authorization: Token fa6931ec4cf5bd46d8dc3a671fe9862c467426b3
# 检查是否首次登录 """
is_first_login = user.is_first_login try:
from .models import User
# 更新最后登录时间 import json
user.last_login = datetime.now() from django.contrib.auth.hashers import check_password
user.save() from datetime import datetime
# 构造返回数据 data = json.loads(request.body)
user_data = {
'user_id': user.id, # 获取登录参数
'email': user.email, email = data.get('email')
'is_first_login': is_first_login, password = data.get('password')
'name': user.name,
'company': user.company if not email or not password:
} return JsonResponse({
'code': 400,
return JsonResponse({ 'message': '缺少必要参数: email 或 password',
'code': 200, 'data': None
'message': '登录成功', }, json_dumps_params={'ensure_ascii': False})
'data': user_data
}, json_dumps_params={'ensure_ascii': False}) # 查询用户
try:
except User.DoesNotExist: user = User.objects.get(email=email)
# 用户不存在,创建新用户
new_user = User.objects.create( # 验证密码
email=email, if not user.check_password(password):
password=password, # 注意:实际使用时应该哈希存储密码 return JsonResponse({
is_first_login=True, 'code': 401,
last_login=datetime.now() 'message': '用户名或密码错误',
) 'data': None
}, json_dumps_params={'ensure_ascii': False})
return JsonResponse({
'code': 200, # 生成并保存token
'message': '登录成功', token = create_user_token(user)
'data': {
'user_id': new_user.id, # 检查是否首次登录
'email': new_user.email, is_first_login = user.is_first_login
'is_first_login': True,
'name': None, # 更新最后登录时间
'company': None user.last_login = timezone.now()
} user.save()
}, json_dumps_params={'ensure_ascii': False})
# 构造返回数据
except Exception as e: user_data = {
logger.error(f"用户登录失败: {e}") 'user_id': user.id,
import traceback 'email': user.email,
logger.error(f"详细错误: {traceback.format_exc()}") 'is_first_login': is_first_login,
return JsonResponse({ 'name': user.name,
'code': 500, 'company': user.company,
'message': f'登录失败: {str(e)}', 'token': token
'data': None }
}, json_dumps_params={'ensure_ascii': False})
return JsonResponse({
'code': 200,
@csrf_exempt 'message': '登录成功',
@require_http_methods(["POST"]) 'data': user_data
def update_user_info(request): }, json_dumps_params={'ensure_ascii': False})
"""更新用户信息,首次登录时填写公司和姓名"""
try: except User.DoesNotExist:
from .models import User return JsonResponse({
import json 'code': 404,
'message': '用户不存在',
data = json.loads(request.body) 'data': None
}, json_dumps_params={'ensure_ascii': False})
# 获取参数
user_id = data.get('user_id') except Exception as e:
company = data.get('company') logger.error(f"用户登录失败: {e}")
name = data.get('name') import traceback
logger.error(f"详细错误: {traceback.format_exc()}")
if not user_id: return JsonResponse({
return JsonResponse({ 'code': 500,
'code': 400, 'message': f'登录失败: {str(e)}',
'message': '缺少必要参数: user_id', 'data': None
'data': None }, json_dumps_params={'ensure_ascii': False})
}, json_dumps_params={'ensure_ascii': False})
# 如果是首次登录,需要填写公司和姓名 @csrf_exempt
if not company or not name: @api_view(['POST'])
return JsonResponse({ @permission_classes([IsAuthenticated])
'code': 400, def update_user_info(request):
'message': '首次登录需要填写公司和姓名', """更新用户信息,需要认证"""
'data': None try:
}, json_dumps_params={'ensure_ascii': False}) data = json.loads(request.body)
# 查询用户并更新信息 # 获取参数
try: company = data.get('company')
user = User.objects.get(id=user_id) name = data.get('name')
# 更新信息 # 获取当前认证用户
user.company = company user = request.user
user.name = name
user.is_first_login = False # 更新后不再是首次登录 # 如果请求中包含 user_id 且与当前用户不匹配,返回错误
user.save() if 'user_id' in data and int(data['user_id']) != user.id:
return JsonResponse({
return JsonResponse({ 'code': 403,
'code': 200, 'message': '您只能修改自己的信息',
'message': '信息更新成功', 'data': None
'data': { }, json_dumps_params={'ensure_ascii': False})
'user_id': user.id,
'email': user.email, # 如果是首次登录,需要填写公司和姓名
'is_first_login': False, if not company or not name:
'name': user.name, return JsonResponse({
'company': user.company 'code': 400,
} 'message': '首次登录需要填写公司和姓名',
}, json_dumps_params={'ensure_ascii': False}) 'data': None
}, json_dumps_params={'ensure_ascii': False})
except User.DoesNotExist:
return JsonResponse({ # 更新信息
'code': 404, user.company = company
'message': f'找不到ID为 {user_id} 的用户', user.name = name
'data': None user.is_first_login = False # 更新后不再是首次登录
}, json_dumps_params={'ensure_ascii': False}) user.save()
except Exception as e: return JsonResponse({
logger.error(f"更新用户信息失败: {e}") 'code': 200,
import traceback 'message': '信息更新成功',
logger.error(f"详细错误: {traceback.format_exc()}") 'data': {
return JsonResponse({ 'user_id': user.id,
'code': 500, 'email': user.email,
'message': f'更新用户信息失败: {str(e)}', 'is_first_login': False,
'data': None 'name': user.name,
}, json_dumps_params={'ensure_ascii': False}) 'company': user.company
}
}, json_dumps_params={'ensure_ascii': False})
@csrf_exempt
@require_http_methods(["POST"]) except Exception as e:
def user_register(request): logger.error(f"更新用户信息失败: {e}")
"""用户注册接口,允许用户创建新账户,可选填写公司和姓名""" import traceback
try: logger.error(f"详细错误: {traceback.format_exc()}")
from .models import User return JsonResponse({
import json 'code': 500,
from datetime import datetime 'message': f'更新用户信息失败: {str(e)}',
'data': None
data = json.loads(request.body) }, json_dumps_params={'ensure_ascii': False})
# 获取注册参数
email = data.get('email') @csrf_exempt
password = data.get('password') @api_view(['POST'])
company = data.get('company') # 可选参数 @permission_classes([AllowAny])
name = data.get('name') # 可选参数 def user_register(request):
"""用户注册接口"""
# 检查必要参数 try:
if not email or not password: from .models import User
return JsonResponse({ import json
'code': 400, from datetime import datetime
'message': '缺少必要参数: email 或 password',
'data': None data = json.loads(request.body)
}, json_dumps_params={'ensure_ascii': False})
# 获取注册参数
# 检查邮箱是否已注册 email = data.get('email')
if User.objects.filter(email=email).exists(): password = data.get('password')
return JsonResponse({ company = data.get('company') # 可选参数
'code': 409, name = data.get('name') # 可选参数
'message': '该邮箱已注册',
'data': None # 检查必要参数
}, json_dumps_params={'ensure_ascii': False}) if not email or not password:
return JsonResponse({
# 创建用户 'code': 400,
try: 'message': '缺少必要参数: email 或 password',
# 根据是否提供公司和姓名决定是否为首次登录 'data': None
is_first_login = not (company and name) }, json_dumps_params={'ensure_ascii': False})
# 创建用户 # 检查邮箱是否已注册
user = User.objects.create( if User.objects.filter(email=email).exists():
email=email, return JsonResponse({
password=password, # 注意:实际使用时应该哈希存储密码 'code': 409,
company=company, 'message': '该邮箱已注册',
name=name, 'data': None
is_first_login=is_first_login, }, json_dumps_params={'ensure_ascii': False})
last_login=datetime.now()
) # 创建用户
try:
# 构造返回数据 # 根据是否提供公司和姓名决定是否为首次登录
user_data = { is_first_login = not (company and name)
'user_id': user.id,
'email': user.email, # 创建用户
'is_first_login': is_first_login, user = User.objects.create_user(
'company': user.company, email=email,
'name': user.name password=password,
} company=company,
name=name,
return JsonResponse({ is_first_login=is_first_login,
'code': 200, last_login=timezone.now()
'message': '注册成功', )
'data': user_data
}, json_dumps_params={'ensure_ascii': False}) # 构造返回数据
user_data = {
except Exception as e: 'user_id': user.id,
logger.error(f"创建用户失败: {e}") 'email': user.email,
return JsonResponse({ 'is_first_login': is_first_login,
'code': 500, 'company': user.company,
'message': f'注册失败: {str(e)}', 'name': user.name
'data': None }
}, json_dumps_params={'ensure_ascii': False})
return JsonResponse({
except Exception as e: 'code': 200,
logger.error(f"用户注册失败: {e}") 'message': '注册成功',
import traceback 'data': user_data
logger.error(f"详细错误: {traceback.format_exc()}") }, json_dumps_params={'ensure_ascii': False})
return JsonResponse({
'code': 500, except Exception as e:
'message': f'注册失败: {str(e)}', logger.error(f"创建用户失败: {e}")
'data': None return JsonResponse({
}, json_dumps_params={'ensure_ascii': False}) 'code': 500,
'message': f'注册失败: {str(e)}',
'data': None
}, json_dumps_params={'ensure_ascii': False})
except Exception as e:
logger.error(f"用户注册失败: {e}")
import traceback
logger.error(f"详细错误: {traceback.format_exc()}")
return JsonResponse({
'code': 500,
'message': f'注册失败: {str(e)}',
'data': None
}, json_dumps_params={'ensure_ascii': False})

View File

@ -44,7 +44,8 @@ INSTALLED_APPS = [
"apps.discovery.apps.DiscoveryConfig", "apps.discovery.apps.DiscoveryConfig",
"apps.template.apps.TemplateConfig", "apps.template.apps.TemplateConfig",
"apps.brands.apps.BrandsConfig", "apps.brands.apps.BrandsConfig",
'rest_framework',
'rest_framework_simplejwt',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -173,4 +174,34 @@ LOGGING = {
'propagate': True, 'propagate': True,
}, },
}, },
} }
# 自定义用户模型
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.contrib import admin
from django.urls import path, include from django.urls import path, include
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), 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/user/', include('apps.user.urls')),
path('api/daren_detail/', include('apps.daren_detail.urls')), path('api/daren_detail/', include('apps.daren_detail.urls')),
path('api/operation/', include('apps.expertproducts.urls')), path('api/operation/', include('apps.expertproducts.urls')),