前后端不分离项目
天天生鲜
项目开发流程
B2B,商业对商业的电子商务模式
C2C,消费者个人对个人
B2C,商户对个人,如天天生鲜(Business to Customer)
C2B,个人对企业,个人提出需求,交给企业完成
O2O,线上到线下,如美团
F2C,工厂到个人
B2B2C,商业对商业对个人,如京东、淘宝
项目立项,确定做这个项目
需求分析,功能需求分析,可行性分析
原型设计,磨刀
后端架构设计(模块划分、技术选择)、前端UI设计、页面设计
详细设计,各个功能模块划分及可行性
数据库设计,表、字段、关系
编码实现(git)、单元测试
代码整合
集成测试
上线发布
可行性分析
现在的web系统开发已经相当成熟,技术上可以采用java/python的生态圈实现一个前后端交互的超市系统。
后端再搭配一些机器学习算法实现商品的智能推荐。
开发环境
python 3.6
django2.2.12
mysql-5.7
vscode
相关技术分析
需求分析
用户模块
-
注册页面
用户名唯一,密码满足复杂度,邮箱符合规范,注册完成向用户邮箱发送激活链接,用户点击链接实现激活账号。 -
登录页,使用用户名、密码登录;后端验证、会话保持session&Cookie
-
用户中心
信息页,显示用户的信息,姓名、电话、地址、最近浏览的信息;
地址页,显示默认的收货地址、添加收获地址功能
订单页,显示订单信息
页面顶部,显示已经登录的用户信息
商品模块
-
index.html,网站的首页,动态轮播图(展示推荐商品)
动态指定信息类别(点击跳到对应的锚点)、推荐类别(点击进入详情页)
轮播图展示推荐,活动页展示活动产品
-
list.html,商品列表页
按照类别显示商品 -
detail.html,商品详情页,一个商品的详细信息,可以加入购物车
搜索框,顶部显示,可以 关键字 搜索商品
购物车模块
-
首页展示我的购物车,页面跳转此部分不变(作为父模板)
-
点击商品列表项目,进入详情页,然后可以加入购物车
-
用户登录后,购物车显示加入的商品数
-
购物车页面car.html, 显示加入商品的信息
订单模块
- 提交订单页面,显示将要购买的商品信息及支付方式
- 点击 ‘提交订单’,创建订单
- 用户中心的订单页面显示用户的全部订单,点击支付完成付款
概要设计
系统架构设计----页面
系统架构设计----功能
系统结构设计----部署
总体架构:
mysql, web应用普遍使用的关系数据库,数据量达到亿级别时,检索性能下降,无法很好地应对高并发的压力。
缓存服务器,将经常用到的数据存储在缓存中,减少对mysql数据库的访问,分担数据库的压力。常用redis(缓存数据、session数据),使用python中的redis模块连接redis-server.
celery,异步处理库,处理一些耗时的操作(pip3 install celery)
fastdfs,分布式文件存储系统,django自带的图片上传功能,面对大量数据时,效率不高,所以使用fastdfs。
详细设计
用户模块
采用Django自带的认证系统,继承AbstractUser类,扩展模型类的字段,创建普通用户、管理员用户
使用内建的authenticate 验证用户的登录
使用内建的login、logout 实现用户的登录、登出
使用内建的login_required检测用户的登录
Redis实现对session的缓存,
邮件采用Django内置的send_mail()函数
采用celery实现异步请求,发送邮件
历史浏览记录使用Redis的list作为记录
- 创建user应用
所有应用可以放入一个文件夹apps,然后从文件夹下安装和导入。
#创建应用
python3 manage.py startapp user
#安装应用 settings.py > INSTALLED_APPS > apps.user
#创建模型类User, 继承auth应用的AbstractUser
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer #序列化对象
from db.base_model import BaseModel #自定义的基类 created_time/updated_time/is_delete 字段
class User(AbstractUser, BaseModel): #BaseModel 自定义的时间基类
'''用户模型类'''
#username、password、email 类属性
#is_active/is_superuser
#直接继承AbstractUser
def generate_active_token(self):
'''生成 用户签名 字符串'''
serializer = Serializer(settings.SECRET_KEY, 3600)
info = {'confirm': self.id}
token = serializer.dumps(info) #序列化为字节串
return token.decode()
class Meta:
db_table = 'df_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
# 创建Address模型类
from django.db import models
#自定义一个对象管理器,类似的objects
class AddressManager(models.Manager):
#自定义一个方法
def get_default_address(self, user):
#self is objects
try:
address = self.get(user=user, is_default=True)
except self.model.DoesNotExist as e:
address = None
return address
class Address(BaseModel): #BaseModel已经继承models.Model
"""收件地址模型类"""
user = models.ForeignKey('User', on_delete=models.CASCADE, verbose_name='所属用户')
receiver = models.CharField(max_length=20, verbose_name='收件人')
addr = models.CharField(max_length=256, verbose_name='收货地址')
zip_code = models.CharField(max_length=6, null=True, verbose_name='邮政编码')
phone = models.CharField(max_length=11, verbose_name='联系电话')
is_default = models.BooleanField(default=False, verbose_name='是否默认')
# 自定义一个模型管理器对象, 使用当前对象管理器操作数据
objects = AddressManager()
class Meta:
db_table = 'df_address' #数据库中的表名
verbose_name = '地址' #后台管理的表名
verbose_name_plural = verbose_name #表名为单数形式
# 迁移模型类
python3 manage.py makemigrations
python3 manage.py migrate
- 注册子功能
路由:/user/register ; 注意最后没有/
GET 请求,返回register.html
POST请求,提交注册数据
视图类,RegisterView(View)
from django.views.generic import View
class RegisterView(View):
def get(self,request):
return render(request, 'register.html') #所有的页面放/templates
def post(self, request):
# 接受前端提交的数据,进行注册
username = request.POST.get('user_name')
password = request.POST.get('pwd') #
email = request.POST.get('email')
allow = request.POST.get('allow')
-
登录子功能
-
用户中心子功能
商品模块
采用MySQL数据库存储,自定义模型类
django-haystack+whoosh实现对中文商品名字的检索(全文索引)
Nginx+fastdfs实现对图片的存储
- 创建goods应用
#创建应用
python3 manage.py startapp goods
#安装应用 settings.py > INSTALLED_APPS > apps.goods
#创建模型类
class GoodsType(BaseModel):
"""商品类型 模型类"""
name = models.CharField(max_length=20, verbose_name='种类名称')
#前端通过class属性指定logo,所以这里的logo值为字符串
logo = models.CharField(max_length=20, verbose_name='标识')
#用户上传图片,在根目录下的type目录
image = models.ImageField(upload_to='type', verbose_name='商品类型图片')
class Meta:
db_table = 'df_goods_type'
verbose_name = '商品种类'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsSPU(BaseModel):
"""
商品SPU模型类
标准产品单位,如iPhone13,与颜色、尺寸等无关
"""
name = models.CharField(max_length=20, verbose_name='商品SPU名称')
detail = HTMLField(blank=True, verbose_name='商品详情')
class Meta:
db_table = 'df_goods_spu'
verbose_name = '商品SPU'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsSKU(BaseModel):
"""
商品SKU模型类
标准库存单位,与产品属性有关
"""
status_choices = (
(0, '下线'),
(1, '上线'),
)
type = models.ForeignKey('GoodsType', on_delete=models.CASCADE, verbose_name='商品种类')
goods_spu = models.ForeignKey('GoodsSPU',on_delete=models.CASCADE, verbose_name='商品SPU')
name = models.CharField(max_length=20, verbose_name='商品名称')
desc = models.CharField(max_length=256, verbose_name='商品简介')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
unit = models.CharField(max_length=30, verbose_name='商品单位')
image = models.ImageField(upload_to='goods', verbose_name='商品图片')
stock = models.IntegerField(default=1, verbose_name='商品库存')
sales = models.IntegerField(default=0, verbose_name='商品销量')
status = models.SmallIntegerField(default=1, choices=status_choices, verbose_name='商品状态')
class Meta:
db_table = 'df_goods_sku'
verbose_name = '商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsImage(BaseModel):
"""商品图片模型类"""
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
image = models.ImageField(upload_to='goods', verbose_name='图片路径')
class Meta:
db_table = 'df_goods_image'
verbose_name = '商品图片'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
class IndexGoodsBanner(BaseModel):
"""首页轮播商品展示模型类"""
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
image = models.ImageField(upload_to='banner', verbose_name='图片')
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
class Meta:
db_table = 'df_index_banner'
verbose_name = '首页轮播图片'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
class IndexTypeGoodsBanner(BaseModel):
"""首页分类商品展示模型类"""
DISPLAY_TYPE_CHOICES = (
(0, '标题'),
(1, '图片'),
)
type = models.ForeignKey('GoodsType', on_delete=models.CASCADE, verbose_name='商品类型')
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
display_type = models.SmallIntegerField(default=1, choices=DISPLAY_TYPE_CHOICES, verbose_name='商品显示方式')
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
class Meta:
db_table = 'df_index_type_goods'
verbose_name = '主页分类展示商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
class IndexPromotionBanner(BaseModel):
"""首页促销活动模型类"""
url = models.CharField(max_length=256, verbose_name='活动链接')
name = models.CharField(max_length=20, verbose_name='活动名称')
image = models.ImageField(upload_to='goods', verbose_name='图片路径')
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
class Meta:
db_table = 'df_index_promotion'
verbose_name = '主页促销活动'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
购物车模块
订单模块
使用MySQL事务,对一组sql操作进行提交或者撤销,使用悲观锁处理订单并发效果。
数据库设计
-
用户表
-
用户收件地址表
-
商品SKU表
-
商品种类表
-
商品图片表
-
商品SPU表
SPU = Standard Product Unit,标准产品单位
属性值、特性相同的商品即可称为一个SPU。如颜色、款式、套餐无关
如迷你香蕉、红富士苹果、iphone7等
SKU = Stock Keep Unit (库存量单位)
库存进出计量单位,如一捆、一箱、1kg等单位,物理上不可分割的最小存货单元。
-
轮播图数据表
-
促销活动表
-
商品列表项 数据表
-
订单信息表
-
订单商品表
redis,实现购物车、用户最近浏览记录
持久化?
编码实现
参考详细设计中的代码
效果展示
总结
参考文献
天天生鲜项目