• 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次)
  • 相关阅读:
    Best Time to Buy and Sell Stock II
    Subsets II
    Subsets I
    Combinations
    Permutation Sequence
    Next Permutation
    Anagrams
    Combination-Sum II
    Combination-Sum I
    Permutations II
  • 原文地址:https://www.cnblogs.com/DEJAVU888/p/14894006.html
Copyright © 2020-2023  润新知