DAY 85 luffy04

1 celery,分布式异步任务框架,异步,定时,延迟任务
-APScheduler
2 包管理方式
-在项目根路径建立一个包
   -celery.py --->把celery的对象实例化出来,本地化,定时任务
   -写很多task文件
   -在使用的位置,导入,调用
  -任务(函数).delay()
   -启动worker
   -启动beat:如果有定时任务
   -任务.apply_async(args=[参数],时间对象)
   
3 定时更新缓存的任务(异步更新)
-mysql和redis双写一致性
  -定时更新
       -新增一条直接删除缓存(更新缓存)
       -先删缓存,再存数据
  -celery使用django,必须让djagno运行
3 课程相关表分析
-课程分类表
   -课程表
   -老师表
   -章节表
   -课时表
4 复制课程列表页面
5 查询所有分类的接口
6 查询所有课程接口
-加入分页
   -加入排序
   -过滤:课程分类过滤
   -区间过滤

 

1 课程列表前端

<template>
   <div class="course">
       <Header></Header>
       <div class="main">
           <!-- 筛选条件 -->
           <div class="condition">
               <ul class="cate-list">
                   <li class="title">课程分类:</li>
                   <li :class="filter.course_category==0?'this':''" @click="filter.course_category=0">全部</li>
                   <li :class="filter.course_category==category.id?'this':''" v-for="category in category_list"
                       @click="filter.course_category=category.id" :key="category.name">{{category.name}}
                   </li>
               </ul>

               <div class="ordering">
                   <ul>
                       <li class="title">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</li>
                       <li class="default" :class="(filter.ordering=='id' || filter.ordering=='-id')?'this':''"
                           @click="filter.ordering='-id'">默认
                       </li>
                       <li class="hot" :class="(filter.ordering=='students' || filter.ordering=='-students')?'this':''"
                           @click="filter.ordering=(filter.ordering=='-students'?'students':'-students')">人气
                       </li>
                       <li class="price"
                          :class="filter.ordering=='price'?'price_up this':(filter.ordering=='-price'?'price_down this':'')"
                           @click="filter.ordering=(filter.ordering=='-price'?'price':'-price')">价格
                       </li>
                   </ul>
                   <p class="condition-result">共{{course_total}}个课程</p>
               </div>

           </div>
           <!-- 课程列表 -->
           <div class="course-list">
               <div class="course-item" v-for="course in course_list" :key="course.name">
                   <div class="course-image">
                       <img :src="course.course_img" alt="">
                   </div>
                   <div class="course-info">
                       <h3>
                           <router-link :to="'/actualcourse/detail/'+course.id">{{course.name}}</router-link>
                           <span><img src="@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习</span></h3>
                       <p class="teather-info">
                          {{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}
                           <span v-if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时</span>
                           <span v-else>共{{course.sections}}课时/更新完成</span>
                       </p>
                       <ul class="section-list">
                           <li v-for="(section, key) in course.section_list" :key="section.name"><span
                                   class="section-title">0{{key+1}}  | {{section.name}}</span>
                               <span class="free" v-if="section.free_trail">免费</span></li>
                       </ul>
                       <div class="pay-box">
                           <div v-if="course.discount_type">
                               <span class="discount-type">{{course.discount_type}}</span>
                               <span class="discount-price">¥{{course.real_price}}</span>
                               <span class="original-price">原价:{{course.price}}</span>
                           </div>
                           <span v-else class="discount-price">¥{{course.price}}</span>
                           <span class="buy-now">立即购买</span>
                       </div>
                   </div>
               </div>
           </div>
           <div class="course_pagination block">
               <el-pagination
                       @size-change="handleSizeChange"
                       @current-change="handleCurrentChange"
                      :current-page.sync="filter.page"
                      :page-sizes="[2, 3, 5, 10]"
                      :page-size="filter.page_size"
                       layout="sizes, prev, pager, next"
                      :total="course_total">
               </el-pagination>
           </div>
       </div>
       <Footer></Footer>
   </div>
</template>

<script>
   import Header from "../components/Header"
   import Footer from "../components/Footer"

   export default {
       name: "ActualCourse",
       data() {
           return {
               category_list: [], // 课程分类列表
               course_list: [],   // 课程列表
               course_total: 0,   // 当前课程的总数量
               filter: {
                   course_category: 0, // 当前用户选择的课程分类,刚进入页面默认为全部,值为0
                   ordering: "-id",    // 数据的排序方式,默认值是-id,表示对于id进行降序排列
                   page_size: 2,       // 单页数据量
                   page: 1,
              }
          }
      },
       created() {
           this.get_category();
           this.get_course();
      },
       components: {
           Header,
           Footer,
      },
       watch: {
           "filter.course_category": function () {
               this.filter.page = 1;
               this.get_course();
          },
           "filter.ordering": function () {
               this.get_course();
          },
           "filter.page_size": function () {
               this.get_course();
          },
           "filter.page": function () {
               this.get_course();
          }
      },
       methods: {

           handleSizeChange(val) {
               // 每页数据量发生变化时执行的方法
               this.filter.page = 1;
               this.filter.page_size = val;
          },
           handleCurrentChange(val) {
               // 页码发生变化时执行的方法
               this.filter.page = val;
          },
           get_category() {
               // 获取课程分类信息
               this.$http.get(`${this.$BASE_URL}course/coursecategory/`).then(response => {
                   this.category_list = response.data;
              }).catch(() => {
                   this.$message({
                       message: "获取课程分类信息有误,请联系客服工作人员",
                  })
              })
          },
           get_course() {
               // 排序
               let filters = {
                   ordering: this.filter.ordering, // 排序
              };
               // 判决是否进行分类课程的展示
               if (this.filter.course_category > 0) {
                   filters.course_category = this.filter.course_category;
              }

               // 设置单页数据量
               if (this.filter.page_size > 0) {
                   filters.page_size = this.filter.page_size;
              } else {
                   filters.page_size = 5;
              }

               // 设置当前页码
               if (this.filter.page > 1) {
                   filters.page = this.filter.page;
              } else {
                   filters.page = 1;
              }


               // 获取课程列表信息
               // filter={ordering:-id,page_size:2,page:1}
               this.$http.get(`${this.$BASE_URL}course/course/`, {
                   params: filters
              }).then(response => {
                   // console.log(response.data);
                   this.course_list = response.data.results;
                   this.course_total = response.data.count;
                   // console.log(this.course_list);
              }).catch(() => {
                   this.$message({
                       message: "获取课程信息有误,请联系客服工作人员"
                  })
              })
          }
      }
  }
</script>

<style scoped>
  .course {
       background: #f6f6f6;
  }

  .course .main {
        1100px;
       margin: 35px auto 0;
  }

  .course .condition {
       margin-bottom: 35px;
       padding: 25px 30px 25px 20px;
       background: #fff;
       border-radius: 4px;
       box-shadow: 0 2px 4px 0 #f0f0f0;
  }

  .course .cate-list {
       border-bottom: 1px solid #333;
       border-bottom-color: rgba(51, 51, 51, .05);
       padding-bottom: 18px;
       margin-bottom: 17px;
  }

  .course .cate-list::after {
       content: "";
       display: block;
       clear: both;
  }

  .course .cate-list li {
       float: left;
       font-size: 16px;
       padding: 6px 15px;
       line-height: 16px;
       margin-left: 14px;
       position: relative;
       transition: all .3s ease;
       cursor: pointer;
       color: #4a4a4a;
       border: 1px solid transparent; /* transparent 透明 */
  }

  .course .cate-list .title {
       color: #888;
       margin-left: 0;
       letter-spacing: .36px;
       padding: 0;
       line-height: 28px;
  }

  .course .cate-list .this {
       color: #ffc210;
       border: 1px solid #ffc210 !important;
       border-radius: 30px;
  }

  .course .ordering::after {
       content: "";
       display: block;
       clear: both;
  }

  .course .ordering ul {
       float: left;
  }

  .course .ordering ul::after {
       content: "";
       display: block;
       clear: both;
  }

  .course .ordering .condition-result {
       float: right;
       font-size: 14px;
       color: #9b9b9b;
       line-height: 28px;
  }

  .course .ordering ul li {
       float: left;
       padding: 6px 15px;
       line-height: 16px;
       margin-left: 14px;
       position: relative;
       transition: all .3s ease;
       cursor: pointer;
       color: #4a4a4a;
  }

  .course .ordering .title {
       font-size: 16px;
       color: #888;
       letter-spacing: .36px;
       margin-left: 0;
       padding: 0;
       line-height: 28px;
  }

  .course .ordering .this {
       color: #ffc210;
  }

  .course .ordering .price {
       position: relative;
  }

  .course .ordering .price::before,
  .course .ordering .price::after {
       cursor: pointer;
       content: "";
       display: block;
        0px;
       height: 0px;
       border: 5px solid transparent;
       position: absolute;
       right: 0;
  }

  .course .ordering .price::before {
       border-bottom: 5px solid #aaa;
       margin-bottom: 2px;
       top: 2px;
  }

  .course .ordering .price::after {
       border-top: 5px solid #aaa;
       bottom: 2px;
  }

  .course .ordering .price_up::before {
       border-bottom-color: #ffc210;
  }

  .course .ordering .price_down::after {
       border-top-color: #ffc210;
  }

  .course .course-item:hover {
       box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
  }

  .course .course-item {
        1100px;
       background: #fff;
       padding: 20px 30px 20px 20px;
       margin-bottom: 35px;
       border-radius: 2px;
       cursor: pointer;
       box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
       /* css3.0 过渡动画 hover 事件操作 */
       transition: all .2s ease;
  }

  .course .course-item::after {
       content: "";
       display: block;
       clear: both;
  }

   /* 顶级元素 父级元素  当前元素{} */
  .course .course-item .course-image {
       float: left;
        423px;
       height: 210px;
       margin-right: 30px;
  }

  .course .course-item .course-image img {
       max- 100%;
       max-height: 210px;
  }

  .course .course-item .course-info {
       float: left;
        596px;
  }

  .course-item .course-info h3 a {
       font-size: 26px;
       color: #333;
       font-weight: normal;
       margin-bottom: 8px;
  }

  .course-item .course-info h3 span {
       font-size: 14px;
       color: #9b9b9b;
       float: right;
       margin-top: 14px;
  }

  .course-item .course-info h3 span img {
        11px;
       height: auto;
       margin-right: 7px;
  }

  .course-item .course-info .teather-info {
       font-size: 14px;
       color: #9b9b9b;
       margin-bottom: 14px;
       padding-bottom: 14px;
       border-bottom: 1px solid #333;
       border-bottom-color: rgba(51, 51, 51, .05);
  }

  .course-item .course-info .teather-info span {
       float: right;
  }

  .course-item .section-list::after {
       content: "";
       display: block;
       clear: both;
  }

  .course-item .section-list li {
       float: left;
        44%;
       font-size: 14px;
       color: #666;
       padding-left: 22px;
       /* background: url("路径") 是否平铺 x轴位置 y轴位置 */
       background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
       margin-bottom: 15px;
  }

  .course-item .section-list li .section-title {
       /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */
       text-overflow: ellipsis;
       overflow: hidden;
       white-space: nowrap;
       display: inline-block;
       max- 200px;
  }

  .course-item .section-list li:hover {
       background-image: url("/src/assets/img/play-icon-yellow.svg");
       color: #ffc210;
  }

  .course-item .section-list li .free {
        34px;
       height: 20px;
       color: #fd7b4d;
       vertical-align: super;
       margin-left: 10px;
       border: 1px solid #fd7b4d;
       border-radius: 2px;
       text-align: center;
       font-size: 13px;
       white-space: nowrap;
  }

  .course-item .section-list li:hover .free {
       color: #ffc210;
       border-color: #ffc210;
  }

  .course-item {
       position: relative;
  }

  .course-item .pay-box {
       position: absolute;
       bottom: 20px;
        600px;
  }

  .course-item .pay-box::after {
       content: "";
       display: block;
       clear: both;
  }

  .course-item .pay-box .discount-type {
       padding: 6px 10px;
       font-size: 16px;
       color: #fff;
       text-align: center;
       margin-right: 8px;
       background: #fa6240;
       border: 1px solid #fa6240;
       border-radius: 10px 0 10px 0;
       float: left;
  }

  .course-item .pay-box .discount-price {
       font-size: 24px;
       color: #fa6240;
       float: left;
  }

  .course-item .pay-box .original-price {
       text-decoration: line-through;
       font-size: 14px;
       color: #9b9b9b;
       margin-left: 10px;
       float: left;
       margin-top: 10px;
  }

  .course-item .pay-box .buy-now {
        120px;
       height: 38px;
       background: transparent;
       color: #fa6240;
       font-size: 16px;
       border: 1px solid #fd7b4d;
       border-radius: 3px;
       transition: all .2s ease-in-out;
       float: right;
       text-align: center;
       line-height: 38px;
       position: absolute;
       right: 0;
       bottom: 5px;
  }

  .course-item .pay-box .buy-now:hover {
       color: #fff;
       background: #ffc210;
       border: 1px solid #ffc210;
  }

  .course .course_pagination {
       margin-bottom: 60px;
       text-align: center;
  }
</style>

 

2 课程详情前端

1 this.$route的解释
//this.$route  当前正在在的路由对象
//在路由文件中如果: path: '/actualcourse/detail/:id',
//id就会被放到路由对象的,params对象中
//如果路径是/actualcourse/detail/2?name=lqz
//name会被放到路由对象的:query对象中
console.log(this.$route)


2 this.$route 和this.$router区别
-第一个是当前正在访问的路由对象
   -第二个是vue-router对象
   
3 video-player下载播放器组件
-cnpm install vue-video-player
   -在main.js中配置
       //视频组件的使用
       require('video.js/dist/video-js.css');
       require('vue-video-player/src/custom-theme.css');
       import VideoPlayer from 'vue-video-player'
       Vue.use(VideoPlayer);
   -在组件中
  import {videoPlayer} from 'vue-video-player';
       在组件中注册
   -html中:
       <videoPlayer class="video-player vjs-custom-skin"
       ref="videoPlayer"
      :playsinline="true"
          :options="playerOptions"
               @play="onPlayerPlay($event)"
               @pause="onPlayerPause($event)">
       </videoPlayer>
       
   -js中
         playerOptions: {
                   aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9""4:3"
                   sources: [{ // 播放资源和资源格式
                       type: "video/mp4",
                       src: "https://video.pearvideo.com/mp4/adshort/20210513/cont-1729236-15674065_adpkg-ad_hd.mp4" //你的视频地址(必填)
                  }],
              }
 

3 章节接口

3.1 路由

router.register('chapters', views.CourseChapterView, 'chapters')

3.2 视图类

class CourseChapterView(GenericViewSet, ListModelMixin):
   queryset = CourseChapter.objects.all()
   serializer_class = CourseChapterModelSerializer
   # 配置一个过滤,以课程为过滤条件
   filter_backends = [DjangoFilterBackend,]
   filter_fields = ('course',)

3.3 序列化类(子序列化)

class CourseSectionModelSerializer(serializers.ModelSerializer):
   class Meta:
       model = CourseSection
       fields = ['name', 'orders', 'section_type', 'section_link', 'duration', 'free_trail']


class CourseChapterModelSerializer(serializers.ModelSerializer):
   # 子序列化方式实现,一个章节下有很多课时,必须写many=True
   coursesections = CourseSectionModelSerializer(many=True)

   class Meta:
       model = CourseChapter
       fields = ['course', 'chapter', 'name', 'summary', 'coursesections']

3.4 访问路径

http://127.0.0.1:8000/course/chapter/?course=3

4 章节前端

    get_chapter() {
               // 获取当前课程对应的章节课时信息
               // http://127.0.0.1:8000/course/chapters/?course=(pk)
               this.$http.get(`${this.$BASE_URL}course/chapters/`, {
                   params: {
                       "course": this.course_id,
                  }
              }).then(response => {
                   this.course_chapters = response.data;
                   console.log(this.course_chapters)
              }).catch(error => {
                   console.log(error.response);
              })
          },

 

 

 

5 七牛视频托管

1 第一:放到media文件夹下(小项目)
2 第二:第三方托管平台(oss,七牛云,腾讯云)
-
3 第三:自己搭建文件存储服务器(ceph,fastdfs(少了),go-fastdfs,minIo)

4 实际视频上传方案
-第一种:运营把视频上传到托管平台---》托管平台会生成一个连接,把连接使用admin配置到表中
   -第二种:有一个视频上传接口
  -运营使用我们自己的后台管理---》传到咱们后台---》调用七牛云的sdk---》传到七牛云---》返回一个地址---》写道咱么数据库中
       -运营使用我们自己的后台管理---》使用js---》调用七牛云的sdk---》传到七牛云---》返回一个地址---》调用咱们后端接口---》写道咱么数据库中

 

6 搜索接口

1 只搜索课程名(mysql)
2 老师名,课程介绍,章节内
-elasticsearch:分布式的全文检索引擎,近实时查询
class CourseSearchView(GenericViewSet, ListModelMixin):
queryset = Course.objects.all()
serializer_class = CourseModelSerializer

filter_backends=[SearchFilter,]
pagination_class = CommonPageNumberPagination
search_fields=['name']

# def list(self, request, *args, **kwargs):
#
# response=super().list( request, *args, **kwargs) # 查到的只是实战课
# search=request.query_params.get('search')
# # 轻课表查询,序列化
# # 免费课表查询,序列化
#
# data={'actual_course':response.data,'free':[],'lite_course':[]}

7 搜索前端

<template>
<div class="search-course course">
<Header/>

<!-- 课程列表 -->
<div class="main">
<div v-if="course_list.length > 0" class="course-list">
<div class="course-item" v-for="course in course_list" :key="course.name">
<div class="course-image">
<img :src="course.course_img" alt="">
</div>
<div class="course-info">
<h3>
<router-link :to="'/free/detail/'+course.id">{{course.name}}</router-link>
<span><img src="@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习</span></h3>
<p class="teather-info">
{{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}
<span v-if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时</span>
<span v-else>共{{course.sections}}课时/更新完成</span>
</p>
<ul class="section-list">
<li v-for="(section, key) in course.section_list" :key="section.name"><span
class="section-title">0{{key+1}} | {{section.name}}</span>
<span class="free" v-if="section.free_trail">免费</span></li>
</ul>
<div class="pay-box">
<div v-if="course.discount_type">
<span class="discount-type">{{course.discount_type}}</span>
<span class="discount-price">¥{{course.real_price}}元</span>
<span class="original-price">原价:{{course.price}}元</span>
</div>
<span v-else class="discount-price">¥{{course.price}}元</span>
<span class="buy-now">立即购买</span>
</div>
</div>
</div>
</div>
<div v-else style="text-align: center; line-height: 60px">
没有搜索结果
</div>
<div class="course_pagination block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="filter.page"
:page-sizes="[2, 3, 5, 10]"
:page-size="filter.page_size"
layout="sizes, prev, pager, next"
:total="course_total">
</el-pagination>
</div>
</div>
</div>
</template>

<script>
import Header from '../components/Header'

export default {
name: "Search",
components: {
Header,
},
data() {
return {
course_list: [],
course_total: 0,
filter: {
page_size: 10,
page: 1,
search: '',
}
}
},
created() {
this.get_course()
},
watch: {
'$route.query' () {
this.get_course()
}
},
methods: {
handleSizeChange(val) {
// 每页数据量发生变化时执行的方法
this.filter.page = 1;
this.filter.page_size = val;
},
handleCurrentChange(val) {
// 页码发生变化时执行的方法
this.filter.page = val;
},
get_course() {
// 获取搜索的关键字
this.filter.search = this.$route.query.word || this.$route.query.wd;

// 获取课程列表信息
this.$http.get(`${this.$BASE_URL}course/search/`, {
params: this.filter
}).then(response => {
// 如果后台不分页,数据在response.data中;如果后台分页,数据在response.data.results中
this.course_list = response.data.results;
this.course_total = response.data.count;
}).catch(() => {
this.$message({
message: "获取课程信息有误,请联系客服工作人员"
})
})
}
}
}
</script>

<style scoped>
.course {
background: #f6f6f6;
}

.course .main {
1100px;
margin: 35px auto 0;
}

.course .condition {
margin-bottom: 35px;
padding: 25px 30px 25px 20px;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 4px 0 #f0f0f0;
}

.course .cate-list {
border-bottom: 1px solid #333;
border-bottom-color: rgba(51, 51, 51, .05);
padding-bottom: 18px;
margin-bottom: 17px;
}

.course .cate-list::after {
content: "";
display: block;
clear: both;
}

.course .cate-list li {
float: left;
font-size: 16px;
padding: 6px 15px;
line-height: 16px;
margin-left: 14px;
position: relative;
transition: all .3s ease;
cursor: pointer;
color: #4a4a4a;
border: 1px solid transparent; /* transparent 透明 */
}

.course .cate-list .title {
color: #888;
margin-left: 0;
letter-spacing: .36px;
padding: 0;
line-height: 28px;
}

.course .cate-list .this {
color: #ffc210;
border: 1px solid #ffc210 !important;
border-radius: 30px;
}

.course .ordering::after {
content: "";
display: block;
clear: both;
}

.course .ordering ul {
float: left;
}

.course .ordering ul::after {
content: "";
display: block;
clear: both;
}

.course .ordering .condition-result {
float: right;
font-size: 14px;
color: #9b9b9b;
line-height: 28px;
}

.course .ordering ul li {
float: left;
padding: 6px 15px;
line-height: 16px;
margin-left: 14px;
position: relative;
transition: all .3s ease;
cursor: pointer;
color: #4a4a4a;
}

.course .ordering .title {
font-size: 16px;
color: #888;
letter-spacing: .36px;
margin-left: 0;
padding: 0;
line-height: 28px;
}

.course .ordering .this {
color: #ffc210;
}

.course .ordering .price {
position: relative;
}

.course .ordering .price::before,
.course .ordering .price::after {
cursor: pointer;
content: "";
display: block;
0px;
height: 0px;
border: 5px solid transparent;
position: absolute;
right: 0;
}

.course .ordering .price::before {
border-bottom: 5px solid #aaa;
margin-bottom: 2px;
top: 2px;
}

.course .ordering .price::after {
border-top: 5px solid #aaa;
bottom: 2px;
}

.course .ordering .price_up::before {
border-bottom-color: #ffc210;
}

.course .ordering .price_down::after {
border-top-color: #ffc210;
}

.course .course-item:hover {
box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
}

.course .course-item {
1100px;
background: #fff;
padding: 20px 30px 20px 20px;
margin-bottom: 35px;
border-radius: 2px;
cursor: pointer;
box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
/* css3.0 过渡动画 hover 事件操作 */
transition: all .2s ease;
}

.course .course-item::after {
content: "";
display: block;
clear: both;
}

/* 顶级元素 父级元素 当前元素{} */
.course .course-item .course-image {
float: left;
423px;
height: 210px;
margin-right: 30px;
}

.course .course-item .course-image img {
max- 100%;
max-height: 210px;
}

.course .course-item .course-info {
float: left;
596px;
}

.course-item .course-info h3 a {
font-size: 26px;
color: #333;
font-weight: normal;
margin-bottom: 8px;
}

.course-item .course-info h3 span {
font-size: 14px;
color: #9b9b9b;
float: right;
margin-top: 14px;
}

.course-item .course-info h3 span img {
11px;
height: auto;
margin-right: 7px;
}

.course-item .course-info .teather-info {
font-size: 14px;
color: #9b9b9b;
margin-bottom: 14px;
padding-bottom: 14px;
border-bottom: 1px solid #333;
border-bottom-color: rgba(51, 51, 51, .05);
}

.course-item .course-info .teather-info span {
float: right;
}

.course-item .section-list::after {
content: "";
display: block;
clear: both;
}

.course-item .section-list li {
float: left;
44%;
font-size: 14px;
color: #666;
padding-left: 22px;
/* background: url("路径") 是否平铺 x轴位置 y轴位置 */
background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
margin-bottom: 15px;
}

.course-item .section-list li .section-title {
/* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: inline-block;
max- 200px;
}

.course-item .section-list li:hover {
background-image: url("/src/assets/img/play-icon-yellow.svg");
color: #ffc210;
}

.course-item .section-list li .free {
34px;
height: 20px;
color: #fd7b4d;
vertical-align: super;
margin-left: 10px;
border: 1px solid #fd7b4d;
border-radius: 2px;
text-align: center;
font-size: 13px;
white-space: nowrap;
}

.course-item .section-list li:hover .free {
color: #ffc210;
border-color: #ffc210;
}

.course-item {
position: relative;
}

.course-item .pay-box {
position: absolute;
bottom: 20px;
600px;
}

.course-item .pay-box::after {
content: "";
display: block;
clear: both;
}

.course-item .pay-box .discount-type {
padding: 6px 10px;
font-size: 16px;
color: #fff;
text-align: center;
margin-right: 8px;
background: #fa6240;
border: 1px solid #fa6240;
border-radius: 10px 0 10px 0;
float: left;
}

.course-item .pay-box .discount-price {
font-size: 24px;
color: #fa6240;
float: left;
}

.course-item .pay-box .original-price {
text-decoration: line-through;
font-size: 14px;
color: #9b9b9b;
margin-left: 10px;
float: left;
margin-top: 10px;
}

.course-item .pay-box .buy-now {
120px;
height: 38px;
background: transparent;
color: #fa6240;
font-size: 16px;
border: 1px solid #fd7b4d;
border-radius: 3px;
transition: all .2s ease-in-out;
float: right;
text-align: center;
line-height: 38px;
position: absolute;
right: 0;
bottom: 5px;
}

.course-item .pay-box .buy-now:hover {
color: #fff;
background: #ffc210;
border: 1px solid #ffc210;
}

.course .course_pagination {
margin-bottom: 60px;
text-align: center;
}
</style>

 

5 支付宝支付介绍

1 需要商户号:公司营业执照,手续费
2 沙箱环境:开发测试----》转成正式的--》只需要修改key
3 集成支付宝支付
-官方没有提供python的sdk
-正常情况需要使用api开发,但是太麻烦,我们使用第三方的sdk
-https://github.com/fzlee/alipay


4 正常地址(不是咱们操作的):https://mrchportalweb.alipay.com/accountmanage/bind/appIdBindList
5 沙箱环境:https://openhome.alipay.com/platform/appDaily.htm

6 支付过程需要加密(非对称加密:公钥私钥)
-支付宝提供一个公钥私钥生成的工具
-https://opendocs.alipay.com/open/291/105971#LDsXr
-生成公钥私钥,把公钥复制到支付宝网站上
-网站会生成一个支付宝公钥(留着)
-自己的私钥(留着)
-下在app(付款时,使用这个app扫码)
-商家账号babdgw8208@sandbox.com
登录密码111111
-买家账号
买家账号bfxtlv8393@sandbox.com
登录密码111111
支付密码111111

 

6 支付宝sdk支付演示

from alipay import AliPay
from alipay.utils import AliPayConfig

# app_private_key_string = """
# -----BEGIN RSA PRIVATE KEY-----
# MIICXQIBAAKBgQDBQCtQUmyJY+Z9zDgf/rVj9AXN/ungJGyxWEu9NAU8QZKIV82tWq6wYw6udHd8cuaRyIdTPwgJl0Faxx2IzwtALlishHF7a0b5yMIS8IYC2z+QwKG+8JNQjWpCqMd8hCIvdbVfzNS0T92PEFreA904qwTFSRveMVHEcqs1siDtmQIDAQABAoGBAKlc75jpY63VG7/MJrQDqjz7M8shSR7jTU7vrxEWcjAo158eNGdlPgvgBJHoCH/Mwz2onNDcQNMG+IPyVXP84EHNa3+ClTH5RbQqEvQHD00mvzo8ajUvlkYe9oCn2ewotUbe4nFRDb6qaiIVuBIMsg8ruFD4rWF9D9QecAJHFJKxAkEA4toKGoCZBOmaKyI0dyE9DnsC4dffHeSKDOsqPDnZvDZZkEfap0u4xQR917bP121canRPwaAhbtnrtt4wXPtDXwJBANoU3xM4RHIiDTID7mdoWvG+E/pnKxra5Lc+wQAOG7fQzzEjx8Yspltd8O82m2PY+l/fnV8gezF2d/M/Pr6bqgcCQAgRj0hwCIFHOceM+Oa/1Ocd8vVLc1Eh3tMkziTEPf1WxYq/M4S9yb2gMkWo5+2WozHaHzgY1PeXYq3nazrzaOsCQQCL4R9EgK7GVjkIf6UHBtRugnDmCA6J5yUUtFeu5V26BWEgL8cPwcvihtrnVKtO2/mcTR3vyjG6hDZj+4kPUWE5AkB+qa/4abMJuys9BMuIK8CWKQ8PwawYqo1uwjl7wu6Sdc69XH7rWedn3fG0lnyfhcCSpjEIJl4DVLluDEYhoWgf
# -----END RSA PRIVATE KEY-----
# """
#
# alipay_public_key_string = """
# -----BEGIN PUBLIC KEY-----
# MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBQCtQUmyJY+Z9zDgf/rVj9AXN/ungJGyxWEu9NAU8QZKIV82tWq6wYw6udHd8cuaRyIdTPwgJl0Faxx2IzwtALlishHF7a0b5yMIS8IYC2z+QwKG+8JNQjWpCqMd8hCIvdbVfzNS0T92PEFreA904qwTFSRveMVHEcqs1siDtmQIDAQAB
# -----END PUBLIC KEY-----"""


app_private_key_string = open("./private.pem").read()
alipay_public_key_string = open("./publish.pem").read()
alipay = AliPay(
appid="2016092000554611",
app_notify_url='http://127.0.0.1:8000/', # post回调地址,公网地址,默认回调url
app_private_key_string=app_private_key_string,
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=alipay_public_key_string,
sign_type="RSA2", # RSA 或者 RSA2
debug=True, # 默认False
)

# 调用某个方法,传入商品名,和价格,就能生成一个支付宝连接
'''
subject, out_trade_no, total_amount,return_url=None, notify_url=None
'''
res = alipay.api_alipay_trade_page_pay(
subject='兰博基尼',
out_trade_no='asdfasddeede33334ddde',
total_amount=99999,
return_url='http://192.168.12.126:8080/',
notify_url='http://127.0.0.1:8000/'
)

print('https://openapi.alipaydev.com/gateway.do?' + res)

 

7 支付宝支付封装

alpay
__init__.py
pay.py
private.pem
publish.pem
settings.py

###### pay.py
from alipay import AliPay
from . import settings
alipay = AliPay(
appid=settings.APPID,
app_notify_url=settings.APP_NOTIFY_URL, # post回调地址,公网地址,默认回调url
app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
sign_type="RSA2", # RSA 或者 RSA2
debug=True, # 默认False
)

###### settings.py
APP_PRIVATE_KEY_STRING= open("./private.pem").read()
ALIPAY_PUBLIC_KEY_STRING = open("./publish.pem").read()
APPID='2016092000554611'
APP_NOTIFY_URL='http://127.0.0.1:8000/'
DEBUG=True

 

8 订单表分析

from django.db import models
from user.models import User
from course.models import Course
# Create your models here.


# 订单表,订单详情表
"""
class Order(models.Model):
# 主键、总金额、订单名、订单号、订单状态、创建时间、支付时间、流水号(支付宝返回的)、支付方式、支付人(外键)
pass

class OrderDetail(models.Model):
# 订单号(外键)、商品(外键)、实价、成交价
pass
"""

class Order(models.Model):
"""订单模型"""
status_choices = (
(0, '未支付'),
(1, '已支付'),
(2, '已取消'),
(3, '超时取消'),
)
pay_choices = (
(1, '支付宝'),
(2, '微信支付'),
)
subject = models.CharField(max_length=150, verbose_name="订单标题")
total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0)
out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True)
trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号") # 支付宝返回的流水号
order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

class Meta:
db_table = "luffy_order"
verbose_name = "订单记录"
verbose_name_plural = "订单记录"

def __str__(self):
return "%s - ¥%s" % (self.subject, self.total_amount)



class OrderDetail(models.Model):
"""订单详情"""
order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单")
course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False, verbose_name="课程")
price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")

class Meta:
db_table = "luffy_order_detail"
verbose_name = "订单详情"
verbose_name_plural = "订单详情"

def __str__(self):
try:
return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
except:
return super().__str__()

 

9 订单相关接口分析

1 生成订单接口
-前端需要传的数据{courses:[1,2,3],total_money:888,}
-用户登录(认证类:可以自己写,可以使用内置的(认证类,权限类))
   -校验前端传入的价格是否合法
   -生成支付连接(调用封装好的pay)
   -入库(存俩表),重写create方法
   
2 支付宝get回调到前端,前端发起一个请求到后端(可写可不写)
3 支付宝post回调接口
-验证是否支付成功,如果支持成功,修改订单状态和时间
   -需要给支付宝回复(如果你不回复,它人为你没收,24小时内,给你发8次)
原文地址:https://www.cnblogs.com/DEJAVU888/p/14894006.html