美多商城
虚拟环境
虚拟环境安装
pip install virtualenv
创建虚拟环境
python -m venv venv
Django
激活安装,框架
项目创建
django-admin startproject meiduo_dall
配置开发目录
libs 存放第三方库文件
apps 存放Django的应用
templates 模板存放文件夹
utils 存放项目自己定义的公共函数或类等
setting文件配置
配置允许访问的域名
配置templates
修改语言
mysql数据库
创建数据库
配置数据库
redis数据库
安装django-redis
pip install django-redis
配置redis
CACHES = {
"default": { # 默认
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": { # session
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
配置工程日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已经存在的日志器
'formatters': { # 日志信息显示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': { # 对日志进行过滤
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { # 日志处理方法
'console': { # 向终端中输出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': { # 向文件中输出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'logs/meiduo.log'), # 日志文件的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': { # 日志器
'django': { # 定义了一个名为django的日志器
'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志
'propagate': True, # 是否继续传递日志信息
'level': 'INFO', # 日志器接收的最低日志级别
},
}
}
创建字应用
配置apps文件路径
配置子应用
注册
用户表的创建
手机号码的创建
CORS和自定义转换器
注册的创建
class RegisterView(View):
def post(self,request):
# 1.接收数据
body_bytes=request.body
body_dict=json.loads(body_bytes)
#2.获取数据
username=body_dict.get('username')
password=body_dict.get('password')
password2=body_dict.get('password2')
mobile=body_dict.get('mobile')
sms_code=body_dict.get('sms_code')
allow=body_dict.get('allow')
#3.数据验证
#3.1用户,密码,确认密码,手机号,协议等参数必备
if not all([username,password,password2,mobile,allow]):
return JsonResponse({'code':400,'errmsg':'缺少必要参数!'})
#3.2用户名满足规则
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$',username):
return JsonResponse({'code':400,'errmsg':'用户名格式有误!'})
#3.3 密码满足规则
if not re.match(r'^[0-9A-Za-z]{8,20}$',password):
return JsonResponse({'code':400,'errmsg':'密码格式有误!'})
#3.4确认密码必须与之前输入的密码格式一致
if password !=password2:
return JsonResponse({'code':400,'errmsg':'两次输入的密码不一致!'})
#3.5手机号满足规则,并不能重复
if not re.match(r'^1[345789]\d{9}$',mobile):
return JsonResponse({'code':400,'errmsg':'手机号格式有误!'})
#3.6必须同意协议
if allow !=True:
return JsonResponse({'code':400,'errmsg':'allow格式有误!'})
#4.数据入库
# User.objects.create(username=username,password=password,mobile=mobile)
try:
user=User.objects.create_user(username=username,password=password,mobile=mobile)
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'注册失败'})
#保持会话
login(request,user)
#5.返回响应
return JsonResponse({'code':0,'errmsg':'ok'})
验证码
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse,JsonResponse
from django_redis import get_redis_connection
from libs.captcha.captcha import captcha
from random import randint
from libs.yuntongxun.sms import CCP
from users.models import User
# Create your views here.
class ImageCodeView(View):
def get(self,request,uuid):
"""
:param request:
:param uuid:
:return:
text:验证码内容
image:图片
"""
text,image=captcha.generate_captcha()
#将验证码存到redis库中
redis_cli=get_redis_connection('code')
redis_cli.setex(uuid,100,text)
return HttpResponse(image,content_type='image/jpeg')
class SmsCodeView(View):
"""
短信验证码
"""
def get(self,request,mobile):
#1.接收参数
image_code=request.GET.get('image_code')
uuid=request.GET.get('image_code_id')
#2.验证参数
if not all([image_code,uuid]):
return JsonResponse({'code':400,'errmsg':'参数不全'})
redis_cli=get_redis_connection('code')
redis_image_code=redis_cli.get(uuid)
if redis_image_code is None:
return JsonResponse({'code':400,'errmsg':'图片验证码过期'})
if redis_image_code.decode().lower()!=image_code.lower():
return JsonResponse({'code':400,'errmsg':'图片验证码输入错误'})
if User.objects.filter(mobile=mobile).count()>=1:
return JsonResponse({'code':400,'errmsg':'手机号已注册'})
#标记位短信状态判断
send_flag=redis_cli.get('send_flag_%s'%mobile)
if send_flag is not None:
return JsonResponse({'code':400,'errmsg':'请勿在60秒内频繁改善验证码'})
#3.生成验证码
sms_code='%06d'%randint(0,999999)
#4.将验证码保存到redis中
redis_cli.setex(mobile,300,sms_code)
#设置短信改善状态标记位,有效期为60秒 标记位状态值
redis_cli.setex('send_flag_%s'%mobile,60,1)
#5.发送
CCP().send_template_sms(mobile,[sms_code,5],1)
#6.返回响应
return JsonResponse({'code':0,'errmsg':'ok'})
利用管道修改
Celery
安装
pip install -U celery
或
sudo easy_install Celery
初体验
class Broker:
#消息队列
broker_list=[]
class Worker:
def run(self,broker,func):
if func in broker.broker_list:
func()
else:
return 'error'
class Celery:
def __init__(self):
self.broker=Broker()
self.worker=Worker()
def add(self,func):
self.broker.broker_list.append(func)
def work(self,func):
self.worker.run(self.broker,func)
def send_sms_dode():
print('send_sms_dode')
app=Celery()
app.add(send_sms_dode)
app.work(send_sms_dode)
创建异步执行任务 (celery_task.py)
创建执行任务 (produce_task.py)
from celery_task import send_email
result=send_email.delay('yuan')
print(result.id)
result2=send_email.delay('alex')
print(result2.id)
创建查看任务执行结果 (resutl.py)
from celery.result import AsyncResult
from celery_task import cel
async_result=AsyncResult(id='5a1c6207-3f5f-4a4a-983e-e5a1bf21964a',app=cel)
if async_result.successful():
result=async_result.get()
print(result)
elif async_result.failed():
print('执行失败')
elif async_result.status=='PENDING':
print('任务等待中被执行')
elif async_result.status=='RETRY':
print('任务异常后正在重试')
elif async_result.status=='STARTED':
print('任务已经开始被执行')
多目录异步执行
定时任务执行
简单下的定时任务
import pytz
from celery_task import send_email
from datetime import datetime
from datetime import timedelta
#方式一
# v1=datetime(2024,3,23,14,49)
# print(v1)
# # v2=datetime.utcfromtimestamp(v1.timestamp())#要弃用了
# #使用fromtimestamp创建本地时间
# local_time=datetime.fromtimestamp(v1.timestamp(),pytz.utc)
# print(local_time)
# result=send_email.apply_async(args=['egon',],eta=local_time)
# print(result.id)
# # #使用pytz转换为UTC时间
# # v2=local_time.replace(tzinfo=pytz.timezone('UTC')).astimezone(pytz.utc)
#方式二
ctime=datetime.now()
#默认用utf时间
utc_ctime=datetime.fromtimestamp(ctime.timestamp(),pytz.utc)
time_delay=timedelta(seconds=10)
task_time=utc_ctime+time_delay
#使用apply_async并设定时间
result=send_email.apply_async(args=['egon'],eta=task_time)
print(result)
# print(v2)
多目录下执行定时任务
cel.conf.beat_schedule={
'add-every-10-seconds':{
#执行tasks01下的send_email函数
'task':'celery_tasks.task01.send_email',
#每隔6秒执行一次
#'schedule':1.0,
#'schedule':crontab(minute="*/1"),
'schedule':timedelta(seconds=6),
#传递参数
'args':('张三',)
},
# 'add-every-12-seconds':{
# 'task':'celery_tasks.task01.send_email',
# #每年4月11号,8点42分执行
# 'schedule':crontab(minute=42,hour=8,day_of_month=11,month_of_year=4),
# 'args':('张三',)
# }
}
账号登录
class LoginView(View):
"""
登录接口
"""
def post(self,request):
#1.接收数据 让前端传过来的json转成python字典
data=json.loads(request.body)
username=data.get('username')
password=data.get('password')
remembered=data.get('remembered')
# 2.验证数据
if not all([username,password]):
return JsonResponse({'code':400,'errmsg':'参数不全'})
#通过USERNAME_FIELD去改变authenticate查询行为 让他去查询mobile
if re.match(r'1[3-9]\d{9}',username):
User.USERNAME_FIELD='mobile'
else:
User.USERNAME_FIELD='username'
#3.判断账号密码
user=authenticate(username=username,password=password)
if user is None:
return JsonResponse({'code':400,'errmsg':'账号或密码错误'})
# 4.判断是否记住密码
if remembered:
#如果记住密码 session信息保存两周
request.session.set_expiry(None)
else:
#浏览器关闭后session失效
request.session.set_expiry(0)
#5.状态保持
login(request,user)
response=JsonResponse({'code':0,'errmsg':'ok'})
#让前端显示用户手机号/昵称 cookie 有效期为15天
response.set_cookie('username',username,max_age=3600*24*15)
return response
用户中心
激活验证
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
class LoginRequiredJSONMixin(LoginRequiredMixin):
"""
方式一
"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse({'code':400,'errmsg':'用户未登录'})
return super().dispatch(request,*args,**kwargs)
class LoginRequiredJSONMixin_2(LoginRequiredMixin):
"""
方式二
"""
def handle_no_permission(self):
return JsonResponse({'code':400,'errmsg':'账号未登录'})
邮箱验证
class EmailView(LoginRequiredJSONMixin,View):
"""邮箱登录"""
def put(self,request):
#1.接收参数
json_data=json.loads(request.body)
email=json_data.get('email')
#2.验证参数
if not email:
return JsonResponse({'code':400,'errmsg':'缺少email参数'})
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9]+(\.[a-z]{2,5}){1,2}$',email):
return JsonResponse({'code':400,'errmsg':'email格式不正确'})
#3.保存邮件
try:
request.user.email=email
request.user.save()
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'添加邮箱失败'})
#4.发送激活邮件
"""
subject:邮件标题
message:邮件内容
from_email:发件人 发件人邮件需要设置
recipient_list:收件人 [列表类型]
"""
subject='美多商城激活邮件'
message=''
# html_message="""
# 尊敬的用户:
# 你好!欢迎使用美多商城,需要你激活当前邮件,请点击:<a href='http://www.baidu.com'>www.baidu.com
# """
from_email='xxxx@163.com'
recipient_list=['xxx@qq.com']
#需要声明一个token值 本质是当前用户登录的id
token=generic_email_verify_token(request.user.id)
verify_url='http://127.0.0.1:8080/success_verify_email.html?token=%s'%token
html_message='<p>尊敬的用户您好!</p>' \
'<p>感谢您使用美多商城。</p>' \
'<p>您的邮箱为:%s。请点击此链接激活您的邮箱:</p>' \
'<p><a href="%s">%s<a></p>'%(email,verify_url,verify_url)
# send_mail(subject=subject,
# message=message,
# from_email=from_email,
# recipient_list=recipient_list,
# html_message=html_message)
#使用celery进行邮件发送
celery_send_email.delay(
subject=subject,
message=message,
from_email=from_email,
recipient_list=recipient_list,
html_message=html_message
)
return JsonResponse({'code': 0, 'errmsg': 'ok'})
状态验证
#邮箱状态验证
class EmailVerifyView(View):
def put(self,request):
#接收参数 因为当前的参数是通过查询字符串进行传递的
params=request.GET
token=params.get('token')
#验证参数
if token is None:
return JsonResponse({'code':400,'errmsg':'参数缺失'})
#获取用户的user_id 解密当前用户id加密状态
user_id=check_verify_token(token)
#判断当前id是否存在
if user_id is None:
return JsonResponse({'code':400,'errmsg':'参数错误'})
#根据用户id进行查询
user=User.objects.get(id=user_id)
#修改数据
user.email_active=True
#返回响应
return JsonResponse({'code':0,'errmsg':'ok'})
用户增删改查
class AddressCreateView(LoginRequiredJSONMixin,View):
"""用户收货地址增加"""
def post(self,request):
#1.获取用户信息
user=request.user
#2.接收请求
data=json.loads(request.body)
#3.获取参数
receiver=data.get('receiver')
province_id=data.get('province_id')
city_id=data.get('city_id')
district_id=data.get('district_id')
place=data.get('place')
mobile=data.get('mobile')
tel=data.get('tel')
email=data.get('email')
#4.验证参数
if not all([receiver,province_id,city_id,district_id,place,mobile]):
return JsonResponse({'code':400,'errmsg':'缺少必传参数'})
if not re.match(r'^1[3-9]\d{9}$',mobile):
return JsonResponse({'code':400,'errmsg':'参数mobile错误'})
if tel:
if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$',tel):
return JsonResponse({'code':400,'errmsg':'参数tel有误'})
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$',email):
return JsonResponse({'code':400,'errmsg':'参数email有误'})
#保存地址信息
try:
address=Address.objects.create(
#外键 需要保存用户实例对象 而不是一个具体的值
user=user,
title=receiver,
receiver=receiver,
province_id=province_id,
city_id=city_id,
district_id=district_id,
place=place,
mobile=mobile,
tel=tel,
email=email
)
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'数据库入库出错'})
address_dict={
'id':address.id,
'title':address.title,
"receiver":address.receiver,
"province":address.province.name,
"city":address.city.name,
"district":address.district.name,
"place":address.place,
"mobile":address.mobile,
"tel":address.tel,
"email":address.email
}
return JsonResponse({'code':0,'errmsg':'ok','address':address_dict})
class ShowAddress(LoginRequiredJSONMixin,View):
"""显示页面获取"""
def get(self,request):
#1.获取当前登录用户的所有地址
addresses=Address.objects.filter(user=request.user,is_deleted=False)
#2.创建一个空的列表
address_dict_list=[]
#3.对当前查询的结果集进行遍历
for address in addresses:
#创建一个字典
address_dict=dict(
id=address.id,
title=address.title,
receiver=address.receiver,
province=address.province.name,
city=address.city.name,
district=address.district.name,
place=address.place,
mobile=address.mobile,
tel=address.tel,
email=address.email
)
address_dict_list.append(address_dict)
return JsonResponse({'code':0,'errmsg':'ok','addresses':address_dict_list})
class UpdateAddressView(LoginRequiredJSONMixin,View):
"""数据更新,删除"""
def put(self,request,address_id):
#1.接收参数
json_dict=json.loads(request.body)
receiver=json_dict.get('receiver')
province_id=json_dict.get('province_id')
city_id=json_dict.get('city_id')
district_id=json_dict.get('district_id')
place=json_dict.get('place')
mobile=json_dict.get('mobile')
tel=json_dict.get('tel')
email=json_dict.get('email')
#校验地址
if not all([receiver, province_id, city_id, district_id, place, mobile]):
return JsonResponse({'code': 400, 'errmsg': '缺少必传参数'})
if not re.match(r'^1[3-9]\d{9}$', mobile):
return JsonResponse({'code': 400, 'errmsg': '参数mobile错误'})
if tel:
if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
return JsonResponse({'code': 400, 'errmsg': '参数tel有误'})
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return JsonResponse({'code': 400, 'errmsg': '参数email有误'})
#在修改前 需要判断当前地址是否存在
try:
Address.objects.filter(id=address_id).update(
user=request.user,
title=receiver,
receiver=receiver,
province_id=province_id,
city_id=city_id,
district_id=district_id,
place=place,
mobile=mobile,
tel=tel,
email=email
)
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'更新地址失败'})
#构造响应数据
address=Address.objects.get(id=address_id)
address_dict=dict(
id=address.id,
titile=address.title,
receiver=address.receiver,
province=address.province.name,
city=address.city.name,
district=address.district.name,
place=address.place,
mobile=address.mobile,
tel=address.tel,
email=address.email
)
return JsonResponse({'code':0,'errmsg':'ok','address':address_dict})
def delete(self,request,address_id):
#1.查询需要删除的地址
try:
address=Address.objects.get(id=address_id)
#2.进行逻辑删除并保存当前删除状态
address.is_deleted=True
address.save()
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'删除地址失败'})
#3.响应
return JsonResponse({'code':0,'errmsg':'ok'})
密码修改
class ChangePassWordView(LoginRequiredJSONMixin,View):
"""修改密码"""
def put(self,request):
#1.接收请求 获取 参数
dict_data=json.loads(request.body)
old_password=dict_data.get('old_password')
new_password=dict_data.get('new_password')
new_password2=dict_data.get('new_password2')
#2.校验参数
if not all([old_password,new_password,new_password2]):
return JsonResponse([{'code':400,'errmsg':'缺少必要参数'}])
#从数据库取出密码进行解密码
result=request.user.check_password(old_password)
if not result:
return JsonResponse({'code':400,'errmsg':'原始密码输入不正确'})
if not re.match(r'^[0-9a-zA-Z]{8,20}$',new_password):
return JsonResponse({'code':400,'errmsg':'密码最少8位,最长20位'})
if new_password != new_password2:
return JsonResponse({'code':400,'errmsg':'两次输入的密码不一致'})
#3.修改密码
try:
request.user.set_password(new_password)
request.user.save()
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'修改密码失败'})
#4.清除登录状态
logout(request)
#5.构造响应
response=JsonResponse({'code':0,'errmsg':'ok'})
#删除cookie信息
response.delete_cookie('username')
#6.返回响应
return response
docker安装
更新软件包列表
sudo apt update
sudo apt upgrade
安装一些必备软件包,让apt通过HTTPS使用
curl -fsSL https://download.docker.com/linux/ubuntu/gpg |sudo apt-key add -
将Docker版本库添加到PATH源
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
确保从Docker版本库
apt-cache policy docker-ce
安装Docker
sudo apt install docker-ce
检查Docker
sudo systemctl status docker
启动与停止
启动docker
sudo service docker start
重启docker
sudo service docker restart
停止docker
sudo service docker stop
镜像操作
例出所有镜像
下载镜像
删除镜像
容器操作
查看容器
sudo docker container ls
sudo docker container ls --all 查看所有
创建容器
停止容器
sudo docker container stop 容器名
查看容器
sudo docker container ls --all
window连接服务器
服务器安装
安装SSH:sudo apt-get install openssh-server
开启SSH:service sshd start
连接服务器
ssh 用户名@IP
创建虚拟环境
python3 -m venv first_one
激活环境
source first_one/bin/activate
下载安装包
sudo apt-get install libmysqlclient-dev
pip install -r requirments.txt
pycharm配置
安装Fastdfs
sudo docker image pull delron/fastdfs
商品首页与列表页
class IndexView(View):
"""商器首页"""
def get(self, request):
# 1.获取商品分类的数据
categories = get_categories()
# 2.广告数据
contents = {}
# 查询出来的数据是查询结果集 需要遍历
contents_categories = ContentCategory.objects.all()
for cat in contents_categories:
contents[cat.key] = cat.content_set.filter(status=True).order_by('sequence')
context = {
'categories': categories,
'contents': contents
}
return render(request, 'index.html', context)
class ListView(View):
"""列表页"""
def get(self, request, category_id):
# 1.接收参数 排序规则 分页数量 当前页数
ordering = request.GET.get('ordering')
page_size = request.GET.get('page_size')
page = request.GET.get('page')
# 2.获取分类id
try:
category = GoodsCategory.objects.get(id=category_id)
except GoodsCategory.DoesNotExist:
return JsonResponse({'code': 400, 'errmsg': '当前数据不存在'})
# 3.获取面包屑
breadcrumb = get_breadcrumb(category)
# 4.查询分类数据对应的sku商品数据
skus = SKU.objects.filter(category=category, is_launched=True).order_by(ordering)
# 分页
# 每页的数据量
paginator = Paginator(skus, per_page=page_size)
# 获取指定页面的数据
page_skus = paginator.page(page)
# 需要将获取到的查询数据集转成列表数据
sku_list = []
for sku in page_skus.object_list:
sku_list.append({
'id': sku.id,
'name': sku.name,
'price': sku.price,
'default_image_url': sku.default_image.url
})
# 获取总页数
total_num = paginator.num_pages
# 返回响应
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'breadcrumb': breadcrumb,
'list': sku_list,
'count': total_num
})
热销数据页
class HotGoodsView(View):
"""商品热销数据展示"""
def get(self, request, category_id):
# 通过模型查询当前的商品数据
skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]
hot_skus = []
for sku in skus:
hot_skus.append({
'id': sku.id,
'default_image_url': sku.default_image.url,
'name': sku.name,
'price': sku.price
})
return JsonResponse({'code': 0, 'errmsg': 'ok', 'hot_skus': hot_skus})
商品搜索
使用Docker安装Elasticsearch
获取Elasticsearch-ik镜像
sudo docker image pull delron/elasticsearch-ik:2.4.6-1.0
启动(需要配置IP)
sudo docker run -dit --name=elasticsearch --ne
twork=host -v /home/ytmjcmn/桌面/elasticsearch-2.4.6/config/:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0
Haystack安装配置
安装
pip install django-haystack
pip install elasticsearch==2.4.1
创建索引类
设置模板
建立索引
商器详情页
class DetailView(View):
"""商品详情页"""
def get(self,request,sku_id):
try:
sku=SKU.objects.get(id=sku_id)
except SKU.DoesNotExist:
pass
#1.分类数据
categories=get_categories()
#2.面包屑
breadcrumb=get_breadcrumb(sku.category)
#3.规格信息
goods_specs=get_goods_specs(sku)
context={
'categories':categories,
'breadcrumb':breadcrumb,
'sku':sku,
'specs':goods_specs
}
#4.返回数据给前端
return render(request,'detail.html',context)
统计商品后端访问量
class CategoryVisitCountView(View):
"""统计分类商品访问量后端"""
def post(self, request, category_id):
# 1.授受参数[分类id]
# 2.验证当前的分类id是否存在于数据库中
try:
category = GoodsCategory.objects.get(id=category_id)
except Exception as e:
print(e)
return JsonResponse({'code': 400, 'errmsg': '没有此分类'})
# 3.当前访问量的数据查询 查询当前的分类记录是否存在
today = date.today()
try:
gvc = GoodsVisitCount.objects.get(category=category, date=today)
except Exception as e:
print(e)
# 如果当前分类没有访问量数据则添加一个访问量数据
GoodsVisitCount.objects.create(
category=category,
date=today,
count=1
)
else:
gvc.count += 1
gvc.save()
return JsonResponse({'code': 0, 'errmsg': 'ok'})
浏览记录
class UserHistoryView(LoginRequiredJSONMixin,View):
"""浏览记录"""
#显示用户浏览信息
def get(self,request):
#1.连接redis
redis_cli=get_redis_connection('history')
#2.获取redis保存的数据
ids=redis_cli.lrange('history_%s'%request.user.id,0,-1)
#3.根据商品id进行查询
history_list=[]
for sku_id in ids:
sku=SKU.objects.get(id=sku_id)
history_list.append({
'id':sku.id,
'name':sku.name,
'default_image_url':sku.default_image.url,
'price':sku.price
})
return JsonResponse({'code':0,'errmsg':'ok','skus':history_list})
#添加用户浏览信息
def post(self,request):
#1.接受请求
data=json.loads(request.body)
#2.获取参数
sku_id=data.get('sku_id')
#3.验证参数
try:
sku=SKU.objects.get(id=sku_id)
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'没有此商品'})
#4.连接redis 保存浏览信息到redis中
redis_cli=get_redis_connection('history')
#5.数据去重
redis_cli.lrem('history_%s'% request.user.id,0,sku_id)
#6.保存数据 列表类型(或zset)
redis_cli.lpush('history_%s'% request.user.id,sku_id)
#7.保存数据 只 存储5条
redis_cli.ltrim('history_%s'% request.user.id,0,4)
#8.返回数据
return JsonResponse({'code':0,'errmsg':'ok'})
购物车
import json,pickle,base64
from django.views import View
from django.http import JsonResponse
from django_redis import get_redis_connection
from django.shortcuts import render
from goods.models import SKU
# Create your views here.
class CartsView(View):
"""购物车"""
def get(self,request):
"""
展示购物车
:param request:
:return:
"""
#获取用户信息
user=request.user
#判断当前用户是否登录
if user.is_authenticated:
#连接redis
redis_cli=get_redis_connection('carts')
#获取redis中的购物车信息
redis_cart=redis_cli.hgetall('carts_%s'%user.id)
#获取redis中的选中状态
cart_selected=redis_cli.smembers('selected_%s'%user.id)
#将redis中的数据构成cookie中的数据格式 方便统一查询
cart_dict={}
for sku_id,count in redis_cart.items():
cart_dict[int(sku_id)]={
'count':int(count),
'selected':sku_id in cart_selected
}
else:
#获取cookie信息 bytes
cart_str=request.COOKIES.get('carts')
#判断当前是否存在cookie信息
if cart_str:
#对当前cookie信息进行解码
cart_dict=pickle.loads(base64.b64decode(cart_str.encode()))
else:
#如果没有cookie信息 则初始化一个新的空字典
cart_dict={}
#构造购物车页面的数据渲染
sku_ids=cart_dict.keys()
skus=SKU.objects.filter(id__in=sku_ids)
#初始化空列表
cart_skus=[]
#针对当前的orm查询的数据集进行遍历
for sku in skus:
cart_skus.append({
'id':sku.id,
'name':sku.name,
'count':cart_dict.get(sku.id).get('count'),
'selected':cart_dict.get(sku.id).get('selected'),
'default_image_url':sku.default_image.url,
'price':sku.price,
'amount':(sku.price* cart_dict.get(sku.id).get('count')),
})
return JsonResponse({'code':0,'errmsg':'ok','cart_skus':cart_skus})
def post(self,request):
"""
添加购物车
:param request:
:return:
"""
#1.接收参数
json_dict=json.loads(request.body)
sku_id=json_dict.get('sku_id')
count=json_dict.get('count')
selected=json_dict.get('selected',True)
#2.验证参数
if not all([sku_id,count]):
return JsonResponse({'code':400,'errmsg':'缺少必要参数'})
#3.判断sku_id是否存在于mysql数据库中
try:
SKU.objects.get(id=sku_id)
except SKU.DoesNotExist:
return JsonResponse({'code':400,'errmsg':'当前商品不存在'})
#4.判断前端传递的数量值是否为一个整数
try:
count=int(count)
except Exception as e:
print(e)
return JsonResponse({'code':400,'errmsg':'参数count有误'})
#5.判断selected是否为bool值
if selected:
if not isinstance(selected,bool):
return JsonResponse({'code':400,'errmsg':'参数selected有误'})
#6.判断当前用户是否登录
user=request.user
#7.如果登录 则保存到redis
if user.is_authenticated:
redis_cli=get_redis_connection('carts')
#对数据进行保存
redis_cli.hincrby('carts_%s'%user.id,sku_id,count)# 减一个商品 count + (-1) 那么count就为0
# redis_cli.hset('carts_%s'%user.id,sku_id,count)#hash
redis_cli.sadd('selected_%s'% user.id,sku_id)#set 默认选中
#返回响应
return JsonResponse({'code':0,'errmsg':'ok'})
else:
#读取cookie数据
cart_str=request.COOKIES.get('carts')
#如果用户操作过购物车
if cart_str:
#将cart_str转成bytes 再将bytes转成base64的btyes 最后转成字典
cart_dict=pickle.loads(base64.b64decode(cart_str.encode()))
else:
#用户没有操作过购物车
cart_dict={}
#判断要加入几辆的商品是否存在购物车中,如果有相同商品 累加,没有添加
if sku_id in cart_dict:
#累加数量
origin_count=cart_dict[sku_id]['count']
count+=origin_count
cart_dict[sku_id]={
'count':count,
'selected':selected
}
#将字典转成bytes再将bytes转成base64的bytes,最后将bytes转成字符串
cookie_cart_str=base64.b64encode(pickle.dumps(cart_dict)).decode()
#创建响应对象
response=JsonResponse({'code':0,'errmsg':'ok'})
response.set_cookie('carts',cookie_cart_str,max_age=7*24*3600)
#返回响应
return response
def put(self,request):
"""
修改购物车
:param request:
:return:
"""
#获取用户信息
user=request.user
#接收数据
data=json.loads(request.body)
sku_id=data.get('sku_id')
count=data.get('count')
selected=data.get('selected',True)
#验证参数
if not all([sku_id,count]):
return JsonResponse({'code':400,'errmsg':'参数不全'})
#判断sku_id是否存在
try:
SKU.objects.get(id=sku_id)
except SKU.DoesNotExist:
return JsonResponse({'code':400,'errmsg':'此商品不存在'})
#判断count是否为数字
try:
count=int(count)
except Exception as e :
print(e)
return JsonResponse({'code':400,'errmsg':'参数count有误'})
#判断当前用户是否登录
if user.is_authenticated:
#连接redis
redis_cli=get_redis_connection('carts')
#更新hash
redis_cli.hset('carts_%s'%user.id,sku_id,count)
#更新set
if selected:
redis_cli.sadd('selected_%s'% user.id,sku_id)
else:
#如果当前用户将选中状态改为false时 需要删除selected 集合中的选中状态数据
redis_cli.srem('selected_%s'%user.id,sku_id)
return JsonResponse({'code':0,'errmsg':'ok','cart_sku':{'count':count,'selected':selected}})
else:
#读取cookie信息
cookie_cart=request.COOKIES.get('carts')
#判断当前cookie数据是否存在
if cookie_cart:
carts=pickle.loads(base64.b64decode(cookie_cart))
else:
carts={}
#更新数据
if sku_id in carts:
carts[sku_id]={
'count':count,
'selected':selected
}
#对当前cookie信息进行编码
new_carts=base64.b64encode(pickle.dumps(carts))
#设置cookie
response=JsonResponse({'code':0,'errmsg':'ok','cart_sku':{'count':count,'selected':selected}})
response.set_cookie('carts',new_carts.decode(),max_age=7*24*3600)
#返回响应
return response
def delete(self,request):
"""
删除购物车
:param request:
:return:
"""
#获取数据
json_data=json.loads(request.body)
sku_id=json_data.get('sku_id')
#验证参数
try:
SKU.objects.get(id=sku_id)
except SKU.DoesNotExist:
return JsonResponse({'code':400,'errmsg':'此商品不存在'})
#判断用户状态
user=request.user
if user.is_authenticated:
#连接redis
redis_cli=get_redis_connection('carts')
#删除hash
redis_cli.hdel('carts_%s'%user.id,sku_id)
#删除set
redis_cli.srem('selected_%s'%user.id,sku_id)
#返回响应
return JsonResponse({'code':0,'errmsg':'ok'})
else:
#获取cookie
cookie_cart=request.COOKIES.get('carts')
if cookie_cart:
#解码数据
carts=pickle.loads(base64.b64decode(cookie_cart))
else:
carts={}
#删除cookie
del carts[sku_id]
#cookie信息进行base64编码
new_carts=base64.b64encode(pickle.dumps(carts))
#设置cookie
response=JsonResponse({'code':0,'errmsg':'ok'})
response.set_cookie('carts',new_carts.decode(),max_age=7*24*3600)
return response
class CartsSelectAllView(View):
"""全选购物车"""
def put(self,request):
#接收参数
json_dict=json.loads(request.body)
selected=json_dict.get('selected',True)
#校验参数
if selected:
if not isinstance(selected,bool):
return JsonResponse({'code':400,'errmsg':'selected参数有误'})
#用户登录状态的判断
user=request.user
if user.is_authenticated:
redis_cli=get_redis_connection('carts')
cart=redis_cli.hgetall('carts_%s'%user.id)
#获取到页面中的所有的商品的id
sku_id_list=cart.keys()
#判断当前前端页面中的商品选中状态
if selected:
#修改redis中的商品的selected中的状态值
redis_cli.sadd('selected_%s'%user.id,*sku_id_list)
else:
#取消全选
redis_cli.srem('selected_%s'%user.id,*sku_id_list)
#返回响应
return JsonResponse({'code':0,'errmsg':'购物车全选/取消全选成功'})
else:
cart=request.COOKIES.get('carts')
#构造返回对象
response=JsonResponse({'code':0,'errmsg':'购物车全选/取消全选成功'})
if cart is not None:
#cookie 解码
cart=pickle.loads(base64.b64decode(cart.encode()))
for sku_id in cart:
cart[sku_id]['selected']=selected
#对组织好的字典数据进行base64编码
cookie_cart=base64.b64encode(pickle.dumps(cart)).decode()
#设置cookie
response.set_cookie('carts',cookie_cart,max_age=7*24*3600)
return response
class CartsSimpleView(View):
"""简单展示购物车"""
def get(self,request):
#判断当前用户是否登录
user=request.user
if user.is_authenticated:
#登录用户查询redis
redis_cli=get_redis_connection('carts')
redis_cart=redis_cli.hgetall('carts_%s'%user.id)
cart_selected=redis_cli.smembers('selected_%s'%user.id)
#因为redis数据结构与cookie数据结构不一致,需要重组
cart_dict={}
for sku_id,count in redis_cart.items():
cart_dict[int(sku_id)]={
'count':int(count),
'selected':sku_id in cart_selected
}
else:
#用户未登录则查询cookie
cart_str=request.COOKIES.get('carts')
#判断当前cookie是否为空
if cart_str:
#解码
cart_dict=pickle.loads(base64.b64decode(cart_str.encode()))
else:
cart_dict={}
#构造购物车数据
cart_skus=[]
sku_ids=cart_dict.keys()
#根据获取到的商品id进行商品数据查询
skus=SKU.objects.filter(id__in=sku_ids)
for sku in skus:
cart_skus.append({
'id':sku.id,
'name':sku.name,
'count':cart_dict.get(sku.id).get('count'),
'default_image_url':sku.default_image.url
})
#返回响应
return JsonResponse({'code':0,'errmsg':'ok','cart_skus':cart_skus})
订单
import json
from django.views import View
from django.http import JsonResponse
from django_redis import get_redis_connection
from django.utils import timezone
from django.db import transaction
from django.shortcuts import render
from users.models import Address
from utils.views import LoginRequiredJSONMixin
from goods.models import SKU
from decimal import Decimal
from .models import OrderInfo,OrderGoods
# Create your views here.
class OrederSettlementView(LoginRequiredJSONMixin,View):
"""订单页面"""
def get(self,request):
#获取用户信息
user=request.user
#查询地址信息
addresses=Address.objects.filter(user=request.user,is_deleted=False)
#将查询结果集转为字典
address_list=[]
for address in addresses:
address_list.append({
'id':address.id,
'province':address.province.name,
'city':address.city.name,
'district':address.district.name,
'place':address.place
})
#获取购物车中选中的商品信息
#1.连接redis
redis_cli=get_redis_connection('carts')
#2.通过管道获取hash和set数据
pipeline=redis_cli.pipeline()
pipeline.hgetall('carts_%s'%user.id)
pipeline.smembers('selected_%s'%user.id)
#3.管道提交
result=pipeline.execute()
#获取商品id 商品数量以及选中状态
sku_id_counts=result[0]
selected_ids=result[1]
#对当前选中状态进行筛选
selected_carts={}
for sku_id in selected_ids:
selected_carts[int(sku_id)]=int(sku_id_counts[sku_id])
#初始化列表
sku_list=[]
for sku_id,count in selected_carts.items():
sku=SKU.objects.get(id=sku_id)
sku_list.append({
'id':sku.id,
'name':sku.name,
'count':count,
'default_image_url':sku.default_image.url,
'price':sku.price
})
#声明运费变量 变量类型为货币类型
freight=Decimal('10')
context={
'skus':sku_list,
'addresses':address_list,
'freight':freight
}
return JsonResponse({'code':0,'errmsg':'ok','context':context})
class OrderCommitView(LoginRequiredJSONMixin,View):
"""订单提交"""
def post(self,request):
#获取商品信息
user=request.user
#接收数据
data=json.loads(request.body)
address_id=data.get('address_id')
pay_method=data.get('pay_method')
#验证数据
if not all([address_id,pay_method]):
return JsonResponse({'code':400,'errmsg':'请选择收货地址和支付方式'})
try:
address=Address.objects.get(id=address_id)
except Address.DoesNotExist:
return JsonResponse({'code':400,'errmsg':'参数address有误'})
#验证支付方式
if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'],OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:
return JsonResponse({'code':400,'errmsg':'参数pay_method有误'})
#手动生成order_id 格式 年月日时分秒+用户id(9位好数字)
order_id=timezone.localtime().strftime('%Y%m%d%H%M%S%f')+'%09d'%user.id
if pay_method==OrderInfo.PAY_METHODS_ENUM['CASH']:
#如果用户选择的支付方式为货到付款 那么支付状态为待发货
status=OrderInfo.ORDER_STATUS_ENUM['UNSEND']
else:
#如果用户选择的支付方式为支付宝 支付状态为待付款
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID']
#初始化商品数量与总金额 当前声明的默认值为0 等数据入库时取出数据并计算
total_count=0
total_amount=Decimal('0')
#运费
freight=Decimal('10.00')
#在数据入库之前需要添加事务回滚 保证数据的一致性
with transaction.atomic():
#设置一个事务开始点
point=transaction.savepoint()
#数据入库
orderinfo=OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=total_count,
total_amount=total_amount,
freight=freight,
status=status
)
#订单商品信息保存
redis_cli=get_redis_connection('carts')
#获取hash和set
sku_id_counts=redis_cli.hgetall('carts_%s'%user.id)
selected_ids=redis_cli.smembers('selected_%s'%user.id)
#遍历数据 并重新组织数据
carts={}
for sku_id in selected_ids:
#组织数据
carts[int(sku_id)]=int(sku_id_counts[sku_id])
for sku_id,count in carts.items():
while True:
#根据sku_id进行查询
try:
sku=SKU.objects.get(id=sku_id)
except SKU.DoesNotExist:
return JsonResponse({'code':400,'errmsg':'商品不存在'})
#库存判断
if sku.stock<count:
#如果库存不足 那么需要还原数据 数据回滚点
transaction.savepoint_rollback(point)
return JsonResponse({'code':400,'errmsg':'库存不足'})
# #减少库存
# sku.stock-=count
# #增加销量
# sku.sales+=count
# sku.save()
#通过乐观锁解决超卖
old_stock=sku.stock
#声明更新后的数据
new_stock=sku.stock - count
new_sales=sku.sales + count
#更新数据 在更新之前我们需要比对数据
result=SKU.objects.filter(id=sku_id,stock=old_stock).update(stock=new_stock,sales=new_sales)
#操作成功之后 执行修改后的数据数量
if result==0:
continue
# return JsonResponse({'code':400,'errmsg':'下单失败~~~~'})
#累加总数量和总金额
orderinfo.total_count+=count
orderinfo.total_amount+=(count * sku.price)
#保存数据
OrderGoods.objects.create(
order=orderinfo,
sku=sku,
count=count,
price=sku.price
)
break
orderinfo.save()
#事务提交
transaction.savepoint_commit(point)
#移除redis数据
pipeline=redis_cli.pipeline()
pipeline.hdel('carts_%s'%user.id,*selected_ids)
pipeline.srem('selected_%s'%user.id,*selected_ids)
pipeline.execute()
#返回响应
return JsonResponse({'code':0,'errmsg':'ok','order_id':order_id})
支付宝支付
from django.shortcuts import render
from django.views import View
from django.http import JsonResponse
from utils.views import LoginRequiredJSONMixin
from orders.models import OrderInfo
from meiduo_mall import settings
from alipay import AliPay,AliPayConfig
from .models import Payment
# Create your views here.
class PayUrlView(LoginRequiredJSONMixin,View):
"""支付宝支付"""
def get(self,request,order_id):
#获取用户信息
user=request.user
#获取数据并验证数据 为了验证当前订单的准确性 需要判断当前订单id 订单状态[待支付] 需要根据用户找到对应订单数据
try:
order=OrderInfo.objects.get(order_id=order_id,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'],user=user)
except OrderInfo.DoesNotExist:
return JsonResponse({'code':400,'errmsg':'此订单不存在...'})
#读取应用私钥和支付宝公钥
app_private_key_string=open(settings.APP_PRIVATE_KEY_PATH).read()
alipay_public_key_string=open(settings.ALIPAY_PUBLIC_KEY_PATH).read()
#创建支付宝实例
alipay=AliPay(
appid=settings.ALIPAY_APPIP,
app_notify_url=None,
app_private_key_string=app_private_key_string,
alipay_public_key_string=alipay_public_key_string,
#加密方式
sign_type='RSA2',
debug=settings.ALIPAY_DEBUG,
config=AliPayConfig(timeout=15),
)
#调用支付宝支付方法
subject='美多商城测试订单'
order_string=alipay.api_alipay_trade_page_pay(
#订单id
out_trade_no=order_id,
#订单总金额 需要转成字符串
total_amount=str(order.total_amount),
subject=subject,
return_url=settings.ALIPAY_RETURN_RUL
)
#拼接链接
pay_url="{}?{}".format(settings.ALIPAY_URL, order_string)
#返回响应
return JsonResponse({'code':0,'errmsg':'ok','alipay_url':pay_url})
class PaymentStatusView(View):
"""添加订单信息"""
def put(self,request):
#接收数据
data=request.GET
#将数据转为字典
data=data.dict()
#获取交易流水号
signature=data.pop('sign')
# 读取应用私钥和支付宝公钥
app_private_key_string = open(settings.APP_PRIVATE_KEY_PATH).read()
alipay_public_key_string = open(settings.ALIPAY_PUBLIC_KEY_PATH).read()
# 创建支付宝实例
alipay = AliPay(
appid=settings.ALIPAY_APPIP,
app_notify_url=None,
app_private_key_string=app_private_key_string,
alipay_public_key_string=alipay_public_key_string,
# 加密方式
sign_type='RSA2',
debug=settings.ALIPAY_DEBUG,
config=AliPayConfig(timeout=15),
)
success=alipay.verify(data,signature)
if success:
trade_no=data.get('trade_no')
order_id=data.get('out_trade_no')
#保存订单号
Payment.objects.create(
trade_id=trade_no,
order_id=order_id
)
#改变订单状态
OrderInfo.objects.filter(order_id=order_id).update(
status=OrderInfo.ORDER_STATUS_ENUM['UNSEND']
)
#返回响应
return JsonResponse({'code':0,'errmsg':'ok','trade_id':trade_no})
else:
return JsonResponse({'code':400,'errmsg':'订单状态修改错误...'})