小鱼商城项目

项目开发小鱼商城

因为网络原因,一些图片加载不出来,可以去我的内容里面浏览原始md文档

一、前期准备

项目需求模块归纳

项目开发模式与运行机制项目创建与配置:

1. 项目需求:

需求驱动开发,它是开发的基石,也是开发人员的工作目标,在着手开发项目之前,明确项目的业务流程和主要的业务需求非常必要。

实际开发中需求由产品经理提供,开发人员通过产品经理提供的示例网站、产品原型图或需求分档明确项目需求,确定项目业务

我们从电商平台通常所具备的首页、用户、商品、购物车、结算和支付这些模块入手,结合示例网站分析项目需求,明确项目各模块的业务

1.1 首页

 

首页是网站的入口,该页面一般呈现广告信息和其他页面的入口

首页主要分为两部分

第一部分是各种功能的入口,包括导航栏、搜索栏、简单购物车和分类导航菜单;

第二部分是广告,包括轮播图、快讯以及页面底部的分层广告。

分类导航栏

分类导航菜单是首页的核心之一,这个菜单分为三级,其中一级菜单包含11个频道,每个频道显示3~4个分类(如频道1包含手机、相机、数码三个分类);二级菜单是各个频道中各个分类的详细类别(如手机分类包含手机通讯、手机配件等7个二级类别);三级菜单是每个二级类别的详细类别(如手机通讯包含手机、游戏手机等4个三级类别),具体如下图所示

 

  
  

广告信息:

分类导航菜单右侧的轮播图、快讯和页面下方的楼层是首页的第二项核心,虽然这些部分包含一些商品信息,但它们不是商品列表,而是小鱼商城的广告。当下流行的电商网站都在首页放置广告、展示商品新鲜资讯以吸引用户

  
  

 

 

1.2 用户

若想要通过该平台购买商品,那么网站的访问者应先在该平台进行注册,成为该平台的用户。小鱼商城的首页已经登录了则单击首页右上角的【退出】,退出登录,此时页面导航栏的“用户名”和“退出”分别变为“登录”和“注册”

  
  

 

注册和登录是与用户相关的两个重要功能,单击“注册”按钮,可进入注册页面注册用户;单击“登录”按钮,可进入登录页面,使用注册的账户信息进行登录

 

若已注册账号,可通过下图展示的登录页面进行登录。若成功登录,浏览器将跳转到首页

  
  

已登录的用户可对账号信息进行管理,小鱼商城将此项功能放在导航栏的“用户中心”中实现。单击导航栏的用户中心进入相应页面,如下图所示

  
  

单击收货地址选项,进入收货地址页面。

  
  

 

单击全部订单选项,进入全部订单页面

  
  

单击修改密码选项,进入修改密码页面

  
  

 

 

 

1.3 商品

商品列表页面

首页是用户选购商品时的入口,用户可以选择首页轮播图、快讯、楼层中的商品,或者通过分类列表进入商品列表页或商品详情页。首页的分类导航菜单中选择了频道1中的“手机”→“手机通讯”→“手机”,单击三级分类,网站将跳转到商品列表页面

  
  

商品详情页面

商品列表页面只能看到商品的简略信息,单击列表或热销排行中的商品,可进入商品详情页面

 

  
  

 

 

商品搜索页面

 

 

1.4 购物车页面

用户可在详情页面选择商品数量与规格,单击“加入购物车”按钮,将选中的商品放入购物车。若商品成功加入购物车,当前页面的顶部会弹出如下图所示的提示弹框

 

  
  

 

 

悬浮购物车

小鱼商城有两个购物车入口,分别为导航栏的“我的购物车”,和部分页面搜索框右侧的“我的购物车”。搜索框右侧的“我的购物车”是一个简单购物车,鼠标移动到该位置,页面会呈现一个包含加购商品缩略信息的悬浮购物车

  
  

 

单击上述两个位置的“我的购物车”,都可进入购物车页面

 

 

1.5 结算页面

单击购物车页面的“去结算”按钮进入结算页面

  
  

用户可在结算页面选择收件地址和支付方式(货到付款或支付宝),并确认选购的商品信息。单击“提交订单”按钮提交订单,之后跳转到提交结果页面

  
  

1.6 支付页面

单击订单提交成功页面右下角的“去支付”按钮可进入支付页面。小鱼商城支持使用支付宝在线支付,实现此项功能需要对接支付宝提供的支付平台。示例网站已对接了支付宝提供的支付平台,单击“去支付”按钮后将跳转到支付宝的支付页面

  
  

2. 模块归纳

实际开发中,一个项目一般由多名开发人员协作完成。为了方便管理项目以及协同开发,人们会根据需求和功能将项目划分为不同的模块。利用Django框架开发网站时,可以按模块创建应用,这么做不仅可以降低项目的耦合度,也利于项目管理以及协同开发

  
  

 

3. 项目开发模式与运行机制

 

小鱼商城是一个涉及前端开发和后端开发的电商项目,在开发之前需要先确定项目的开发模式;项目会用到一些第三方服务和接口,确定使用哪些服务和接口,明确项目的运行机制,也是开发前的准备工作之一

 

  
  

小鱼商城项目的静态文件包括CSS文件、JS文件、图片文件;动态数据由Jinja2模板渲染,该服务由 Django程序提供。Django程序的后端提供了登录、状态缓存、短信、商品列表、搜索、购物车、订单、验证等业务,实现这些业务会涉及到数据存储服务、缓存服务。

Django后端在提供服务时会用到一些外部接口,如提供支付和订单查询功能的支付宝等。

  
  

 

 

4.项目创建与配置(项目原型搭建)

 

1) 创建xiaoyu_mall Django项目,且选择jinja2模板,默认创建 goods应用

  
  

 

配置Jinja2板

考虑到后续开发中还需要配置其他全局文件,为保证项目结构清晰,这里先在xiaoyu_mall目录下创建用于存放全局文件的包utils,之后创建模板引擎环境配置文件jinja2_env.py。

jinja2_env.py

 

 

 

  
  

*settings.py*

  
  

提示:

确保安装了django2.2.8

  
  

确保安装了jinja2

  
  

2) 配置MySQL数据库

项目拟采用MySQL存储商品数据、用户账户数据和订单等数据量较大、需持久化存储的数据,Django默认使用的数据库是sqlite3

  
  

新建mysql数据库xiaoyu,编码方式为utf8

  
  

配置**MySQL数据库**

 

  
  

提示:

安装pymysql

pip install pymysql

在 xiaoyu_mall 子文件夹中

添加代码

 

3) *配置前端静态文件*

(1) 准备静态文件

提供的静态文件存储在static文件夹中,将static文件夹直接复制到xiaoyu_mall目录下

 

  
  

 

 

(2) 指定静态文件加载路径

在配置文件settings.py中指定静态文件的加载路径,代码如下所示:

 

  
  

 

 

 

以上配置指定了静态文件的请求路径。Django项目接收到请求后根据路由前缀(/static/)判断请求的是否为静态文件,若是则直接在静态文件路径下找文件

配置完成后重启项目,在浏览器中访问http://127.0.0.1:8000/static/images/adv01.jpg,浏览器呈现的内容如下图所示

 

  
  

 

 

3)创建应用

以用户模块的应用users为例,创建应用的命令如下

 

  
  

 

 

 

按照以上命令为下面8个模块分别创建应用。

用户模块 :users

验证模块:verifications首页广告:contents

商品模块:goods

购物车模块:carts订单模块:orders 支付模块:payment

应用创建完成后,在**INSTALLED_APPS中安装应用,具体如下**

  
  

二、用户管理与验证

主要内容

定义用户模型类用户注册

用户登录用户中心

1. 定义用户模型类

 

用户在注册页面中按提示输入用户名、密码、确认密码、手机号、图形验证码、短信验证码信息,若输入的数据能通过校验,便可完成注册

 

  
  

 

 

虽然Django提供了用户模型类User,但内置User类不包含手机号字段,因此,为了满足存储小鱼商城用户信息的需求,需要自定义用户模型类

models.py

 

Django项目默认使用的User类是Django内置的User类,若想使用以上自定义的User类,还需修改全局配置项AUTH_USER_MODEL,使其指向自定义的User类。在settings.py文件配置 AUTH_USER_MODEL,具体如下

settings.py

  
  

数据库迁移

  
  

 

 

2. 用户注册

1) 用户注册逻辑分析

用户在注册页面填写注册信息时,前端会校验用户填写的每一条注册信息,若注册信息为空或不符合规则,相应信息文本框下会呈现错误信息,如下图所示

 

 

 

a) 前端已经实现的需求

 

  
  

 

 

b) 后端需要实现的需求

 

  
  

 

 

2) 用户注册后端基础需求的实现

用户发起注册请求后,小鱼商城调用后端定义的接口呈现注册页面,提供用户注册功能。下面分设计接口、定义接口、配置URL、渲染模板四个部分来实现用户注册的后端需求。

 

 

 

1. 接口设计

在设计用户注册接口时需明确请求方法、请求地址、请求参数以及响应结果。其中请求地址指用户在浏览器输入的URL;请求方法 指浏览器在向小鱼商城服务器发送请求或提交资源使用的不同方法,如用户访问注册页面,浏览器发送GET请求,小鱼商城后端使用get()方法处理该请求;请求参数指发送请求过程携带的参数;响应结果指服务器接收请求后返回的处理结果。

 

  
  

 

 

 

用户只有输入正确的图形验证码才可以获取短信验证码,图形验证码的校验应在发送短信之前完成,而发送短信功能由前端实现,因此后端不需要校验图形验证码

 

  
  

2. 定义接口

在users应用的views.py文件中定义处理用户注册请求的视图类RegisterView,在该视图中处理用户在使用注册功能时发起的GET请求与POST请求。

处理GET请求

当用户向后端发送GET请求时,类视图RegisterView调用get()方法处理GET请求,并返回register.html页面。

*users.views.py*

 

 

 

处理**POST请求**

注册数据通过前端校验后,用户单击“注册”按钮,浏览器会向后端发送POST请求,Django接收到注册页面发送来的POST请求,调用RegisterView类中的post()方法处理该请求

post()方法中对POST请求的处理可以分为四步:接收请求参数、校验请求参数、保存注册数据、返回注册结果

users.views.py

 

  
  

 

 

 

3. *配置路**由*

在小鱼商城项目的根urls.py文件中配置users应用的路由,具体如下:对一个应用,设置一个名称空间

 

 

 

在users应用中新建urls.py文件,配置子路由并定义命名空间,具体如下:

 

  
  

注册能的入口在首页的工具栏,我们在contents应用的views.py文件中定义呈现首页的视图类

IndexView,代码如下:

  
  

在根urls.py中配置contents应用的总路由并指定命名空间,代码如下:

  
  

在contents应用中新建urls.py文件,在其中配置子路由并定义命名空间,代码如下:

  
  

 

 

 

4. 渲染模板

如果用户注册失败,需要在register.html页面上渲染出注册失败的提示信息,具体代码如下

 

 

 

在index.html代码中设置注册页面的入口链接,代码如下:

 

  
  

 

 

 

*报错提示**:*

 

  
  

 

 

解决方案

更新utils包的 jinja2_env.py文件

 

  
  

 

 

 

5. 运行效果

 

 

 

  
  

 

 

 

至此,小鱼商城的注册功能完成。启动项目,访问小鱼商城首页,单击首页右上角的“注册”,便可进入注册页面注册用户。

3) 用户名与手机号唯一性校验

1. 用户名唯一性校验

用户输入的用户名通过前端的正则校验后,前端会向后端发送查询请求,后端在数据库中查询当前用户名并统计其在数据库中的数量。若数据库中当前用户名的数量为0,说明用户名不存在;若数量为1,说明用户名已存在,此时在前端渲染提示信息

用户名唯一性校验流程

 

  
  

 

 

(1) 定义接**口**

重复注册接口是对输入的用户名数量进行查询,请求方式为GET请求。查询用户名数量时需要将被查询的用户名作为路由参数传递到后端视图

 

选**项**方**案**
请求方法GET
请求地址/usernames/(?P[a-zA-Z0-9_-]{5,20})/count/

(2) 定义响应结果

后端需要向前端返回代表查询结果的状态码、错误信息、用户名个数,因为前端规定使用JSON格式响应数据,所以后端需要返回JSON格式的响应结果。

 

响应结**果**响应内**容**
code状态码
errmsg错误信息
count记录该用户名的个数

(3) 后端逻辑实现

检测用户是否重复注册的逻辑为:提取路由中的用户名,在数据库中查询该用户名对应的记录条数,响应查询结果。具体代码如下:

 

  
  

 

 

 

提示:

由于整个项目都使用response_code.py文件中的错误提示与状态码,我们需要将该文件拷贝到

xiaoyu_mall

/utils下

(4) 配置URL

在users应用的urls.py文件中定义检测用户名是否重复注册的路由,具体如下:

 

  
  

 

 

 

(5)前端逻辑实现

在register.js的check_username()方法中通过axios发送检测用户名是否重复注册的A JAX请求,具体代码如下:

 

  
  

 

 

 

(6) 效果展**示**

 

  
  

 

 

2. 手机号唯一性校验

(1) 设计接**口**

 

选**项**方**案**
请求方法GET
请求地址/mobiles/(?P1[3-9]\d{9})/count/

 

(2) 响应结**果**

 

响应结**果**响应内**容**
code状态码
errmsg错误信息
count记录该手机号的个数

(3) 后端逻辑实**现**

在users应用的views.py文件中定义用于处理手机号重复注册的MobileCountView视图,具体代码如下:

 

  
  

 

 

 

MobileCountView视图的get()方法实现了手机号重复的检测,为了调用该功能,我们还需要在users应用的urls.py文件中配置检测手机号重复的路由。

 

  
  

 

 

 

(4)前端逻辑实现

在register.js文件中的check_mobile ()方法中通过axios发送检测手机号是否重复注册的A JAX请求,具体代码如下:

 

  
  

 

 

 

3. 用户登录

处理登录时后端首先会接收前端发送的请求参数并校验参数的格式与唯一性,若校验通过,查询数据库中中的数据,校验登录信息的正确性。用户通过认证后后端需实现状态保持,并将Session数据保 存到Redis数据库中,最后返回登录结果。

 

 

 

1) 使用用户名登录

1. 设计接口

用户访问登录页面时浏览器向后端发送GET请求,用户提交登录表单中的用户名与密码时浏览器会向后端发送POST请求;用户登录请求地址可设计为/login/。

2. 请求参数

用户请求登录时,后端需要获取用户输入的用户名、密码和是否记住用户名等信息,其中是否记住用户名为非必传参数。用户登录功能涉及的请求参数的类型及说明如下表所示。

 

 

 

 

 

 

字**段**类**型**是否必**传**说**明**
usernamestr用户名
passwordstr密码
rememberedstr是否记住用户名

3. 响应结果

用户登录成功后会重定向到小鱼商城首页,用户登录失败后会在登录页面中显示错误提示

4. 接口定义

get()方法用于接收GET请求,并向用户响应登录页面;post()方法接收到POST请求后首先会接收并校验前端发送的参数,若校验未通过,则向前端响应对应的错误提示,若校验通过则使用Django内置的认证系统进行用户认证,若通过认证表示登录成功,页面跳转到小鱼商城首页,若认证失败则响应对应的错误提示。Views.py文件中LoginView视图的代码具体如下。

 

  
  

 

 

 

 

 

users应用的路由设置

 

  
  

 

 

 

首页index.html 登录跳转设置

 

  
  

拷贝素材中的login.html到templates模板页面中

2)状态保持session mysql数据库中

用户登录后,如果希望登录状态保持一段时间,那么需要实现状态保持;用户完成注册后,应自动登录网站,所以小鱼商城的用户注册和用户登录功能都需要实现状态保持

Django认证系统提供的login()函数封装了写入Session数据的操作,本项目把状态数据保存到redis缓存数据库中

1 redis数据库安装与配置

#####

 

  
  

 

 

配置redis数据库

 

安装django-redis扩展包

pip install django-redis

在配置文件settings.py中添加如下配置信息:

 

  
  

 

 

 

以上配置信息通过SESSION_ENGINE选项指定使用Redis存储session数据;

通过SESSION_CACHE_ALIAS选项指定使用名为session的Redis配置项存储session数据;

session配置项中设置Redis默认使用0号库,而使用1号库存储session数据

登录**redis**

 

  
  

 

 

2 功能的状态保持

我们可以通过在RegisterView视图的post()方法中新增调用login()函数的代码,实现状态保持,具体代码如下

 

 

 

启动小鱼商城服务器,进入到index.html页面,点击Chrome浏览器地址栏中的“查看网站信息”可查看

Cookie中存储的信息,如下图所示。

 

  
  

 

 

在Redis数据库(1号库)可查询到保存的sessionid,如下图所示

 

  
  

 

 

由在Cookie和Redis中观察到的信息和数据可知,当前用户信息被成功存储,状态保持实现成功

3.记住登录

用户登录时可选择是否“记住登录”,如果用户勾选“记住登录”,请求对象request会在Session中设置用户信息过期时间,只要在有效期内,用户再次访问网站时无需重新登录,否则用户再次访问网站时需要重新登录。

记住登录的基本逻辑是根据“记住登录”的勾选状态进行判断,如果用户勾选,那么默认设置的过期时间为14天,如果未勾选,则浏览器会话结束后,用户登录信息就会过期。

下面在LoginView视图中实现记住登录功能,具体如下:

 

  
  

 

 

 

实现状态保持与用户登录后,当用户在登录页面勾选“记住登录”后,下次访问小鱼商城主页时就不需要再进行登录操作。

3) 首页展示用户名

用户成功登录后,首页会展示登录用户的用户名,如下图所示

 

  
  

 

 

实现以上效果的原理是:用户成功登录后,用户名会被存储到Cookie中,每当页面发生跳转时,Vue将从Cookie中读取用户信息,渲染到页面之上。为实现以上功能,需分别在前端和后端补充代码。

1. 补充前端代码

在index.html文件中找到类名为“fr”的div盒子,在其中补充如下代码:

 

  
  

 

 

 

2. 补充后端代码

为了保证前端能从Cookie中读取用户信息,注册视图RegisterView与登录视图Loginview在返回响应之前,需要先将用户名保存到Cookie中。修改RegisterView视图和LoginView视图中的代码,修改后的代码具体如下。

 

  
  

 

 

 

当用户再次登录或刷新当前页面后,在首页的右上角会显示当前登录用户的用户名。

4) 退出登录

退出登录本质上是清除服务端的Session信息和客户端Cookie中的用户名。在users应用中的urls.py追加退出登录路由,具体如下。

 

  
  

 

 

 

在users应用中的views.py中定义实现退出登录功能的视图LogoutView,具体如下。

 

 

 

在index.html中配置退出登录的反向解析,以便小鱼商城用户退出后浏览器可会跳转到商城首页,具体如下。

 

  
  

 

 

 

4 用户中心

 

1) 用户基本信息

 

  
  

 

 

在小鱼商城中,只有登录成功的用户才可以访问用户中心,因此在处理访问请求时需要先判断用户是否已经成功登录:如果用户登录成功,则可以正常跳转到用户中心页面;如果用户未登录则应先跳转到登录页面,用户需先登录,登录成功后再跳转到用户中心页面中

Django内置了限制用户访问功能的LoginRequiredMixin类,该类继承AccessMixin类,通过类属性 login_url和redirect_field_name可以设置未登录时的重定向地址和登录时的默认重定向地址,以实现上述功能

1. 未登录重定向地址

类属性login_url默认值为None,其值可通过类方法get_login_url()中的变量login_url进行设置。在类方法get_login_url()中变量login_url通过类属性login_url或配置文件settings.py中LOGIN_URL进行赋值。我们可以在小鱼商城项目的settings.py文件中设置重定向地址。具体如下

 

  
  

 

 

 

2. 登陆后后重定向地址

 

类属性redirect_field_name默认值为“next”,该值保存了用户验证成功时跳转的访问地址,通过它可设置重定向登录后的访问地址。在LoginView视图中判断当前URL中是否包含next,如果包含重定向到 next指定的页面,否则重定向到首页。具体如下。

 

  
  

 

 

 

用户限制访问实现之后,在users应用的urls.py文件中定义访问用户中心的路由,具体如下:

 

  
  

 

 

 

在users应用的views.py中定义用于处理用户中心的类视图UserInfoView,具体如下:

 

  
  

 

 

 

用户中心页面中展示的用户名、联系方式可在UserInfoView视图的request对象获取并通过context上下文传递到模板中,具体如下

 

  
  

 

 

 

在首页中设置用户中心的跳转

 

  
  

 

 

 

为了保证数据能够在前端页面中正确渲染,需要对user_center_info.html页面中的用户名、联系方式、

Email和验证状态进行双向绑定,具体如下。

 

 

 

且在页面绑定变量

 

  
  

 

 

 

再次运行小鱼商城项目刷新user_center_info.html页面,此时页面会显示当前用户的基本信息,如下图所示。

三、商品数据呈现

电商平台呈现商品的方式会直接影响消费者行为。小鱼商城与商品相关的页面很多,包括首页、商品列表页、商品详情页等。

学习内容:

商品数据库表设计准备商品数据

呈现首页数据商品列表

商品搜索商品详情

用户浏览记录

1. 商品数据库表设计

 

1) 商品广告数据表设计

首页的轮播图、快讯、页头和楼层都是广告,且都需要存储到数据库中;虽然目前首页涉及的广告只有

4种,但为了后期网站升级考虑,网站的类别也需要单独的数据表来存储

 

  
  

 

 

 

2) 商品数据表设计

小鱼商城中的商品种类繁多,为了便于组织数据,考虑将商品分类信息和商品信息分开存储

小鱼商城的首页包含一个商品导航栏,为了有层次地呈现商品分类,该导航栏中的分类信息被分成了三个级别:商品频道组、商品频道和商品类别

鱼商城涉及的商品类别多达几百种,用户很难迅速从几百种分类中快速找到自己所需的分类,为方便用户快速定位,考虑对商品类别进行划分:从几百个商品类别中抽出小部分类别,将其作为二级分类—— 商品频道,再将商品频道划分为多个组,最后仅在页面呈现商品频道组,并实现逐级导航的效果

 

  
  

 

 

电子商务中在定义商品时通常会用到两个重要概念:SPU和SKU

 

  
  

 

 

 

商品品牌、商品SPU、商品SKU依次为一对多关系,为了保证平台的可持续发展,方便管理商家、商品

SPU、商品SKU,这里分三张表存储商品品牌、商品**SPU和商品SKU数据**。

 

  
  

 

 

小鱼商城商品的SPU、SKU与规格和其他信息间的对应关系如下

 

 

考虑将前面对应关系中的两种对象存储在不同的表中,分析对应关系后还需要新建的数据表有:SPU规 格表、SKU规格表、SKU图片表、规格选项表。

 

  
  

 

 

综合考虑商品类别与商品,每个类别对应多种**SPU,同时也对应多个SKU**

 

  
  

 

 

2准备商品数据

 

导入商品数据之前需要先创建数据库表。利用Django的ORM机制可以通过定义模型类与生成迁移便捷 地创建数据模型对应的表。

 

 

 

1)创建模型

 

  
  

 

 

contents.models.py

 

  
  

 

 

 

 

 

goods.models.py

 

  
  

 

 

 

group = models.ForeignKey(GoodsChannelGroup, verbose_name='频道组名', on_delete=models.CASCADE)

category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别')

url = models.CharField(max_length=50, verbose_name='频道页面链接')

sequence = models.IntegerField(verbose_name='组内顺序')

 

 

class Meta:

db_table = 'tb_goods_channel' verbose_name = '商品频道' verbose_name_plural = verbose_name

 

 

def

str (self):

return self.category.name

 

 

 

 

class Brand(BaseModel): """品牌"""

name = models.CharField(max_length=20, verbose_name='名称')

logo = models.ImageField(verbose_name='Logo图片')

first_letter = models.CharField(max_length=1, verbose_name='品牌首字母')

 

 

class Meta:

db_table = 'tb_brand' verbose_name = '品牌'

verbose_name_plural = verbose_name

 

 

def

str (self): return self.name

 

 

 

class SPU(BaseModel): """商品SPU"""

name = models.CharField(max_length=50, verbose_name='名称')

brand = models.ForeignKey(Brand, on_delete=models.PROTECT, verbose_name='品

牌')

category1 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT,

related_name='cat1_spu', verbose_name='一级类别')

category2 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat2_spu', verbose_name='二级类别')

category3 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat3_spu', verbose_name='三级类别')

sales = models.IntegerField(default=0, verbose_name='销量')

comments = models.IntegerField(default=0, verbose_name='评价数') desc_detail = models.TextField(default='', verbose_name='详细介绍') desc_pack = models.TextField(default='', verbose_name='包装信息') desc_service = models.TextField(default='', verbose_name='售后服务')

 

class Meta:

db_table = 'tb_spu' verbose_name = '商品SPU'

verbose_name_plural = verbose_name

 

 

def

str (self): return self.name

 

 

 

class SKU(BaseModel):

 

"""商品SKU"""

name = models.CharField(max_length=50, verbose_name='名称') caption = models.CharField(max_length=100, verbose_name='副标题')

spu = models.ForeignKey(SPU, on_delete=models.CASCADE, verbose_name='商品') category = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT,

verbose_name='从属类别')

price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')

cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='进价')

market_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='市场价')

stock = models.IntegerField(default=0, verbose_name='库存')

sales = models.IntegerField(default=0, verbose_name='销量') comments = models.IntegerField(default=0, verbose_name='评价数')

is_launched = models.BooleanField(default=True, verbose_name='是否上架销售') default_image = models.ImageField(max_length=200, default='', null=True,

blank=True, verbose_name='默认图片')

 

 

class Meta:

db_table = 'tb_sku' verbose_name = '商品SKU'

verbose_name_plural = verbose_name

 

 

def

str (self):

return '%s: %s' % (self.id, self.name)

 

 

 

 

class SKUImage(BaseModel): """SKU图片"""

sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku') image = models.ImageField(verbose_name='图片')

 

class Meta:

db_table = 'tb_sku_image' verbose_name = 'SKU图片' verbose_name_plural = verbose_name

 

 

def

str (self):

return '%s %s' % (self.sku.name, self.id)

 

 

 

 

class SPUSpecification(BaseModel): """商品SPU规格"""

spu = models.ForeignKey(SPU, on_delete=models.CASCADE, related_name='specs', verbose_name='商品SPU')

name = models.CharField(max_length=20, verbose_name='规格名称')

 

 

class Meta:

db_table = 'tb_spu_specification' verbose_name = '商品SPU规格' verbose_name_plural = verbose_name

 

 

def

str (self):

return '%s: %s' % (self.spu.name, self.name)

 

 

 

 

class SpecificationOption(BaseModel):

 

 

 

在**utils包中创建models.py模块**

 

  
  

 

 

 

2) 数据库迁移

python manage.py makemigrations python manage.py migrate

3) 准备商品数据

商品数据分为商品信息数据和商品图片数据

 

 

 

4) 录入商品数据

 

  
  

 

 

至此商品数据准备完毕。

 

  
  

 

 

*3* *呈现首页数据*

 

1)呈现首页商品分类

 

 

 

首页的商品分类部分呈现11个频道组,每个频道组中包含3~4个频道;

频道组中的每个频道对应一些分类,鼠标放置在频道组时,呈现频道组中所有频道对应的商品分类;

频道组对应的商品分类分为两级,每个分类又细分为多个子类。

总的来说,商品分类中的频道组(与频道)、频道分类以及子类互相关联,首页的商品分类实现了三级 联动,同时对频道进行了分组。

 

商品分类在后端的组织形式(以JSON为例)

 

  
  

 

 

 

 

 

  
  

 

 

 

考虑到通过商品频道可以相对方便地查询商品频道组和商品类别,这里将在contents应用中根据商品频道组查询首页商品分类,为此需要在contents的views.py文件中先导入模型类 GoodsChannel。

由于呈现在页面中的商品分类信息是有序的,但字典本身无序,所以在后端代码中定义存储分类数据的字典时,需要导入collections模块中的有序字典OrderedDict。

 

  
  

 

 

 

下面实现商品分类查询,代码如下:

 

  
  

 

 

 

 

 

后端代码将商品分类信息放在上下文字典中传递给模板文件,模板文件可以通过键categories读取到商品分类信息。修改模板文件index.html中的代码,修改的部分代码如下:

 

  
  

 

 

 

重新启动项目,访问站点http://127.0.0.1:8000/,查看首页的商品分类,若呈现的效果如下图所示,说明项目配置成功。

 

  
  

 

 

*2)* *呈现首页商品广告*

当前小鱼商城首页的广告数据都是在模板文件中以硬代码形式编写的数据,实际应用中,页面上呈现的数据存储在数据库中,开发人员需要编写代码从数据库中读取数据,在页面中渲染从数据库中读取的数 据

以JSON形式展示广告数据,具体如下所示:

 

  
  

 

 

编写代码,查询首页商品广告,代码如下:

 

  
  

 

 

 

 

 

后端代码将首页商品广告信息放在上下文字典中传递给模板文件,模板文件可以通过键contents读取到首页商品广告信息

轮播图广告

 

  
  

 

 

 

以上代码在循环中遍历contents的轮播图广告,取出数据库中存储的图片文件名,将其和文件资源路径/static/images/goods/及后缀.jpg拼接成为图片资源,传递给前端页面。

快讯和页头广告

 

  
  

 

 

 

一楼楼层广告

 

  
  

 

​​

{% for content in contents.index_1f_pd %}

{{ content.title }}

{% endfor %}

</div>

​​

{% for content in contents.index_1f_bq %}

{{ content.title }}

{% endfor %}

</div>

</div>

​​

<ul v-show="f1_tab===1" class="goods_list fl">

{% for content in contents.index_1f_ssxp %}

<li>

<a href="{{ content.url }}" class="goods_pic">

 

<img

content.image.url }}.jpg"></a>

src="/static/images/goods/{{

​​

content.title }}">{{ content.title }}</a></h4>

{{ content.text }}

</li>

{% endfor %}

</ul>

<ul v-show="f1_tab===2" class="goods_list fl">

{% for content in contents.index_1f_cxdj %}

<li>

<a href="{{ content.url }}" class="goods_pic">

<img

content.image.url }}.jpg"></a>

src="/static/images/goods/{{

​​

content.title }}">{{ content.title }}</a></h4>

{{ content.text }}

</li>

{% endfor %}

</ul>

<ul v-show="f1_tab===3" class="goods_list fl">

{% for content in contents.index_1f_sjpj %}

<li>

<a href="{{ content.url }}" class="goods_pic">

<img

content.image.url }}.jpg"></a>

src="/static/images/goods/{{

​​

![img](file:///C:\Users\Administrator\AppData\Local\Temp\ksohtml11020\wps189.pngcontent.title }}">{{ content.title }}</a></h4>

{{ content.text }}

</li>

{% endfor %}

</ul>

</div>

</div>

</div>

4商品列表

 

1) 商品列表页面分析

  
  

1. 商品列表页功能分析

 

  
  

2 商品列表页接口设计和定义

(1)请求方式

从首页跳转到商品列表页面是纯粹的数据获取,不涉及数据提交,因此请求方法为GET

  
  

请求地址由关键字list、类别id、页码和排序方式组成,将其归纳为正则表达式,具体如下:

/list/(?P<category_id>\d+)/(?P<page_num>\d+)/?sort=排序方式

参数**名**类**型**是否必**传**说**明**
category_idstring商品分类id,第三级分类
page_numstring当前页面
sortstring排序方式

(2) 响应结果

商品列表页通过新的模板文件list.html呈现,程序需将该文件作为响应结果返回。

(3) 接口定义

在goods应用的views.py文件中定义类ListView,该类的get()方法将处理商品列表页的请求。get()方法接收首页或当前列表页面传递来的参数category_id、page_num和sort,对参数进行校验和处理,返回响应。

  
  

(4)路由设置

根路由设置:

 

  
  

 

 

 

 

  
  

 

 

 

2)呈现商品列表

商品列表的呈现包含获取商品列表、排序和分页三个功能。

 

  
  

 

 

 

商品列表中的每一个数据是一个SKU,商品类别与SKU之间存在一对多关系;

前面分析商品列表页时定义了ListView类的get()方法,该方法接收前端页面传递来的商品类别的id—— category_id,并通过该参数查询了商品类别

 

  
  

 

 

 

 

这里可以通过商品类别与SKU之间的一对多关系,从数据库中查询出当前类别下所有上架的SKU。查询代码如下:

 

  
  

 

 

 

以上代码利用filter()方法而非all()方法进行查询,这是因为商品有个记录状态的字段is_launched,该字段为True时表示商品在售,为False时表示商品已下架。商品列表中应只展示在售的商品,所以这里需要使用filter()获取过滤后的结果。

商品列表页的排序方式分为默认(按商品的创建日期)、按价格(由高到低)排序和按人气(销量由高到低)排序。代码中可通过request对象GET字段中的数据获取排序方式,示例如下:

 

  
  

 

 

 

以上代码用于接收sort参数,该参数有price和hot两种取值,即按照价格或人气排序。若用户未传入

sort参数,使用默认的排序规则,即根据商品创建日期排序

之后根据排序方式设置SKU表的排序字段,再根据排序字段对查询到的商品列表进行排序。

 

 

 

实现分页功能需要使用django.core.paginator模块的Paginator类,该类的构造方法可接收对象列表和每页的对象数量,返回分页后的数据。

 

 

  
  

 

 

 

获取商品类别数据及数据传递

 

  
  

 

 

 

在goods创建一个utils.py模块,且编写get_categories()方法

 

  
  

 

 

 

 

 

 

 

界面实现

商品分类界面设置

 

  
  

 

 

 

 

 

排序方式高亮

 

  
  

 

 

 

分类商品列表显示

 

  
  

 

 

 

前端分页器插件内容

 

  
  

 

 

 

 

3)列表页热销排行 手机 :

 

热销排行显示当前类别销量最高、未下架的两种商品,因此应从SKU表中根据category_id查询商品、将筛选出的商品按销量由高到低排序,再使用切片取出排名第一、第二的两个商品。在商品销售火爆的情况下,热销商品频繁变动,热销排行应实时刷新,但商品列表一般情况下不会变化,因此热销排行需要局部刷新。实现局部刷新需要前端发送A JAX请求,后端返回JSON数据。

热销商品的类别应与当前列表页的类别相同,前端页面以GET方式发送A JAX请求,并利用category_id传递商品类别。ListView类的get()方法应将category_id放在上下文中传递给模板。

定义Ajax请求地址:

 

  
  

 

 

 

Json数据包含的字段

 

字**段**说**明**
code状态码
errmsg错误信息
hot_skus热销SKU列表
idSKU编号
default_image_url商品默认图片
name商品名称
price商品价格

热销数据结构

 

  
  

 

 

 

路由:

 

 

 

views.py

 

  
  

 

 

 

页面

 

  
  

 

 

 

数据双向绑定

 

  
  

 

4) 商品列表没有数据原因及解决办法

查询sku表,得知 只有笔记本、手机类别 有商品数据 , 所有 只有查看 笔记本和 手机 类别 才能看到商品列表

 

 

但是 当显示笔记本或者手机类别 的sku商品数据时,会报错

  
  

解决办法

在goods.urls.py文件中,添加路由

  
  

在goods.views.py文件中,添加DetailView类视图

  
  

 

 

四、商品详情

 

1) 分析与准备商品详情页

 

商品详情页呈现一类商品的详情信息和本类商品中某个SKU的图片、名字、副标题、规格等信息。例如单击商品列表页中金色、64GB的iPhone 8 Plus,商品详情页呈现iPhone 8 Plus的信息,和金色

iPhone 8 Plus的图片、标题等,默认选中的规格为金色、64GB。

商品详情页必须知道当前需要渲染的是哪个SKU,这就要求在用户单击商品发送请求的同时,将当前商品的sku_id传递到商品详情页,另外由于发生了页面跳转,需要配置URL,这里设置请求地址为:

  
  

请求商品详情页时需要获取数据,请求方式为GET;商品详情页通过模板文件detail_org.html呈现。在

goods/views.py中定义接口,代码如下:

  
  

goods.urls.py

  
  

2) 呈现商品详情数据

 

商品详情页默认呈现跳转之前所选择的商品的图片、名称、副标题以及规格选项等内容,地址栏中为

detail/sku_id

  
  

商品在数据库中的sku_id为3,此时商品详情页地址为“detail/3

该页面展示了金色iPhone 8P的图片、名称、副标题,以及iPhone 8P的所有规格,同时选中3号商品的规格“金色”“64GB”

用户可以在详情页选择商品的数量、规格,当数量改变时,总价同步改变;当选择不同的规格时,地址栏的sku_id、商品的图片、名称、副标题、价格和总价都需要改变。但一个SPU对应一组商品详情、规格与包装、售后服务信息,规格改变不影响这些信息。

![img](file:///C:\Users\Administrator\AppData\Local\Temp\ksohtml11020\wps234.pngDetailView(View): """商品详情页"""

def get(self, request, sku_id): """提供商品详情页"""

# 获取当前sku的信息

try:

sku = SKU.objects.get(id=sku_id) except SKU.DoesNotExist:

return render(request, '404.html')

# 查询商品频道分类

categories = get_categories() # 查询面包屑导航

breadcrumb = get_breadcrumb(sku.category)

# 构建当前商品的规格键

sku_specs = sku.specs.order_by('spec_id') sku_key = []

for spec in sku_specs: sku_key.append(spec.option.id)

# 获取当前商品的所有SKU

skus = sku.spu.sku_set.all() # 构建不同规格参数(选项)的sku字典 spec_sku_map = {}

for s in skus:

# 获取sku的规格参数

s_specs = s.specs.order_by('spec_id') # 用于形成规格参数-sku字典的键

key = []

for spec in s_specs: key.append(spec.option.id)

# 向规格参数-sku字典添加记录

spec_sku_map[tuple(key)] = s.id # 获取当前商品的规格信息

goods_specs = sku.spu.specs.order_by('id') # 若当前sku的规格信息不完整,则不再继续

if len(sku_key) < len(goods_specs): return

for index, spec in enumerate(goods_specs): # 复制当前sku的规格键

key = sku_key[:] # 该规格的选项

spec_options = spec.options.all() for option in spec_options:

# 在规格参数sku字典中查找符合当前规格的sku

key[index] = option.id

option.sku_id = spec_sku_map.get(tuple(key)) spec.spec_options = spec_options

# 渲染页面

context = {

'categories': categories, 'breadcrumb': breadcrumb, 'sku': sku,

'specs': goods_specs,

# 商品数量

 

面包屑导航

  
  

五、GIT代码版本控制系统

 

1. git特点与优势

 

1) 分布式,强调个体,不需要联网可在本地记录变化,即本地提交版本

2) 公共服务器压力和数据量都不会太大

3) 速度快、灵活

4) 可以离线工作

 

  
  

 

 

 

2. git服务端搭建

 

1) 通过gitblit搭建本地git服务器:

2) 通过第三方代码托管平台 Github(GitHub: Let’s build from here · GitHub) gitee(Gitee - 企业级 DevOps 研发效能平台)

阿里云(阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台)

华为云等

3. git客户端安装

1)先安装git 客户端软件

  
  

2) 再安装git客户端的外壳:

pycharm 集成

  
  

tortoiseGit sourceTree smartGit

4. 使用git版本控制系统实现协同开发

git 管理人员

git 协同开发成员

 

 

 

1) 作为git版本控制管理员,涉及的操作

\1. 在gitee创建仓库(使用第三方平台www.gitee.com作为git的代码托管平台)

\2. 添加用户,设置权限(管理者、开发者、观察者、报告者)

\3. 选择master分支,克隆码云的git地址到本地

git clone takeout: 外卖项目

\4. 通过pycharm 创建初始版本

\5. 拷贝克隆到本地的git 文件夹 到 pycharm 项目中(初始版本)

\6. 把初始代码纳入版本库

\7. 添加、配置忽略文件

注意:配置忽略文件须在提交代码之前配置

\8. 代码提交与更新

commit:提交 :提交到本地 push : 推送到服务端 commit/push :提交与推送

pull : 从服务端拉取数据,更新代码

clone: 克隆, 从服务端克隆数据到本地

2) 作为基于git的协同开发成员,涉及的操作

  
  

git clone takeout: 外卖项目

  
  

 

假如需要配置用户名和邮箱,则输入如下命令:

  
  

\2. fetch -->origin: 从远程抓取数据,得知是否有新数据

- 假如为空,表示没有更新

- 有更新,合并远程的最新代码到本地

Merge: 合并代码

\3. 通过fetch/merge 或者pull 都可获取服务端最新代码

\4. commit:提交到本地服务端

\5. push: 推送到远程服务端

3) git冲突与解决

\1. 用别人的代码

\2. 用自己的代码

\3. 自行解决冲突代码

使用冲突工具 ,merge tool

手动解决冲突

删除不满足语法的箭头协商后,删除代码

获取gitee仓库中 小鱼商城的代码

git clone xiaoyu_mall: 小鱼商城的git 仓库

 

  
  

 

 

六、Linux项目部署

 

1. Nginx+uWSGI介绍

 

uWSGI是一个Web服务器,它实现了WSGI协议、uwsgi、http等协议。Nginx中HttpUwsgiModule的 作用是与 uWSGI服务器进行交换。 要注意 WSGI / uwsgi / uWSGI 这三个概念的区分。 WSGI是一种通信协议。 uwsgi是一 种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信。 而uWSGI是实现了uwsgi 和WSGI两种协议的Web服务器。

Nginx 是一个高性能的HTTP和反向代理服务器. Nginx与uWSGI交互流程

 

 

 

首先客户端请求服务资源,

1、nginx作为直接对外的服务接口,接收到客户端发送过来的http请求,会解包、分析,

2、如果是静态文件请求就根据nginx配置的静态文件目录,返回请求的资源,

3、如果是动态的请求,nginx就通过配置文件,将请求传递给uWSGI;uWSGI 将接收到的包进行处理,并转发给 wsgi,

4、wsgi根据请求调用django工程的某个文件或函数,处理完后django将返回值交给wsgi, 5、wsgi将返回值进行打包,转发给uWSGI,

6、uWSGI接收后转发给nginx,nginx最终将返回值返回给客户端(如浏览器)。

uwsgi需要将过来的请求转给django 处理,那么uwsgi 和 django的交互和调用就需要一个统一的规范,这个规范

就是WSGI WSGI(Web Server Gateway Interface) ,WSGI是 Python PEP333中提出的一个 Web 开发统一规 范。

Web 应用的开发通常都会涉及到 Web 框架(django, flask)的使用,各个 Web 框架内部由于实现不同相互不兼 容,给用户的学习,使用和部署造成了很多麻烦。正是有了WSGI这个规范,它约定了wsgi server 怎么调用web应 用程序的代码,web 应用程序需要符合什么样的规范,只要 web 应用程序和 wsgi server 都遵守 WSGI 协议,那 么,web 应用程序和 wsgi server就可以随意的组合。

2. 部署前准备

在部署之前需要将setting.py中的调试开关关闭

  
  

将开发环境下使用的包导出保存到文件 requirements.txt

  
  

将整个项目包括requirements.txt 文件一起上传到linux服务器 (demo项目)

删除迁移文件,将测试时生成的迁移文件删除,否则无法再次进行迁移

3. 安装与配置uWSGI

 

特别提示:

Centos8安装docker遇到的问题:CentOS Linux 8 - AppStream 错误:为仓库 AppStream下载元数据失败_LallanaLee的博客-CSDN博客

修改centos仓库镜像地址

  
  

升级到stream版本

  
  

安装uWSGI:

  
  

 

 

uWSGI配置文件: uwsgi.ini

在/etc目录中创建uwsgi.d目录,在该目录下创建配置文件 uwsgi.ini

  
  

启动

 

#测试

进程启动是否成功:

  
  

 

http://127.0.0.1:8080/register

  
  

如运行有问题,查看/etc/uwsgi.d/uwsgi.log文件,查看报错原因需要安装第三方包:

  
  

测试没问题,将配置中启用socket,禁用http,重启uwsgi

注意:启用uwsgi后,修改视图或者其他配置都需要重启uwsgi.

4. 安装与配置Nginx

 

1. 安装nginx

命令: yum install -y nginx

 

配置文件: /etc/nginx/nginx.conf

启动: nginx

停止: nginx -s stop

查看进程: ps -aux | grep nginx

测试: 127.0.0.1

 

  
  

 

 

2. 配置nginx

对于处理http协议的请求,主要配置三个节点

http:表示所有的http请求的处理 server:监听端口,绑定服务器

location:匹配请求路径,转到相应的处理

配置文件: /etc/nginx/nginx.conf

 

  
  

 

测试配置是否**ok** : nginx -t

Ngnix更多配置及详细解释参考 配置参考配置好后重启Ngnix 和uwsgi

所有访问本机的80端口的http数据静态文件由Ngnix返回,动态数据转发给uwsgi处理。

\3. 访问**nginx+uwsgi**

 

  
  

 

 

5. 安装与配置mysql

 

1. 安装mysql

sudo dnf install @mysql

2. 设置开机自启动

sudo systemctl enable --now mysqld

\3. 设置**mysql安全认证** (root密码) sudo mysql_secure_installation

4. 启动mysql

systemctl start mysqld systemctl status mysqld ps -aux | grep mysql

5. 登录mysql

 

  
  

 

 

\6. 在**mysql中创建** demodb**数据库**

create database demodb charset=utf8;

7. 数据库迁移

 

pip3 install pymysql

 

  
  

python3 manage.py migrate

 

 

 

6. 测试demo项目

 

 

启动uwsgi

uwsgi --ini /etc/uwsgi.d/uwsgi.ini uwsgi --stop /etc/uwsgi.d/uwsgi.pid

启动nginx

nginx

nginx -s stop

查看ip,停止centos防火墙,打开浏览器

ifconfig、systemctl stop firewalld

 

 

 

提示:假如windows 还访问不了web,可能是网卡配置问题

(/etc/sysconfig/network-scripts/ifcfg-ens33)

 

 

 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欧阳文博

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值