分布式架构项目-前端

第八阶段模块二

前端门户系统

访问:http://edufront.lagou.com/

用户名:15510792995 密码:111111

页面不需要我们自己开发,使用提供的页面即可

运行项目 npm run serve

1、首页显示全部课程

Index.vue

<script>
import Header from "./Header/Header"; //顶部登录条
import Footer from "./Footer/index"; //顶部登录条
export default {
  name: "Index",
  components: {
    Header,
    Footer,
  },
  data() {
    return {
      activeName: "allLesson",
      courseList:[] // 课程集合
    };
  },
  created() {
    this.getCourseList(); // 当组件创建完毕,就调用获取课程的方法
  },
  methods: {
    changeCourseTab(tabName) {
      this.classSelect = tabName;
      sessionStorage && sessionStorage.setItem("courseTab", tabName);
    },
    gotoDetail() {
      this.$router.push({ name: "Course", params: { courseid: 1 } });
    },
    getCourseList(){  // 去dubbo服务获取全部课程的数据
      return this.axios
      .get("http://localhost:8002/course/getAllCourse")
      .then(result =>{
        console.log(result);
        this.courseList = result.data;
      }).catch(error =>{
        this.$message.error("获取课程信息失败!!!");
      });
    }
  },
};
</script>
<template>
  <div>
    <Header></Header>
    <div style="width: 850PX;display: inline-block; margin:0px auto;">
      <el-tabs v-model="activeName">
        <el-tab-pane label="选课" name="allLesson">
          <ul class="course-ul-pc">
            <!-- 课程信息展示开始 -->
            <li class="course-li" v-for="(item,index) in courseList" :key="index">
              <!-- 课程封面图 -->
              <img
                :src="item.courseImgUrl"
                class="teacher-portrait hover-pointer"/>
              <!-- 课程文字信息 -->
              <div class="content-main">
                <!-- 课程标题 -->
                <div class="content-title hover-pointer">
                  <div
                    class="p-title"
                    style="text-align:left;"
                    @click="gotoDetail"
                  >
                    <span>
                      {{item.courseName}}
                    </span>
                  </div>
                  <!-- 作者和职称 -->
                  <p class="p-title-buy text-overflow">
                    <span class="p-author-span">
                      {{item.teacher.teacherName}}
                    </span>
                    <span class="p-author-line" />
                    <span class="p-author-span">
                      {{item.teacher.position}}
                    </span>
                  </p>
                  <p></p>
                  <!-- 课程简单描述 -->
                  <p class="p-describe" style="text-align:left;">
                    {{item.brief}}
                  </p>
                </div>
                <!-- 课程前两个章节信息 -->
                <ul class="content-course" style="text-align:left;">
                  <!-- 章节1 免费试看,通常是第一章的前两节课-->
                  <li
                    class="content-course-lesson text-overflow"
                    style="width:300px" 
                    v-for="(lesson,index) in item.courseSections[0].courseLessons.slice(0,2)" :key="index"
                  >
                    <!-- 免费试看图标 -->
                    <img
                      src="@/assets/course-list/free-course.png"
                      class="free-label hover-pointer"
                    />
                    <span class="theme-span hover-pointer">
                      {{lesson.theme}}
                      </span>
                  </li>

                </ul>
                <!-- 价格信息 -->
                <div class="content-price" style="text-align:left;">
                  <p class="content-price-p">
                    <span class="content-price-orange-sm"></span>
                    <span class="content-price-orange">{{item.discounts}}</span>
                    <span class="current-price">
                      <span class="current-price-unite"></span>
                      {{item.price}}
                    </span>
                    <span class="activity-name">成就自己</span>
                    <span class="content-price-buy">{{item.sales}}</span>
                  </p>
                  <div class="btn btn-green btn-offset">立即购买</div>
                </div>
              </div>
            </li>
            <!-- 课程信息结束 -->
          </ul>
        </el-tab-pane>   
      </el-tabs>
    </div>
    <Footer></Footer>
  </div>
</template>

2、登录

<template>
  <div class="header-pc-wrap">
    <!-- 登录框 开始-->
    <el-dialog
      style="width:800px;margin:0px auto;"
      title=""
      :visible.sync="dialogFormVisible">
      <el-form>
        <el-form-item>
          <h1 style="font-size:30px;color:#00B38A">拉勾</h1>
        </el-form-item>
        <el-form-item>
          <el-input v-model="phone" placeholder="请输入常用手机号..."></el-input>
        </el-form-item>
        <el-form-item>
          <el-input v-model="password" placeholder="请输入密码..."></el-input>
        </el-form-item>
      </el-form>
      <el-button
        style="width:100%;margin:0px auto;background-color: #00B38A;font-size:20px"
        type="primary"
        @click="login">确 定</el-button>
      <p></p>
      <!-- 微信登录图标 -->
      <img
        @click="goToLoginWX"
        src="http://www.lgstatic.com/lg-passport-fed/static/pc/modules/common/img/icon-wechat@2x_68c86d1.png"
        alt=""
      />
    </el-dialog>
    <!-- 登录框 结束-->

    <!-- 顶部登录条 -->
    <div class="wrap-box">
      <div @click="toToIndex" class="edu-icon"></div>
      <div @click="toToIndex" class="text">拉勾教育</div>
      <div class="right-var-wrap" v-if="!isLogin">
        <div class="login-handler" @click="goToLogin">登录 | 注册</div>
      </div>
      <div class="right-var-wrap" v-if="isLogin">
        <div
          :class="{ 'tip-icon': true, 'has-new-message': isHasNewMessage }"
          @click="toToNotic"
        >
          <i class="el-icon-bell"></i>
        </div>
        <img :src="userDTO.content.portrait" class="avatar-wrap" />
        <div class="bar-wrap">
          <ul class="account-bar" >
            <li class="user_dropdown" data-lg-tj-track-code="index_user" >
              <span class="unick">{{ userDTO.content.name }}</span>
              <i />
              <ul style="">
                <li @click="goToSetting">
                  账号设置
                </li>
                <li @click="logout">
                  退出
                </li>
              </ul>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Header",

  props: {},
  data() {
    return {
      isLogin: false, // 登录状态,true:已登录,false:未登录
      userDTO:null, // 用来保存登录的用户信息
      isHasNewMessage: false, // 是否有新的推送消息
      dialogFormVisible: false, // 是否显示登录框,true:显示,false:隐藏
      phone: "", // 双向绑定表单 手机号
      password: "", // 双向绑定表单 密码
    };
  },
  computed: {
  },
  watch: {
  },
  mounted() {
  },
  created(){
    // 当刷新页面,组件创建成功之后,立刻检测本地储存中是否存在用户对象
    this.userDTO = JSON.parse(localStorage.getItem("user"));
    if(this.userDTO != null){
      this.isLogin = true;  // 已登录
    }
  },
  methods: {
    goToSetting() {
      this.$router.push("/setting"); // 跳转个人设置页面
    },
    goToLogin() {
      this.dialogFormVisible = true; // 显示登录框
    },
    login(){  // 前去登录
      return this.axios
      .get("http://localhost:8002/user/login",{
        params:{
          phone:this.phone,
          password:this.password
        }
      })
      .then((result) =>{
        console.log(result);
        this.dialogFormVisible = false; // 关闭登录框
        this.userDTO = result.data; // 保存返回数据中的用户对象信息
        this.isLogin = true;  // 更新登录状态
        localStorage.setItem("user",JSON.stringify(this.userDTO));  // 将登录成功的对象信息保存到本地储存中
      }).catch((error) =>{
        this.$message.error("登录失败!!!");
      });
    },
    goToLoginWX() {
      alert("微信登录");
    },
    toToIndex() {
      this.$router.push("/"); //回到首页
    },
    toToNotic(){
    },
    logout(){ // 登出
      localStorage.setItem("user",null);  // 将登录成功的对象信息保存到本地储存中
      this.isLogin = false;  // 更新登录状态
      alert("谢谢使用,再见!!!");
    }
  },
};
</script>

3、已购课程

<script>
import Header from "./Header/Header"; //顶部登录条
import Footer from "./Footer/index"; //顶部登录条
export default {
  name: "Index",
  components: {
    Header,
    Footer,
  },
  data() {
    return {
      activeName: "allLesson",
      courseList:[], // 课程集合
      myCourseList:[],  // 我购买过的课程列表
      isLogin:false, // 登录状态
      user:null,     // 已登录的用户对象信息
    };
  },
  created() {
    this.user = JSON.parse(localStorage.getItem("user"));
    if(this.user != null){
      this.isLogin = true;  // 已登录
      this.getMyCourseList(); // 调用查询我购买的课程
    }

    this.getCourseList(); // 当组件创建完毕,就调用获取所有课程的方法
  },
  methods: {
    changeCourseTab(tabName) {
      this.classSelect = tabName;
      sessionStorage && sessionStorage.setItem("courseTab", tabName);
    },
    gotoDetail() {
      this.$router.push({ name: "Course", params: { courseid: 1 } });
    },
    getCourseList(){  // 去dubbo服务获取全部课程的数据
      return this.axios
      .get("http://localhost:8002/course/getAllCourse")
      .then(result =>{
        console.log(result);
        this.courseList = result.data;
      }).catch(error =>{
        this.$message.error("获取课程信息失败!!!");
      });
    },
    getMyCourseList(){  // 查询我购买的课程
      return this.axios
      .get("http://localhost:8002/course/getCourseByUserId/" + this.user.content.id)
      .then(result =>{
        console.log(result);
        this.myCourseList = result.data;
      }).catch(error =>{
        this.$message.error("获取已购买课程信息失败!!!");
      });
    }
  },
};
</script>
<el-tab-pane label="已购" name="hasPay">
    <div v-if="!isLogin">
        <img src="@/assets/course-list/no-login@2x.png" class="no-data-icon"/>
        <div class="no-data-title">您还没有登录</div>
        <div class="no-data-title">登录后即可查看已购课程</div>
        <div class="btn btn-yellow btn-center">立即登录</div>
    </div>
    <div v-if="isLogin">
        <ul class="course-ul-pc">
            <!-- 课程信息展示开始 -->
            <li class="course-li" v-for="(item,index) in myCourseList" :key="index">
                <!-- 课程封面图 -->
                <img
                     :src="item.courseImgUrl"
                     class="teacher-portrait hover-pointer"/>
                <!-- 课程文字信息 -->
                <div class="content-main">
                    <!-- 课程标题 -->
                    <div class="content-title hover-pointer">
                        <div
                             class="p-title"
                             style="text-align:left;"
                             @click="gotoDetail"
                             >
                            <span>
                                {{item.courseName}}
                            </span>
                        </div>
                        <!-- 作者和职称 -->
                        <p class="p-title-buy text-overflow">
                            <span class="p-author-span">
                                {{item.teacher.teacherName}}
                            </span>
                            <span class="p-author-line" />
                            <span class="p-author-span">
                                {{item.teacher.position}}
                            </span>
                        </p>
                        <p></p>
                        <!-- 课程简单描述 -->
                        <p class="p-describe" style="text-align:left;">
                            {{item.brief}}
                        </p>
                    </div>
                    <!-- 课程前两个章节信息 -->
                    <ul class="content-course" style="text-align:left;">
                        <!-- 章节1 免费试看,通常是第一章的前两节课-->
                        <li
                            class="content-course-lesson text-overflow"
                            style="width:300px" 
                            v-for="(lesson,index) in item.courseSections[0].courseLessons.slice(0,2)" :key="index">
                            <!-- 免费试看图标 -->
                            <img src="@/assets/course-list/free-course.png" class="free-label hover-pointer"/>
                            <span class="theme-span hover-pointer">
                                {{lesson.theme}}
                            </span>
                        </li>

                    </ul>
                    <!-- 价格信息 -->
                    <div class="content-price" style="text-align:left;">
                        <p class="content-price-p">
                            <span class="content-price-orange-sm"></span>
                            <span class="content-price-orange">{{item.discounts}}</span>
                            <span class="current-price">
                                <span class="current-price-unite"></span>
                                {{item.price}}
                            </span>
                            <span class="activity-name">成就自己</span>
                            <span class="content-price-buy">{{item.sales}}</span>
                        </p>
                        <div class="btn btn-yellow btn-offset">好好学习</div>
                    </div>
                </div>
            </li>
            <!-- 课程信息结束 -->
        </ul>
    </div>
</el-tab-pane>

4、课程详情-基本信息

Index.vue

<div
     class="p-title"
     style="text-align:left;"
     @click="gotoDetail(item)"
     >
    <span>
        {{item.courseName}}
    </span>
</div>

<script>
	methods: {	// 将课程对象传递到课程详情组件
        gotoDetail(item) {
            this.$router.push({ name: "Course", params: { course: item } });
        },
    }
</script>

Course.vue

<script>
    data() {
    return {
      activeName: "intro",
      course:null,
    };
  },
  created(){
    this.course = this.$route.params.course;  // 从路由中获得参数对象赋值给本组件的参数
  },
</script>
<div class="intro-content">
    <img class="course-img" :src="course.courseImgUrl" alt="课程图片"/>
    <div class="conent-wrap">
        <div class="name" style="text-align:left;">
            {{course.courseName}}
        </div>
        <div class="des text-omit" style="text-align:left;">
            {{course.brief}}
        </div>
        <div class="title">
            <div class="teacher-name text-omit">
                讲师:{{course.teacher.teacherName}}
                <span class="line"></span>
                {{course.teacher.position}}
            </div>
        </div>
        <div class="lesson-info">
            <div class="boook-icon backgroun-img-set"></div>
            <div class="time">{{totalLessons}} 讲 / {{course.totalDuration}} 课时</div>
            <div class="person-icon backgroun-img-set"></div>
            <div class="person">{{course.sales}}人已购买</div>
        </div>
    </div>
</div>

5、课程详情-共计多少讲

<div class="time">{{totalLessons}} 讲 / {{course.totalDuration}} 课时</div>
<script>
	data() {
    return {
      activeName: "intro",
      course:null,
      totalLessons:0, // 本门课程的总节数
    };
  },
  created(){
    this.course = this.$route.params.course;  // 从路由中获得参数对象赋值给本组件的参数
    console.log(this.course);
    let x = 0;
    for(let i = 0; i < this.course.courseSections.length; i++){
      let section = this.course.courseSections[i];  // 每一章
      for(let j = 0; j < section.courseLessons.length; j++){
        x++;
      }
    }
    this.totalLessons = x;
  },
</script>

6、课程详情-实现课程信息描述

html不识别数据库的html标签,必须使用v-html才可以

<el-tab-pane label="课程信息" name="intro">
    <div v-html="course.courseDescription" class="content-p pc-background"></div>
</el-tab-pane>

7、课程详情-显示章节目录

<div class="public-class-container is-pc">
    <el-tabs v-model="activeName">
        <el-tab-pane label="课程信息" name="intro">
            <div v-html="course.courseDescription" class="content-p pc-background"></div>
        </el-tab-pane>

        <el-tab-pane label="目录" name="directory">
            <div class="class-menu-contaniner list-page-container more-sections more-sections-padding">
                <!-- 每章 开始 -->
                <div v-for="(section , index) in course.courseSections" :key="index">
                    <div class="section-name single-line">
                        {{section.sectionName}}
                    </div>

                    <!-- 每节课 开始 -->
                    <div class="class-menu-block">
                        <div v-for="(lesson , index) in section.courseLessons" :key="index" class="class-level-one over-ellipsis" @click="watchCourse(1)">
                            <div class="text-wrap">
                                <div class="content">{{lesson.theme}}</div>
                                <div class="item-status-wrap item-status-wrap-list">
                                    <div class="item-status test-watch">试看</div>
                                </div>
                            </div>
                        </div>
                        <!-- 每节课 结束 -->
                    </div>
                </div>
                <!-- 每章 结束 -->
            </div>
        </el-tab-pane>
    </el-tabs>
</div>

8、课程详情-显示全部留言

<script>
  data() {
    return {
      activeName: "intro",
      course:null,
      totalLessons:0, // 本门课程的总节数
      commentList:null, // 所有留言
    };
  },
  created(){
    this.course = this.$route.params.course;  // 从路由中获得参数对象赋值给本组件的参数
    //console.log(this.course);
    let x = 0;
    for(let i = 0; i < this.course.courseSections.length; i++){
      let section = this.course.courseSections[i];  // 每一章
      for(let j = 0; j < section.courseLessons.length; j++){
        x++;
      }
    }
    this.totalLessons = x;

    this.getComment();
  },
  methods: {
    watchCourse(lessonid) {
      alert("观看第【" + lessonid + "】节课程视频!");
      this.$router.push({
        name: "videoDetail",
        params: { lessonid: lessonid },
      });
    },
    buy(courseid) {
      alert("购买第【" + courseid + "】门课程成功,加油!");
    },
    getComment(){
      return this.axios
      .get("http://localhost:8002/course/comment/getCourseCommentList/"+this.course.id+"/1/20")
      .then(result =>{
        console.log(result);
        this.commentList = result.data;
      }).catch(error =>{
        this.$message.error("获取留言信息失败!!!");
      });
    },
  },
</script>
<!-- 留言板 开始-->
<div class="message">
    <div class="message-topic">
        <div class="message-topic-title normal-font">精选留言</div>
    </div>
    <div>
        <div class="message-edit">
            <textarea rows="20" style="border:none;resize: none;" 
                      contenteditable="true"
                      placeholder="分享学习心得、思考感悟或者给自己一个小鼓励吧!"
                      class="edit-div pcStyle"
                      ></textarea>
            <div class="message-edit-count">
                <span class="message-edit-count-cur">0</span>
                <span class="message-edit-count-max">/2000</span>
            </div>
        </div>
        <div class="message-edit-footer flex">
            <button class="message-edit-btn disableBg" >发表留言</button>
        </div>
    </div>

    <!-- 留言 开始 -->
    <div class="message-list" v-for="(comment , index) in commentList" :key="index">
        <div class="message-list-title">
            <div class="message-list-title-left">
                <div class="message-list-title-left-name">{{comment.userName}}</div>
                <div class="message-list-title-left-tag"></div>
            </div>
            <div class="message-list-title-right">
                <img class="message-list-title-right-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAJFBMVEVHcEwAuI4AtIsAtIsAtIoAtYwAtIsAtYwAtowAx5kAtIsAs4qd4c1kAAAAC3RSTlMAGMfz3VGnbTYIhXtDq8EAAAETSURBVDjLldWhb8JAFAbwg8CGhGSGzNQtULNkWUioWbJkpmYKAQaBITPLLKZysmLz+xfolUL6/XM72msh9N2X8ImaX17vrq+vVeqU39XTuK/kdEMA01jGDY4ZyYWFIRPxp0S8u+8KeBJ+WDxIGFrMJbQm7qhVYSJgr8JUwNsKtYDtCiHgPavcsDVDstsuyDk7NeYDk6G8pM3bWXNawQUijWq8QyPPpQy+50ET9bF09go5pus3cMV0feFEc2DfieZRBU7Up7fjSkwZZgz3DA8MHxl6DP8YRgxjgokimDHcMdwyfGG4ZPjJMCKoY4KJIpgz3DHcMvQYFuPpk/005t1mffHlOs/ETvxXAA0Ul3oQHsp/xD93wxfHcC4VkwAAAABJRU5ErkJggg==" alt="">
                <div class="message-list-title-right-praise">{{comment.likeCount}}</div>
            </div>
        </div>
        <div class="message-list-content">
            {{comment.comment}}
        </div>
    </div>
    <!-- 留言 结束 -->
</div>
<!-- 留言板 结束-->

9、课程详情-章节状态

<script>
import Header from "./Header/Header"; //顶部登录条
import Footer from "./Footer/index"; //顶部登录条
export default {
  name: "Course",
  components: {
    Header,
    Footer,
  },
  data() {
    return {
      activeName: "intro",
      course:null,
      totalLessons:0, // 本门课程的总节数
      commentList:null, // 所有留言
      isLogin:false,  // 未登录
      isBuy:false,  // 未购买
      user:null,  // 当前用户
      myCourseList:[],  // 当前用户购买过的所有课程
    };
  },
  created(){
    this.course = this.$route.params.course;  // 从路由中获得参数对象赋值给本组件的参数
    
    // 检测是否登录
    this.user = JSON.parse(localStorage.getItem("user"));
    if(this.user != null){
      this.isLogin = true;    // 已登录
      this.getMyCourseList(); // 查询登录用户购买的所有课程
    }

    let x = 0;
    for(let i = 0; i < this.course.courseSections.length; i++){
      let section = this.course.courseSections[i];  // 每一章
      for(let j = 0; j < section.courseLessons.length; j++){
        x++;
      }
    }
    this.totalLessons = x;

    this.getComment();
  },
  methods: {
    watchCourse(lessonid) {
      alert("观看第【" + lessonid + "】节课程视频!");
      this.$router.push({
        name: "videoDetail",
        params: { lessonid: lessonid },
      });
    },
    buy(courseid) {
      alert("购买第【" + courseid + "】门课程成功,加油!");
    },
    // 获取本课程的全部留言
    getComment(){
      return this.axios.get("http://localhost:8002/course/comment/getCourseCommentList/"+this.course.id+"/1/20")
      .then(result =>{
        console.log(result);
        this.commentList = result.data;
      }).catch(error =>{
        this.$message.error("获取留言信息失败!!!");
      });
    },
    // 查询当前用户购买的所有课程
    getMyCourseList(){
      return this.axios
      .get("http://localhost:8002/course/getCourseByUserId/" + this.user.content.id)
      .then((result) =>{
        console.log(result);
        this.myCourseList = result.data;

        // 检测当前的课程是否购买过
        for(let i = 0; i < this.myCourseList.length; i++){
          if(this.myCourseList[i].id == this.course.id){
            this.isBuy = true;  // 标记购买过本课程
            break;
          }
        }

      }).catch(error =>{
        this.$message.error("获取已购买课程信息失败!!!");
      });
    },
  },
};
</script>
<el-tab-pane label="目录" name="directory">
    <div class="class-menu-contaniner list-page-container more-sections more-sections-padding">
        <!-- 第一章 开始 -->
        <div v-for="section in course.courseSections.slice(0,1)">
            <div class="section-name single-line">{{section.sectionName}}</div>
            <!-- 每节课 开始 -->
            <div class="class-menu-block">
                <div 
                     class="class-level-one over-ellipsis"
                     v-for="(lesson , index) in section.courseLessons" :key="index" 
                     @click="watchCourse(1)">
                    <div class="text-wrap">
                        <div class="content">{{lesson.theme}}</div>
                        <div class="item-status-wrap item-status-wrap-list">
                            <!-- 第一章,前两节 -->
                            <div v-if="index<2">
                                <!-- 未登录 => 试看 -->
                                <div v-if="!isLogin" class="item-status test-watch">试看</div>
                                <!-- 已登录,未购买 => 试看 -->
                                <div v-else-if="isLogin && !isBuy" class="item-status test-watch">试看</div>
                                <!-- 已登录,已购买 => 播放 -->
                                <div v-else class="item-status test-watch">播放</div>
                            </div>

                            <!-- 第一章,除了前两节的 -->
                            <div v-if="index>1">
                                <!-- 未登录 => 锁 -->
                                <div v-if="!isLogin" class="item-status lock"></div>
                                <!-- 已登录,未购买 => 锁 -->
                                <div v-else-if="isLogin && !isBuy" class="item-status lock"></div>
                                <!-- 已登录,已购买 => 播放 -->
                                <div v-else class="item-status test-watch">播放</div>
                            </div>

                        </div>
                    </div>
                </div>
                <!-- 每节课 结束 -->
            </div>
        </div>
        <!-- 第一章 结束 -->

        <!-- 其余章 开始 -->
        <div v-for="section in course.courseSections.slice(1,course.courseSections.length)">
            <div class="section-name single-line">{{section.sectionName}}</div>
            <!-- 每节课 开始 -->
            <div class="class-menu-block">
                <div 
                     class="class-level-one over-ellipsis"
                     v-for="(lesson , index) in section.courseLessons" :key="index"
                     @click="watchCourse(1)">
                    <div class="text-wrap">
                        <div class="content">{{lesson.theme}}</div>
                        <div class="item-status-wrap item-status-wrap-list">
                            <!-- 未登录 => 锁 -->
                            <div v-if="!isLogin" class="item-status lock"></div>
                            <!-- 已登录,未购买 => 锁 -->
                            <div v-else-if="isLogin && !isBuy" class="item-status lock"></div>
                            <!-- 已登录,已购买 => 播放 -->
                            <div v-else class="item-status test-watch">播放</div>
                        </div>
                    </div>
                </div>
                <!-- 每节课 结束 -->
            </div>
        </div>
        <!-- 其余章 结束 -->
    </div>
</el-tab-pane>

10、在课程详情页点击视频播放

Course.vue

<el-tab-pane label="目录" name="directory">
    <div class="class-menu-contaniner list-page-container more-sections more-sections-padding">
        <!-- 第一章 开始 -->
        <div v-for="section in course.courseSections.slice(0,1)">
            <div class="section-name single-line">{{section.sectionName}}</div>
            <!-- 每节课 开始 -->
            <div class="class-menu-block">
                <div 
                     class="class-level-one over-ellipsis"
                     v-for="(lesson , index) in section.courseLessons" :key="index" 
                     @click="watchCourse(1,lesson.id,index,lesson.courseMedia)">
                    <div class="text-wrap">
                        <div class="content">{{lesson.theme}}</div>
                        <div class="item-status-wrap item-status-wrap-list">
                            <!-- 第一章,前两节 -->
                            <div v-if="index<2">
                                <!-- 未登录 => 试看 -->
                                <div v-if="!isLogin" class="item-status test-watch">试看</div>
                                <!-- 已登录,未购买 => 试看 -->
                                <div v-else-if="isLogin && !isBuy" class="item-status test-watch">试看</div>
                                <!-- 已登录,已购买 => 播放 -->
                                <div v-else class="item-status test-watch">播放</div>
                            </div>

                            <!-- 第一章,除了前两节的 -->
                            <div v-if="index>1">
                                <!-- 未登录 => 锁 -->
                                <div v-if="!isLogin" class="item-status lock"></div>
                                <!-- 已登录,未购买 => 锁 -->
                                <div v-else-if="isLogin && !isBuy" class="item-status lock"></div>
                                <!-- 已登录,已购买 => 播放 -->
                                <div v-else class="item-status test-watch">播放</div>
                            </div>

                        </div>
                    </div>
                </div>
                <!-- 每节课 结束 -->
            </div>
        </div>
        <!-- 第一章 结束 -->

        <!-- 其余章 开始 -->
        <div v-for="section in course.courseSections.slice(1,course.courseSections.length)">
            <div class="section-name single-line">{{section.sectionName}}</div>
            <!-- 每节课 开始 -->
            <div class="class-menu-block">
                <div 
                     class="class-level-one over-ellipsis"
                     v-for="(lesson , index) in section.courseLessons" :key="index"
                     @click="watchCourse(2,lesson.id,index,lesson.courseMedia)">
                    <div class="text-wrap">
                        <div class="content">{{lesson.theme}}</div>
                        <div class="item-status-wrap item-status-wrap-list">
                            <!-- 未登录 => 锁 -->
                            <div v-if="!isLogin" class="item-status lock"></div>
                            <!-- 已登录,未购买 => 锁 -->
                            <div v-else-if="isLogin && !isBuy" class="item-status lock"></div>
                            <!-- 已登录,已购买 => 播放 -->
                            <div v-else class="item-status test-watch">播放</div>
                        </div>
                    </div>
                </div>
                <!-- 每节课 结束 -->
            </div>
        </div>
        <!-- 其余章 结束 -->
    </div>
</el-tab-pane>

<script>
	methods: {
    //播放视频 ( 章节,小节课编号,每节课的索引,每节课的视频对象)
    watchCourse(status,lessonid,index,media) { 
      if(media == null || media == ""){
          this.$message.error("播放失败,暂无视频!");
      }else{
        // 试看的可以跳转播放页面
        if(status == 1 && index < 2){
          this.$message.success("观看第【" + lessonid + "】节课程视频!");
          this.$router.push({ name: "videoDetail", params: { course: this.course , lessonid:lessonid } });
        }else{ // 锁上的,先验证是否登录
          if(!this.isLogin){
              this.$message.success("请先登录!");
          }else{ // 登录后,再验证是否购买过
            if(!this.isBuy){
              this.$message.warning("请购买并解锁,才能观看本视频!");
            }else{
              this.$message.success("观看第【" + lessonid + "】节课程视频!");
              this.$router.push({ name: "videoDetail", params: { course: this.course , lessonid:lessonid } });
            }
          }
        }
      }
    },
}
</script>

videoDetail.vue

<script>
export default {
  name: "videoDetail",
  components: {},
  data() {
    return {
      myvideo: null, // 播放器对象
      isplay: false, //是否在播放
      nowTime: "00:00", //当前播放时间
      totalTime: "00:00", //总时长
      course:null,  // 课程
      lessonid:0,   // 当前播放视频的课节id
    };
  },
  computed: {},
  created() {
    // 判断登录(暂无)

    // 从上一级页面的请求中获得课程对象和小结编号
    this.course = this.$route.params.course;
    this.lessonid = this.$route.params.lessonid;
  },
  mounted() {
    this.myvideo = document.getElementById("myvideo");
    this.initplay();  // 初始化播放的视频
  },
  methods: {
    // 初始化时播放的视频
    initplay(){
      // 1.在课程信息中查找即将播放的视频小节的编号
      for(let i = 0; i < this.course.courseSections.length; i++){
        let section = this.course.courseSections[i];
        for(let j = 0; j < section.courseLessons.length; j++){
          let lesson = section.courseLessons[j]; // 每节课
          if(lesson.courseMedia != null){
            if(this.lessonid == lesson.courseMedia.lessonId){
              console.log("视频地址:" + lesson.courseMedia.fileEdk);
              // 2.将小节视频的地址 赋值 给播放器,进行播放
              this.myvideo.src = lesson.courseMedia.fileEdk;
              return;
            }            
          }
        }
      }
    },
  },
};
</script>

11、播放页的信息显示

<script>
export default {
  name: "videoDetail",
  components: {},
  data() {
    return {
      myvideo: null, // 播放器对象
      isplay: false, //是否在播放
      nowTime: "00:00", //当前播放时间
      totalTime: "00:00", //总时长
      course:null,  // 课程
      lessonid:0,   // 当前播放视频的课节id
      lessonName:null,  // 当前播放的视频名称
      isLogin:false,  // 未登录
      isBuy:false,  // 未购买
    };
  },
  computed: {},
  created() {
    // 判断登录
    this.user = JSON.parse(localStorage.getItem("user"));
    if(this.user != null){
      this.isLogin = true;    // 已登录
    }

    // 从上一级页面的请求中获得课程对象和小结编号
    this.course = this.$route.params.course;
    this.lessonid = this.$route.params.lessonid;
    this.isBuy = this.$route.params.isBuy;
  },
  mounted() {
    this.myvideo = document.getElementById("myvideo");
    this.initplay();  // 初始化播放的视频
  },
  methods: {
    play() {
      this.isplay = !this.isplay;
      if (this.isplay) {
        this.myvideo.play();
      } else {
        this.myvideo.pause();
      }
    },
    // 获取视频的时间是秒为单位,格式化城00:00的格式
    formatTime(time) {
      let mm = Math.floor((time % 3600) / 60);
      let ss = Math.floor(time % 60);
      mm = mm < 10 ? "0" + mm : mm;
      ss = ss < 10 ? "0" + ss : ss;
      return `${mm}:${ss}`;
    },
    //获取初始化信息
    getInit() {
      if (!this.myvideo) {
        //获取失败,显示0
        this.totalTime = this.formatTime(0);
      } else {
        //获取视频总时长
        this.totalTime = this.formatTime(this.myvideo.duration);
      }
    },
    //播放时显示当前播放时间和总时长
    handlerNowTime() {
      if (!this.myvideo) {
        this.nowTime = `${this.formatTime(0)}`;
      }
      this.nowTime = this.formatTime(this.myvideo.currentTime || 0);
    },
    //返回
    goBack() {
      this.$router.push({ name: "Course" });
    },
    //播放课程
    playLesson() {
      alert("播放视频!");
      //this.myvideo.sec = mp4;
    },
    // 初始化时播放的视频
    initplay(){
      // 1.在课程信息中查找即将播放的视频小节的编号
      for(let i = 0; i < this.course.courseSections.length; i++){
        let section = this.course.courseSections[i];
        for(let j = 0; j < section.courseLessons.length; j++){
          let lesson = section.courseLessons[j]; // 每节课
          if(lesson.courseMedia != null){
            if(this.lessonid == lesson.courseMedia.lessonId){
              console.log("视频地址:" + lesson.courseMedia.fileEdk);
              this.lessonName = lesson.theme;
              // 2.将小节视频的地址 赋值 给播放器,进行播放
              this.myvideo.src = lesson.courseMedia.fileEdk;
              return;
            }            
          }
        }
      }
    },
  },
};
</script>
<div class="content-container">

    <!-- 第一章 开始 -->
    <div v-for="section in course.courseSections.slice(0,1)">
        <div class="content-label">
            <div class="content-label-title single-line">{{section.sectionName}}</div>
            <img
                 class="arrow-icon"
                 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAGFBMVEVHcEz///8dS1W+AAAAB3RSTlMAgwWyMeGgBsFrrQAAAFVJREFUKM9jYBiugMUBQ0i8EF2EsbxcAF1ReXkhuqJiczRl4uVGyqjKgIoUmFCVARUxMKAoAyliYEBRBlaEqiwcpAikrBShKglCqyFUMcEYCgwjBAAAeaoQrHtg6QoAAAAASUVORK5CYII="
                 alt=""
                 />
        </div>

        <div class="content-sections">
            <!-- 未播放的视频 -->
            <div class="content-section" v-for="(lesson,index) in section.courseLessons" :key="index">
                <!-- 第一章,前两节 -->
                <div v-if="index < 2">
                    <div class="section-item clearfix">
                        <span class="kw-icon-video section-type-icon fl"><i class="el-icon-video-play"></i></span>
                        <span class="section-dec need-update">{{lesson.theme}}</span>
                        <span class="section-status-icon pause-play">试看</span>
                    </div>
                    <div class="section-duration">
                        <span v-if="lesson.courseMedia !=null">时长:{{lesson.courseMedia.duration}}</span>
                        <span v-else="lesson.courseMedia ==null">时长:无媒体文件</span>
                    </div>
                </div>
                <div v-if="index > 1">
                    <div class="section-item clearfix">
                        <span class="kw-icon-video section-type-icon fl"><i class="el-icon-video-play"></i></span>
                        <span class="section-dec need-update">{{lesson.theme}}</span>
                        <!-- 未登录 => 锁 -->
                        <span v-if="!isLogin" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,未购买 => 锁 -->
                        <span v-else-if="isLogin && !isBuy" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,已购买 => 播放 -->
                        <span v-else class="section-status-icon pause-play">播放</span>
                    </div>
                    <div class="section-duration">
                        <span v-if="lesson.courseMedia !=null">时长:{{lesson.courseMedia.duration}}</span>
                        <span v-else="lesson.courseMedia ==null">时长:无媒体文件</span>
                    </div>
                </div>                    
            </div>
        </div>

    </div>
    <!-- 第一章 结束 -->

    <!-- 其余章 开始 -->
    <div v-for="section in course.courseSections.slice(1,course.courseSections.length)">
        <div class="content-label">
            <div class="content-label-title single-line">{{section.sectionName}}</div>
            <img
                 class="arrow-icon"
                 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAGFBMVEVHcEz///8dS1W+AAAAB3RSTlMAgwWyMeGgBsFrrQAAAFVJREFUKM9jYBiugMUBQ0i8EF2EsbxcAF1ReXkhuqJiczRl4uVGyqjKgIoUmFCVARUxMKAoAyliYEBRBlaEqiwcpAikrBShKglCqyFUMcEYCgwjBAAAeaoQrHtg6QoAAAAASUVORK5CYII="
                 alt=""
                 />
        </div>

        <div class="content-sections">
            <!-- 未播放的视频 -->
            <div class="content-section" v-for="(lesson,index) in section.courseLessons" :key="index">
                <div>
                    <div class="section-item clearfix">
                        <span class="kw-icon-video section-type-icon fl"><i class="el-icon-video-play"></i></span>
                        <span class="section-dec need-update">{{lesson.theme}}</span>
                        <!-- 未登录 => 锁 -->
                        <span v-if="!isLogin" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,未购买 => 锁 -->
                        <span v-else-if="isLogin && !isBuy" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,已购买 => 播放 -->
                        <span v-else class="section-status-icon pause-play">播放</span>
                    </div>
                    <div class="section-duration">
                        <span v-if="lesson.courseMedia !=null">时长:{{lesson.courseMedia.duration}}</span>
                        <span v-else="lesson.courseMedia ==null">时长:无媒体文件</span>
                    </div>
                </div>                    
            </div>
        </div>

    </div>
    <!-- 其余章 结束 -->

</div>

12、当前播放的视频高亮突出

<div class="content-container">

    <!-- 第一章 开始 -->
    <div v-for="section in course.courseSections.slice(0,1)">
        <div class="content-label">
            <div class="content-label-title single-line">{{section.sectionName}}</div>
            <img
                 class="arrow-icon"
                 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAGFBMVEVHcEz///8dS1W+AAAAB3RSTlMAgwWyMeGgBsFrrQAAAFVJREFUKM9jYBiugMUBQ0i8EF2EsbxcAF1ReXkhuqJiczRl4uVGyqjKgIoUmFCVARUxMKAoAyliYEBRBlaEqiwcpAikrBShKglCqyFUMcEYCgwjBAAAeaoQrHtg6QoAAAAASUVORK5CYII="
                 alt=""
                 />
        </div>

        <!-- 第一章 开始 -->
        <div class="content-sections">
            <div :class="{ 
                         'content-section': lesson.id != lessonid, 
                         'content-section content-section-choose': lesson.id == lessonid, 
                         }"
                 v-for="(lesson,index) in section.courseLessons" :key="index">
                <!-- 第一章,前两节 -->
                <div v-if="index < 2">
                    <div class="section-item clearfix">
                        <span :class="{ 
                                      'kw-icon-video section-type-icon': lesson.id != lessonid,
                                      'kw-icon-video section-type-icon lv': lesson.id == lessonid,
                                      }"><i class="el-icon-video-play"></i></span>
                        <span :class="{
                                      'section-dec': lesson.id != lessonid,
                                      'section-dec lv': lesson.id == lessonid,
                                      }">{{lesson.theme}}</span>
                        <span v-if="lesson.id != lessonid" class="section-status-icon pause-play">试看</span>
                        <span v-else class="section-status-icon pause-play"></span>
                    </div>
                    <div class="section-duration">
                        <span v-if="lesson.courseMedia !=null">时长:{{lesson.courseMedia.duration}}</span>
                        <span v-else="lesson.courseMedia ==null">时长:无媒体文件</span>
                    </div>
                </div>
                <div v-if="index > 1">
                    <div class="section-item clearfix">
                        <span :class="{ 
                                      'kw-icon-video section-type-icon':lesson.id != lessonid,
                                      'kw-icon-video section-type-icon lv':lesson.id == lessonid,
                                      }"><i class="el-icon-video-play"></i></span>
                        <span :class="{
                                      'section-dec': lesson.id != lessonid,
                                      'section-dec lv': lesson.id == lessonid,
                                      }">{{lesson.theme}}</span>
                        <!-- 未登录 => 锁 -->
                        <span v-if="!isLogin" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,未购买 => 锁 -->
                        <span v-else-if="isLogin && !isBuy" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,已购买 => 播放 -->
                        <span v-else-if="lesson.id != lessonid" class="section-status-icon pause-play">播放</span>
                        <span v-else-if="lesson.id == lessonid" class="section-status-icon pause-play"></span>
                    </div>
                    <div class="section-duration">
                        <span v-if="lesson.courseMedia !=null">时长:{{lesson.courseMedia.duration}}</span>
                        <span v-else="lesson.courseMedia ==null">时长:无媒体文件</span>
                    </div>
                </div>                    
            </div>
        </div>

    </div>
    <!-- 第一章 结束 -->

    <!-- 其余章 开始 -->
    <div v-for="section in course.courseSections.slice(1,course.courseSections.length)">
        <div class="content-label">
            <div class="content-label-title single-line">{{section.sectionName}}</div>
            <img
                 class="arrow-icon"
                 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkBAMAAAATLoWrAAAAGFBMVEVHcEz///8dS1W+AAAAB3RSTlMAgwWyMeGgBsFrrQAAAFVJREFUKM9jYBiugMUBQ0i8EF2EsbxcAF1ReXkhuqJiczRl4uVGyqjKgIoUmFCVARUxMKAoAyliYEBRBlaEqiwcpAikrBShKglCqyFUMcEYCgwjBAAAeaoQrHtg6QoAAAAASUVORK5CYII="
                 alt=""
                 />
        </div>

        <div class="content-sections">
            <!-- 未播放的视频 -->
            <div :class="{ 
                         'content-section':lesson.id != lessonid, 
                         'content-section content-section-choose':lesson.id == lessonid, 
                         }" v-for="(lesson,index) in section.courseLessons" :key="index">
                <div>
                    <div class="section-item clearfix">
                        <span :class="{ 
                                      'kw-icon-video section-type-icon':lesson.id != lessonid,
                                      'kw-icon-video section-type-icon lv':lesson.id == lessonid,
                                      }"><i class="el-icon-video-play"></i></span>
                        <span :class="{
                                      'section-dec': lesson.id != lessonid,
                                      'section-dec lv': lesson.id == lessonid,
                                      }">{{lesson.theme}}</span>
                        <!-- 未登录 => 锁 -->
                        <span v-if="!isLogin" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,未购买 => 锁 -->
                        <span v-else-if="isLogin && !isBuy" class="section-status-icon pause-play">未解锁</span>
                        <!-- 已登录,已购买 => 播放 -->
                        <span v-else-if="lesson.id != lessonid" class="section-status-icon pause-play">播放</span>
                        <span v-else-if="lesson.id == lessonid" class="section-status-icon pause-play"></span>
                    </div>
                    <div class="section-duration">
                        <span v-if="lesson.courseMedia !=null">时长:{{lesson.courseMedia.duration}}</span>
                        <span v-else="lesson.courseMedia ==null">时长:无媒体文件</span>
                    </div>
                </div>                    
            </div>
        </div>

    </div>
    <!-- 其余章 结束 -->

</div>

13、点击课标题切换视频并播放

<div :class="{ 
     'content-section': lesson.id != lessonid, 
     'content-section content-section-choose': lesson.id == lessonid, 
  }"
     v-for="(lesson,index) in section.courseLessons" 
     :key="index" 
     @click="playLesson(lesson)"
>
<script>
  methods: {
    //播放课程
    playLesson(status,index,lesson) {      
      if(lesson.courseMedia == null){
          this.$message.error("播放失败,暂无视频!");
      }else{
        // 试看的可以跳转播放页面
        if(status == 1 && index < 2){
          this.playNow(lesson);
        }else{ // 锁上的,先验证是否登录
          if(!this.isLogin){
              this.$message.success("请先登录!");
          }else{ // 登录后,再验证是否购买过
            if(!this.isBuy){
              this.$message.warning("请购买并解锁,才能观看本视频!");
            }else{
              this.playNow(lesson);
            }
          }
        }
      }
    },
    // 立刻播放
    playNow(lesson){
      this.lessonid = lesson.id;  // 当前播放的视频就是我点击的课
      this.myvideo.src = lesson.courseMedia.fileEdk;  // 切换播放器的播放地址
      this.myvideo.play();  // 立刻播放
      this.isplay = true; // 按钮呈现为播放状态
    },
};
</script>

14、留言点赞

14.1 修改dao
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class CourseComment implements Serializable {
    private static final long serialVersionUID = 922554392538715061L;
    
    // 一条留言:N个点赞
    private List<CourseCommentFavoriteRecord> favoriteRecords;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.CourseCommentDao">

    <resultMap type="com.lagou.entity.CourseComment" id="commentMap">
        <result property="id" column="cc_id" jdbcType="VARCHAR"/>
        <result property="courseId" column="course_id" jdbcType="INTEGER"/>
        <result property="sectionId" column="section_id" jdbcType="INTEGER"/>
        <result property="lessonId" column="lesson_id" jdbcType="INTEGER"/>
        <result property="userId" column="cc_user_id" jdbcType="INTEGER"/>
        <result property="userName" column="user_name" jdbcType="VARCHAR"/>
        <result property="parentId" column="parent_id" jdbcType="INTEGER"/>
        <result property="isTop" column="is_top" jdbcType="INTEGER"/>
        <result property="comment" column="comment" jdbcType="VARCHAR"/>
        <result property="likeCount" column="like_count" jdbcType="INTEGER"/>
        <result property="isReply" column="is_reply" jdbcType="INTEGER"/>
        <result property="type" column="type" jdbcType="INTEGER"/>
        <result property="status" column="status" jdbcType="INTEGER"/>
        <result property="createTime" column="cc_create_time" jdbcType="TIMESTAMP"/>
        <result property="updateTime" column="cc_update_time" jdbcType="TIMESTAMP"/>
        <result property="isDel" column="cc_is_del" jdbcType="INTEGER"/>
        <result property="lastOperator" column="last_operator" jdbcType="INTEGER"/>
        <result property="isNotify" column="is_notify" jdbcType="INTEGER"/>
        <result property="markBelong" column="mark_belong" jdbcType="INTEGER"/>
        <result property="replied" column="replied" jdbcType="INTEGER"/>

        <!--N个点赞-->
        <collection property="favoriteRecords" ofType="com.lagou.entity.CourseCommentFavoriteRecord">
            <result property="id" column="ccfr_id" jdbcType="OTHER"/>
            <result property="userId" column="ccfr_user_id" jdbcType="INTEGER"/>
            <result property="commentId" column="comment_id" jdbcType="INTEGER"/>
            <result property="isDel" column="ccfr_is_del" jdbcType="OTHER"/>
            <result property="createTime" column="ccfr_create_time" jdbcType="TIMESTAMP"/>
            <result property="updateTime" column="ccfr_update_time" jdbcType="TIMESTAMP"/>
        </collection>
    </resultMap>

    <!--查询某门课程的全部留言(分页)-->
    <select id="getCommentByCourseId" resultMap="commentMap">
        select
        cc.id cc_id,`course_id`,`section_id`,`lesson_id`,cc.user_id cc_user_id,`user_name`,`parent_id`,`is_top`,`comment`,`like_count`,`is_reply`,`type`,`status`,cc.create_time cc_create_time,cc.update_time cc_update_time,cc.is_del cc_id_del,`last_operator`,`is_notify`,`mark_belong`,`replied`,
        ccfr.id ccfr_id,ccfr.user_id ccfr_user_id,comment_id,ccfr.is_del ccfr_is_del,ccfr.create_time ccfr_create_time,ccfr.update_time ccfr_update_time
        from course_comment cc left join course_comment_favorite_record ccfr
            on cc.id = ccfr.comment_id
            where cc.is_del = 0
            and course_id = #{courseId}
            order by is_top desc , like_count desc , cc.create_time desc
            limit #{offset}, #{pageSize}
    </select>
</mapper>
@Test
public void getComment(){
    List<CourseComment> list = courseCommentDao.getCommentByCourseId(7, 1, 20);
    for (CourseComment comment : list){
        System.out.println("【"+ comment.getUserName() + "】" + "留言:" + comment.getComment());
        for (CourseCommentFavoriteRecord fr : comment.getFavoriteRecords()) {
            System.out.println("---->" + fr.getId());
        }
    }
}
14.2 修改controller
@GetMapping("comment/getCourseCommentList/{courseId}/{pageIndex}/{pageSize}")
public List<CourseComment> getCommentByCourseId(@PathVariable("courseId") Integer courseId,@PathVariable("pageIndex") Integer pageIndex,@PathVariable("pageSize") Integer pageSize){
    int offset = (pageIndex-1)*pageSize;
    List<CourseComment> list = commentService.getCommentByCourseId(courseId, offset, pageSize);
    System.out.println("获取第" + courseId +"门课程的留言:共计" + list.size() + "条");
    return list;
}
14.3 页面点赞之后的显示样式
<!-- 留言板 开始-->
<div class="message">
    <div class="message-topic">
        <div class="message-topic-title normal-font">精选留言</div>
    </div>
    <div>
        <div class="message-edit">
            <textarea rows="20" style="border:none;resize: none;" 
                      contenteditable="true"
                      placeholder="分享学习心得、思考感悟或者给自己一个小鼓励吧!"
                      class="edit-div pcStyle"
                      v-model="comment"
                      ></textarea>
        </div>    
    </div>
    <div class="message-edit-footer flex">
        <button class="message-edit-btn disableBg" >发表留言</button>
    </div>

    <!-- 留言 开始 -->
    <div class="message-list" v-for="(comment , index) in commentList" :key="index">
        <div class="message-list-title">
            <div class="message-list-title-left">
                <div class="message-list-title-left-name">{{comment.userName}}</div>
                <div class="message-list-title-left-tag"></div>
            </div>

            <!-- {{JSON.stringify(comment.favoriteRecords).indexOf( "100030017" )}} -->
            <!-- 已赞 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAJFBMVEVHcEwAuI4AtIsAtIsAtIoAtYwAtIsAtYwAtowAx5kAtIsAs4qd4c1kAAAAC3RSTlMAGMfz3VGnbTYIhXtDq8EAAAETSURBVDjLldWhb8JAFAbwg8CGhGSGzNQtULNkWUioWbJkpmYKAQaBITPLLKZysmLz+xfolUL6/XM72msh9N2X8ImaX17vrq+vVeqU39XTuK/kdEMA01jGDY4ZyYWFIRPxp0S8u+8KeBJ+WDxIGFrMJbQm7qhVYSJgr8JUwNsKtYDtCiHgPavcsDVDstsuyDk7NeYDk6G8pM3bWXNawQUijWq8QyPPpQy+50ET9bF09go5pus3cMV0feFEc2DfieZRBU7Up7fjSkwZZgz3DA8MHxl6DP8YRgxjgokimDHcMdwyfGG4ZPjJMCKoY4KJIpgz3DHcMvQYFuPpk/005t1mffHlOs/ETvxXAA0Ul3oQHsp/xD93wxfHcC4VkwAAAABJRU5ErkJggg== -->
            <div v-if="JSON.stringify(comment.favoriteRecords).indexOf( user.content.id ) >= 0" class="message-list-title-right">
                <img class="message-list-title-right-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAJFBMVEVHcEwAuI4AtIsAtIsAtIoAtYwAtIsAtYwAtowAx5kAtIsAs4qd4c1kAAAAC3RSTlMAGMfz3VGnbTYIhXtDq8EAAAETSURBVDjLldWhb8JAFAbwg8CGhGSGzNQtULNkWUioWbJkpmYKAQaBITPLLKZysmLz+xfolUL6/XM72msh9N2X8ImaX17vrq+vVeqU39XTuK/kdEMA01jGDY4ZyYWFIRPxp0S8u+8KeBJ+WDxIGFrMJbQm7qhVYSJgr8JUwNsKtYDtCiHgPavcsDVDstsuyDk7NeYDk6G8pM3bWXNawQUijWq8QyPPpQy+50ET9bF09go5pus3cMV0feFEc2DfieZRBU7Up7fjSkwZZgz3DA8MHxl6DP8YRgxjgokimDHcMdwyfGG4ZPjJMCKoY4KJIpgz3DHcMvQYFuPpk/005t1mffHlOs/ETvxXAA0Ul3oQHsp/xD93wxfHcC4VkwAAAABJRU5ErkJggg==" alt="">
                <div class="message-list-title-right-praise">{{comment.likeCount}}</div>
            </div>
            <!-- 没点过赞 -->
            <div v-else class="message-list-title-right">
                <img class="message-list-title-right-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAKlBMVEVHcExnZ2dzc3NmZmZqampmZmZmZmZnZ2dnZ2dnZ2dmZmZoaGhmZmZmZmZl+8SAAAAADXRSTlMA/AbsFtilbj5YwSqJPyESoQAAAZxJREFUOMt1lTtLA1EQha8xRhPTBEmhuKCCoNgoIlYLMcRKBG0sxIUgCDaBSDohEO0FEbQyIBZaBazERvAPWCwxPnP+i3tnrlGTmVPswn73NXNm7hrzq9m9kZ2ckTUUABifkOEBrK7liR7BMRFOA/uFc+BUgnV8mFisEW5IsIFi9FzBuwR91KJnAm8S9EIbxSBeBRZHk86MrBQJWjymJUC3nlugSyk+SQyhANfxos+s4krfM0DZvmbw2cuSCHNGi3PAfUygXYiU79ryyw1ibf0xZ9intBsz6SBadx24iiZXz8kPxCiTtYdLPzKTVFkkLQAZO/VikwYW/x/wHohcT/MiPQE8W9frxJrlbpiw4xvA0vbNmWyhj2Nrhmy+B7nEyTsN0rIaJAc0SDWqwX7rhAYfMa/Dui0bDZbwZAwUGNjWUWActnUUyN2hwDTaOkxRaSiwj6pRhjHKgTazSkWlwBK1jgIpBwrkHCgwyZ0oQ86BAjkHCjziG0KE8YBvCA/5KacOm6sgrHFAotouT6J23bkkLbsNDjM9yt7yP+IbQYga5De+eBMAAAAASUVORK5CYII=" alt="">
                <div class="message-list-title-right-praise">{{comment.likeCount}}</div>
            </div>
        </div>
        <div class="message-list-content">
            {{comment.comment}}
        </div>
        <!--删除留言(必须登录才能删除自己的)-->
        <!--
<div class="message-delete pointer">
<img class="message-delete-icon" src="https://img-blog.csdnimg.cn/2022010702583723148.png" alt="">删除
</div>
-->
    </div>
    <!-- 留言 结束 -->
</div>
<!-- 留言板 结束-->
14.4 页面调用点赞和取消赞
<!-- 留言板 开始-->
<div class="message">
    <div class="message-topic">
        <div class="message-topic-title normal-font">精选留言</div>
    </div>
    <div>
        <div class="message-edit">
            <textarea rows="20" style="border:none;resize: none;" 
                      contenteditable="true"
                      placeholder="分享学习心得、思考感悟或者给自己一个小鼓励吧!"
                      class="edit-div pcStyle"
                      v-model="comment"
                      ></textarea>
        </div>    
    </div>
    <div class="message-edit-footer flex">
        <button class="message-edit-btn disableBg" >发表留言</button>
    </div>

    <!-- 留言 开始 -->
    <div class="message-list" v-for="(comment , index) in commentList" :key="index">
        <div class="message-list-title">
            <div class="message-list-title-left">
                <div class="message-list-title-left-name">{{comment.userName}}</div>
                <div class="message-list-title-left-tag"></div>
            </div>

            <!-- {{JSON.stringify(comment.favoriteRecords).indexOf( "100030017" )}} -->
            <!-- 已赞 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAJFBMVEVHcEwAuI4AtIsAtIsAtIoAtYwAtIsAtYwAtowAx5kAtIsAs4qd4c1kAAAAC3RSTlMAGMfz3VGnbTYIhXtDq8EAAAETSURBVDjLldWhb8JAFAbwg8CGhGSGzNQtULNkWUioWbJkpmYKAQaBITPLLKZysmLz+xfolUL6/XM72msh9N2X8ImaX17vrq+vVeqU39XTuK/kdEMA01jGDY4ZyYWFIRPxp0S8u+8KeBJ+WDxIGFrMJbQm7qhVYSJgr8JUwNsKtYDtCiHgPavcsDVDstsuyDk7NeYDk6G8pM3bWXNawQUijWq8QyPPpQy+50ET9bF09go5pus3cMV0feFEc2DfieZRBU7Up7fjSkwZZgz3DA8MHxl6DP8YRgxjgokimDHcMdwyfGG4ZPjJMCKoY4KJIpgz3DHcMvQYFuPpk/005t1mffHlOs/ETvxXAA0Ul3oQHsp/xD93wxfHcC4VkwAAAABJRU5ErkJggg== -->
            <div @click="cancelzan(comment)" v-if="JSON.stringify(comment.favoriteRecords).indexOf( user.content.id ) >= 0" class="message-list-title-right">
                <img class="message-list-title-right-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAJFBMVEVHcEwAuI4AtIsAtIsAtIoAtYwAtIsAtYwAtowAx5kAtIsAs4qd4c1kAAAAC3RSTlMAGMfz3VGnbTYIhXtDq8EAAAETSURBVDjLldWhb8JAFAbwg8CGhGSGzNQtULNkWUioWbJkpmYKAQaBITPLLKZysmLz+xfolUL6/XM72msh9N2X8ImaX17vrq+vVeqU39XTuK/kdEMA01jGDY4ZyYWFIRPxp0S8u+8KeBJ+WDxIGFrMJbQm7qhVYSJgr8JUwNsKtYDtCiHgPavcsDVDstsuyDk7NeYDk6G8pM3bWXNawQUijWq8QyPPpQy+50ET9bF09go5pus3cMV0feFEc2DfieZRBU7Up7fjSkwZZgz3DA8MHxl6DP8YRgxjgokimDHcMdwyfGG4ZPjJMCKoY4KJIpgz3DHcMvQYFuPpk/005t1mffHlOs/ETvxXAA0Ul3oQHsp/xD93wxfHcC4VkwAAAABJRU5ErkJggg==" alt="">
                <div class="message-list-title-right-praise">{{comment.likeCount}}</div>
            </div>
            <!-- 没点过赞 -->
            <div @click="zan(comment)" v-else class="message-list-title-right">
                <img class="message-list-title-right-icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4BAMAAABaqCYtAAAAKlBMVEVHcExnZ2dzc3NmZmZqampmZmZmZmZnZ2dnZ2dnZ2dmZmZoaGhmZmZmZmZl+8SAAAAADXRSTlMA/AbsFtilbj5YwSqJPyESoQAAAZxJREFUOMt1lTtLA1EQha8xRhPTBEmhuKCCoNgoIlYLMcRKBG0sxIUgCDaBSDohEO0FEbQyIBZaBazERvAPWCwxPnP+i3tnrlGTmVPswn73NXNm7hrzq9m9kZ2ckTUUABifkOEBrK7liR7BMRFOA/uFc+BUgnV8mFisEW5IsIFi9FzBuwR91KJnAm8S9EIbxSBeBRZHk86MrBQJWjymJUC3nlugSyk+SQyhANfxos+s4krfM0DZvmbw2cuSCHNGi3PAfUygXYiU79ryyw1ibf0xZ9intBsz6SBadx24iiZXz8kPxCiTtYdLPzKTVFkkLQAZO/VikwYW/x/wHohcT/MiPQE8W9frxJrlbpiw4xvA0vbNmWyhj2Nrhmy+B7nEyTsN0rIaJAc0SDWqwX7rhAYfMa/Dui0bDZbwZAwUGNjWUWActnUUyN2hwDTaOkxRaSiwj6pRhjHKgTazSkWlwBK1jgIpBwrkHCgwyZ0oQ86BAjkHCjziG0KE8YBvCA/5KacOm6sgrHFAotouT6J23bkkLbsNDjM9yt7yP+IbQYga5De+eBMAAAAASUVORK5CYII=" alt="">
                <div class="message-list-title-right-praise">{{comment.likeCount}}</div>
            </div>
        </div>
        <div class="message-list-content">
            {{comment.comment}}
        </div>
        <!--删除留言(必须登录才能删除自己的)-->
        <!--
<div class="message-delete pointer">
<img class="message-delete-icon" src="https://img-blog.csdnimg.cn/2022010702583723148.png" alt="">删除
</div>
-->
    </div>
    <!-- 留言 结束 -->
</div>
<!-- 留言板 结束-->
<script>
  methods: {
    // 点赞
    zan(comment){
      return this.axios
      .get("http://localhost:8002/course/comment/saveFavorite/" + comment.id + "/" + this.user.content.id)
      .then((result) =>{
        // console.log(result);
        // 重新获取本门课的全部留言信息
        this.getComment();
      }).catch(error =>{
        this.$message.error("点赞失败!!!");
      });
    },
    // 取消赞
    cancelzan(comment){
      return this.axios
      .get("http://localhost:8002/course/comment/cancelFavorite/" + comment.id + "/" + this.user.content.id)
      .then((result) =>{
        // console.log(result);
        // 重新获取本门课的全部留言信息
        this.getComment();
      }).catch(error =>{
        this.$message.error("取消赞失败!!!");
      });
    },
  },
};
</script>
<!--查询某门课程的全部留言(分页)-->
<select id="getCommentByCourseId" resultMap="commentMap">
    select
    cc.id cc_id,`course_id`,`section_id`,`lesson_id`,cc.user_id cc_user_id,`user_name`,`parent_id`,`is_top`,`comment`,`like_count`,`is_reply`,`type`,`status`,cc.create_time cc_create_time,cc.update_time cc_update_time,cc.is_del cc_id_del,`last_operator`,`is_notify`,`mark_belong`,`replied`,
    ccfr.id ccfr_id,ccfr.user_id ccfr_user_id,comment_id,ccfr.is_del ccfr_is_del,ccfr.create_time ccfr_create_time,ccfr.update_time ccfr_update_time
    from course_comment cc left join course_comment_favorite_record ccfr
    on cc.id = ccfr.comment_id
    where cc.is_del = 0
    and ccfr.is_del = 0 <!-- 点赞表也要过滤掉被删除的数据 -->
    and course_id = #{courseId}
    order by is_top desc , like_count desc , cc.create_time desc
    limit #{offset}, #{pageSize}
</select>

15、发表留言

Course.vue

<script>
import Header from "./Header/Header"; //顶部登录条
import Footer from "./Footer/index"; //顶部登录条
export default {
  name: "Course",
  components: {
    Header,
    Footer,
  },
  data() {
    return {
      activeName: "intro",
      course:null,
      totalLessons:0, // 本门课程的总节数
      commentList:null, // 所有留言
      isLogin:false,  // 未登录
      isBuy:false,  // 未购买
      user:null,  // 当前用户
      myCourseList:[],  // 当前用户购买过的所有课程
      comment:null, // 待发表的留言内容
    };
  },
  methods: {
    // 发表留言
    saveComment(){
      return this.axios
      .get("http://localhost:8002/course/comment/saveCourseComment",{
        params:{
          courseid:this.courseid,
          userid:this.user.content.id,
          userName:this.user.content.name,
          comment:this.comment,
        }
      })
      .then((result) =>{
        // console.log(result);
        // 重新获取本门课的全部留言信息
        this.getComment();
      }).catch(error =>{
        this.$message.error("发表留言失败!!!");
      });
    },
};
</script>

web消费方

public interface CommentService {

    /**
     * 保存留言
     * @param comment 留言内容对象
     * @return 受影响的行数
     */
    Integer saveComment(CourseComment comment);
}
@RestController
@RequestMapping("course")
public class CommentController {

    @Reference // 远程消费
    private CommentService commentService;

    @GetMapping("comment/saveCourseComment")
    public Object saveCourseComment(Integer courseId,Integer userId,String userName,String comment) throws UnsupportedEncodingException {
        userName = new String(userName.getBytes("ISO-8859-1"),"UTF-8");
        comment = new String(comment.getBytes("ISO-8859-1"),"UTF-8");
        
        CourseComment courseComment = new CourseComment();
        courseComment.setCourseId(courseId); // 课程编号
        courseComment.setSectionId(0);    // 章节编号 (预留字段,为项目的2.0版本保留)
        courseComment.setLessonId(0);     // 小节编号 (预留字段,为项目的2.0版本保留)
        courseComment.setUserId(userId);   // 用户编号
        courseComment.setUserName(userName);  // 用户昵称
        courseComment.setParentId(0); // 没有父id (预留字段,为项目的2.0版本保留)
        courseComment.setComment(comment);  // 留言内容
        courseComment.setType(0); // 0用户留言 (预留字段,为项目的2.0版本保留)
        courseComment.setLastOperator(userId); // 最后操作的用户编号
        Integer i = commentService.saveComment(courseComment);
        System.out.println(i);
        return i;
    }
}

service提供方

<!--保存留言-->
<insert id="saveComment">
    insert  into `course_comment`(`course_id`,`section_id`,`lesson_id`,`user_id`,`user_name`,`parent_id`,`is_top`,`comment`,`like_count`,`is_reply`,`type`,`status`,`create_time`,`update_time`,`is_del`,`last_operator`,`is_notify`,`mark_belong`,`replied`) values
    (#{courseId},#{sectionId},#{lessonId},#{userId},#{userName},#{parentId},0,#{comment},0,0,#{type},0,sysdate(),sysdate(),0,#{lastOperator},1,0,0)
</insert>

微信登录-前后端

微信开放平台(针对开发者和公司) https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

1、准备工作

网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。

在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的AppID和AppSecret,申请微信登录且通过审核后,可开始接入流程。

注册帐号和申请应用都是免费的,必须要有一个线上的网站,才能审核通过(过程还是挺麻烦的),就可以使用微信的登录了

但是如果想使用微信支付的功能,就必须认证开发者资质(认证一次300块人民币)

2、名词解释

2.1 OAuth2.0协议

OAuth(Open Authorization)协议就是为用户资源的授权提供了一个安全、开放、简易的标准。

OAuth在第三方应用与服务提供商之间设置了一个授权层,第三方应用通过授权层获取令牌,再通过令牌获取信息。

比如:皇宫大院,并不是每一块区域你都可以去溜达的。一个小奴才专门负责打扫后宫的寝室卫生,后宫门口有N多带刀侍卫,每次进门工作。都要问皇上,因为想进入到后宫内院,只有皇帝一个人说的算,皇帝让谁进,谁才能进。但是每次问皇上呢,又太累,所以“令牌”出现了,皇上命人制作了一些令牌,给打扫卫生的小太监每人一个,想进去,出示令牌给侍卫即可。这就是“宫廷版Oauth协议”。

玩抖音,发视频,抖音需要访问你相册的授权,话筒的授权,地理位置的授权等等

一句话,我不想帐号密码给第三方应用,但我还想用他们的功能,而他们的功能需要我的部分数据来协助。ok,咱玩令牌。

令牌与密码的作用都可以进入系统,但是有三点差异:

​ 1、令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改, 就不会发生变化。

​ 2、令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销

​ 3、令牌有权限范围,比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。

上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。

OAuth的四种授权模式:

​ 1、授权码模式(功能最完整、流程最严密的授权模式)

​ 说白了,授权码模式,不再client和user之间商量授权,而是client想要被授权,所以 client去找了一个和事佬大妈,大妈将client和user叫到了一起(认证服务器),给大妈个面子,这事就这么定了。就是这样的一个过程,全程中认证服务器会发布一个认证码贯穿始终。

​ 2、密码模式 - 了解

​ 3、简化模式 - 了解

​ 4、客户端模式 - 了解

2.2 AppID

应用ID,唯一标识(身份证号)

2.3 AppSecret

应用的密钥(密码)

2.4 code

授权的临时凭证(例如:临时身份证)

2.5 access_token

接口调用凭证(例如:真正的身份证,虎符,令牌)

3、登录授权时序图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HTSNC55G-1634474311284)(E:\MarkDown\拉勾笔记\微信登录授权时序图)]

4、开发步骤

4.1 vue项目安装

微信官方提供的生成二维码的js

npm install vue-wxlogin

如果不是vue的项目,可以直接引用官方提供的js文件,来生成二维码

4.2 页面引入
<!-- 登录框 开始-->
<el-dialog
           style="width:800px;margin:0px auto;"
           title=""
           :visible.sync="dialogFormVisible">
    <div id="loginForm">
        <el-form>
            <el-form-item>
                <h1 style="font-size:30px;color:#00B38A">拉勾</h1>
            </el-form-item>
            <el-form-item>
                <el-input v-model="phone" placeholder="请输入常用手机号..."></el-input>
            </el-form-item>
            <el-form-item>
                <el-input v-model="password" placeholder="请输入密码..."></el-input>
            </el-form-item>
        </el-form>
        <el-button
                   style="width:100%;margin:0px auto;background-color: #00B38A;font-size:20px"
                   type="primary"
                   @click="login">确 定</el-button>
        <p></p>
        <!-- 微信登录图标 -->
        <img
             @click="goToLoginWX"
             src="http://www.lgstatic.com/lg-passport-fed/static/pc/modules/common/img/icon-wechat@2x_68c86d1.png"
             alt=""
             />
    </div>

    <!-- 二维码 -->
    <wxlogin id="wxLoginForm" style="display:none"
             :appid="appid" :scope="scope" :redirect_uri="redirect_uri">
    </wxlogin>

</el-dialog>
<!-- 登录框 结束-->

<script>
import wxlogin from 'vue-wxlogin'; // 引入

export default {
    name: "Header",
    components:{
        wxlogin // 声明引用的组件
},
data() {
    return {
      isLogin: false, // 登录状态,true:已登录,false:未登录
      userDTO:null, // 用来保存登录的用户信息
      isHasNewMessage: false, // 是否有新的推送消息
      dialogFormVisible: false, // 是否显示登录框,true:显示,false:隐藏
      phone: "", // 双向绑定表单 手机号
      password: "", // 双向绑定表单 密码
      appid:"wxd99431bbff8305a0", // 应用唯一标识,在微信开放平台提交应用审核通过后获得
      scope:"snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可
      redirect_uri:"http://www.pinzhi365.com/wxlogin", //重定向地址,(回调地址)
    };
},
methods: {
    goToLoginWX() {
      // 普通的登录表单隐藏
      document.getElementById("loginForm").style.display = "none";
      // 显示二维码
      document.getElementById("wxLoginForm").style.display = "block";
    },
},
</script>
4.3 修改hosts文件

文件位置:C:\Windows\System32\drivers\etc\hosts

回调默认指定的是80端口,别忘记将tomcat的8002端口修改成80

127.0.0.1 www.pinzhi365.com
4.4 依赖
<!-- 需要使用HttpServletRequest获得参数 -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.4</version>
    <scope>provided</scope>
</dependency>
<!-- 需要使用HttpClient发出请求 -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>
4.5 封装HttpClient
/**
 * @auther wei
 * @date 2021/10/15 14:49
 * @description HttpClient的封装工具类
 */
public class HttpClientUtil {

    public static String doGet(String url){
        return doGet(url,null);
    }

    /**
     * get请求,支持request请求方式,不支持restfull方式
     * @param url 请求地址
     * @param param 参数
     * @return 响应的字符串
     */
    public static String doGet(String url, Map<String,String> param){
        // 创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        String resultString = null;
        CloseableHttpResponse response = null;
        try {
            // 创建url
            URIBuilder builder = new URIBuilder(url);
            if (param != null){
                // 在url后面拼接参数
                for (String key : param.keySet()) {
                    builder.addParameter(key,param.get(key));
                }
            }
            URI uri = builder.build();

            // 创建http get请求
            HttpGet httpGet = new HttpGet(uri);

            // 执行请求
            response = httpClient.execute(httpGet);

            // 从响应对象中获取状态码(成功或失败的状态)
            int statusCode = response.getStatusLine().getStatusCode();
            System.out.println("响应的状态 = " + statusCode);
            // 200:表示响应成功
            if (statusCode == 200){
                // 响应的内容字符串
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            try{
                if (response != null){
                    response.close();
                }
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return resultString;
    }
}
4.6 定义从微信返回的数据对象
/**
 * @auther wei
 * @date 2021/10/15 17:14
 * @description 令牌实体类
 */
public class Token {
    private String access_token;//接口调用凭证
    private String expires_in; //access_token接口调用凭证超时时间,单位(秒)
    private String refresh_token;//用户刷新access_token
    private String openid; //授权用户唯一标识
    private String scope; //用户授权的作用域,使用逗号(,)分隔
    private String unionid; //当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。

    // 略
}
/**
 * @auther wei
 * @date 2021/10/15 17:27
 * @description 微信用户信息
 */
public class WxUser {

    private String openid;      // 普通用户的标识,对当前开发者帐号唯一
    private String nickname;    // 普通用户昵称
    private String sex;         // 普通用户性别,1为男性,2为女性
    private String province;    // 普通用户个人资料填写的省份
    private String city;        // 普通用户个人资料填写的城市
    private String country;     // 国家,如中国为CN
    private String headimgurl;  // 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
    private String privilege;   // 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
    private String unionid;     // 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
	
   	// 略
}
4.7 回调函数controller
@RestController
public class WxLoginController {

    @GetMapping("wxlogin")
    public Object wxlogin(HttpServletRequest request){
        // 1.微信官方发给我们一个临时凭证
        String code = request.getParameter("code");
        System.out.println("【临时凭证】code = " + code);

        // 2.通过code,去微信官方申请一个正式的token(令牌)
        String getTokenByCode_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code="+code+"&grant_type=authorization_code";
        String tokenString = HttpClientUtil.doGet(getTokenByCode_url);
        System.out.println(tokenString);

        // 3将json格式的token字符串转换成实体对象,方便存和取
        Token token = JSON.parseObject(tokenString, Token.class);

        // 4.通过token,去微信官方获取用户信息
        String getUserByToken_url = "https://api.weixin.qq.com/sns/userinfo?access_token="+token.getAccess_token()+"&openid="+token.getOpenid();
        String userInfoString = HttpClientUtil.doGet(getUserByToken_url);
        System.out.println("userInfoString = " + userInfoString);

        // 5.将json格式的user字符串转换成实体对象,方便存和取
        WxUser wxUser = JSON.parseObject(userInfoString, WxUser.class);
        System.out.println("微信昵称 = " +wxUser.getNickname());
        System.out.println("微信头像 = " +wxUser.getHeadimgurl());

        return wxUser;
    }
}

如果下面的错误:是因为谷歌浏览器有bug,待修复!切换别的浏览器即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJKnHRLd-1634474311289)(E:\MarkDown\拉勾笔记\微信登录bug.png)]

web服务的端口号必须是80!

4.8 后续

WxLoginController

@RestController
@RequestMapping("user")
public class WxLoginController {

    @Reference
    private UserService userService;

    private UserDTO dto = null; // 是否用微信登录成功,dto为null,则尚未登录

    @GetMapping("wxlogin")
    public String wxlogin(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 1.微信官方发给我们一个临时凭证
        String code = request.getParameter("code");
        System.out.println("【临时凭证】code = " + code);

        // 2.通过code,去微信官方申请一个正式的token(令牌)
        String getTokenByCode_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=wxd99431bbff8305a0&secret=60f78681d063590a469f1b297feff3c4&code="+code+"&grant_type=authorization_code";
        String tokenString = HttpClientUtil.doGet(getTokenByCode_url);
        System.out.println(tokenString);

        // 3将json格式的token字符串转换成实体对象,方便存和取
        Token token = JSON.parseObject(tokenString, Token.class);

        // 4.通过token,去微信官方获取用户信息
        String getUserByToken_url = "https://api.weixin.qq.com/sns/userinfo?access_token="+token.getAccess_token()+"&openid="+token.getOpenid();
        String userInfoString = HttpClientUtil.doGet(getUserByToken_url);
        System.out.println("userInfoString = " + userInfoString);

        // 5.将json格式的user字符串转换成实体对象,方便存和取
        WxUser wxUser = JSON.parseObject(userInfoString, WxUser.class);
        System.out.println("微信昵称 = " +wxUser.getNickname());
        System.out.println("微信头像 = " +wxUser.getHeadimgurl());

        // 拉勾的业务流程! 需要手机号(wxUser.getUnionid())和密码(wxUser.getUnionid()),头像和昵称
        //return "user/login?phone="+wxUser.getUnionid()+"&password="+wxUser.getUnionid()+"&nickname="+wxUser.getNickname()+"&headimg="+wxUser.getHeadimgurl();
        User user = null;
        dto = new UserDTO();

        // 检测手机号是否注册
        Integer i = userService.checkPhone(wxUser.getUnionid());
        if (i == 0){
            // 未注册,自动注册并登录
            userService.register(wxUser.getUnionid(),wxUser.getUnionid(),wxUser.getNickname(),wxUser.getHeadimgurl());
            dto.setMessage("手机号尚未注册,系统已帮您自动注册,请牢记密码!");
            user = userService.login(wxUser.getUnionid(), wxUser.getUnionid());
        }else {
            user = userService.login(wxUser.getUnionid(), wxUser.getUnionid());
            if (user == null){
                dto.setState(300);  // 300表示失败
                dto.setMessage("账号密码不匹配,登录失败!");
            }else {
                dto.setState(200);  // 200表示成功
                dto.setMessage("登录成功!");
            }
        }
        dto.setContent(user);

        // 重定向
        response.sendRedirect("http://localhost:8080");
        return null;
    }

    @GetMapping("checkWxStatus")
    public UserDTO checkWxStatus(){
        return this.dto;
    }

    @GetMapping("logout")
    public Object logout(){
        this.dto = null;
        return null;
    }
}

Header.vue

<script>
import wxlogin from 'vue-wxlogin'; // 引入

export default {
  name: "Header",
  components:{
    wxlogin // 声明引用的组件
  },

  props: {},
  data() {
    return {
      isLogin: false, // 登录状态,true:已登录,false:未登录
      userDTO:null, // 用来保存登录的用户信息
      isHasNewMessage: false, // 是否有新的推送消息
      dialogFormVisible: false, // 是否显示登录框,true:显示,false:隐藏
      phone: "", // 双向绑定表单 手机号
      password: "", // 双向绑定表单 密码
      appid:"wxd99431bbff8305a0", // 应用唯一标识,在微信开放平台提交应用审核通过后获得
      scope:"snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可
      redirect_uri:"http://www.pinzhi365.com/user/wxlogin", //重定向地址,(回调地址)
    };
  },
  computed: {
  },
  watch: {
  },
  mounted() {
  },
  created(){
    // 当刷新页面,组件创建成功之后,立刻检测本地储存中是否存在用户对象
    this.userDTO = JSON.parse(localStorage.getItem("user"));
    if(this.userDTO != null){
      this.isLogin = true;  // 已登录
    }else{
      // 去检测微信是否登录过
      this.axios
      .get("http://localhost:80/user/checkWxStatus")
      .then((result) =>{
        this.userDTO = (result.data);
        this.phone = this.userDTO.content.phone;
        this.password = this.userDTO.content.password;
        this.login(); // 走普通登录
      }).catch((error) =>{
        //this.$message.error("登录失败!!!");
      });
    }
  },
  methods: {
    logout(){ // 登出
      localStorage.setItem("user",null);  // 将登录成功的对象信息保存到本地储存中
      this.isLogin = false;  // 更新登录状态
      alert("谢谢使用,再见!!!");
      // 去检测微信是否登录过
      this.axios
      .get("http://localhost:80/user/logout")
      .then((result) =>{
      }).catch((error) =>{
        //this.$message.error("登录失败!!!");
      });
    }
  },
};
</script>
4.9 解决二维码在谷歌浏览器的bug

谷歌浏览器调试的时候,iframe标签跨域问题导致无法跳转的bug

如果iframe未添加sandbox属性,或者sandbox属性不赋值,就代表采用默认的安全策略

即:iframe的页面将会被当做一个独立的源,并且不能提交表单,不能执行javascript脚本,也不能让包含iframe的父页面导航到其他地方,所有的插件,如flash等也全部不能起作用

简单来说iframe就只剩下一个展示数据的功能,正如他的名字一样,所有的内容都被放进了一个 “单独的沙盒

sandbox包含的属性及作用:

属性值说明
allow-scripts允许运行执行脚本
allow-top-navigation允许iframe能够主导window.top进行页面跳转
allow-same-origin允许同域请求,比如ajax,storage
allow-forms允许进行提交表单
allow-popups允许iframe中弹出新窗口,比如,window.open,target=”_blank”
allow-pointer-lock在iframe中可以锁定鼠标,主要和鼠标锁定有关

加上 sandbox=“allow-scripts allow-top-navigation allow-same-origin” 属性,即可解决

官方js:http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js

无法修改微信服务器上的js文件,所以我们将js代码放在本地并进行修改:

created(){
    !(function(a, b, c) {
        function d(a) {
            var c = "default";
            a.self_redirect === !0
                ? (c = "true")
                : a.self_redirect === !1 && (c = "false");
            var d = b.createElement("iframe"),
            e =
                "https://open.weixin.qq.com/connect/qrconnect?appid=" +
                a.appid +
                "&scope=" +
                a.scope +
                "&redirect_uri=" +
                a.redirect_uri +
                "&state=" +
                a.state +
                "&login_type=jssdk&self_redirect=" +
                c +
                "&styletype=" +
                (a.styletype || "") +
                "&sizetype=" +
                (a.sizetype || "") +
                "&bgcolor=" +
                (a.bgcolor || "") +
                "&rst=" +
                (a.rst || "");
            (e += a.style ? "&style=" + a.style : ""),
            (e += a.href ? "&href=" + a.href : ""),
            (d.src = e),
            (d.frameBorder = "0"),
            (d.allowTransparency = "true"),
            (d.sandbox = "allow-scripts allow-top-navigation allow-same-origin"),
            // 允许多种请求
            (d.scrolling = "no"),
            (d.width = "300px"),
            (d.height = "400px");
            var f = b.getElementById(a.id);
            (f.innerHTML = ""), f.appendChild(d);
        }
        a.WxLogin = d;
    })(window, document);
}

Course.vue

<div id="wxLoginForm"></div>
methods: {
    // 微信登录
    goToLoginWX() {
        // 普通的登录表单隐藏
        document.getElementById("loginForm").style.display = "none";
        // 显示二维码的容器
        document.getElementById("wxLoginForm").style.display = "block";
       
        // 去生成二维码
        // 待dom更新之后再用二维码渲染其内容
        this.$nextTick(function(){
            this.createCode(); // 直接调用会报错:TypeError: Cannot read property
            'appendChild' of null
        });
    },
    // 生成二维码
    createCode(){
        var obj = new WxLogin({
            id:"wxLoginForm", // 显示二维码的容器
            appid: "wxd99431bbff8305a0", // 应用唯一标识,在微信开放平台提交应用审核通过后获得
            scope: "snsapi_login", // 应用授权作用域,网页应用目前仅填写snsapi_login即可
            redirect_uri: "http://www.pinzhi365.com/user/wxlogin", //重定向地址,(回调地址)
            href: "data:text/css;base64,base64加密后的样式"
        });
    },
}
.impowerBox .qrcode {width: 200px;}
.impowerBox .title {display: none;}
.impowerBox .info {width: 200px;}
.status_icon {display: none}cs
.impowerBox .status {text-align: center;}

我们用站长工具对样式代码进行base64加密:http://tool.chinaz.com/Tools/Base64.aspx

微信支付

1、创建二维码

1、安装 qrcodejs2 (注意:安装的是qrcodejs2,不要安装qrcode —> 会报错)

npm install qrcodejs2 --save

2、Course.vue页面中引入

<!-- 微信支付二维码 -->
<el-dialog :visible.sync="dialogFormVisible" style="width:800px;margin:0px auto;" >
    <h1 style="font-size:30px;color:#00B38A">微信扫一扫支付</h1>
    <div id="qrcode" style="width:210px;margin:20px auto;"></div>
</el-dialog>

<script>
import Header from "./Header/Header"; //顶部登录条
import Footer from "./Footer/index"; //顶部登录条
import QRCode from 'qrcodejs2'; // 引入qrcodejs

export default {
  name: "Course",
  components: {
    Header,
    Footer,
    QRCode, // 声明组件
  },
  data() {
    return {
      dialogFormVisible:false,  // 默认false:隐藏,true:显示
    };
  },
  methods: {
      // 购买课程
    buy(courseid) {
      //alert("购买第【" + courseid + "】门课程成功,加油!");
      this.dialogFormVisible = true;  // 显示提示框
      
      // 待dom更新之后再用二维码渲染其内容
      this.$nextTick(function(){
        this.createCode();  // 直接调用会报错:TypeError: Cannot read properties of undefined (reading 'appendChild')
      });
    },
    // 生成二维码
    createCode(){
      // QRCode(存放二维码的dom元素的id,二维码的属性参数)
      let qrcode = new QRCode('qrcode',{
        width:200,
        height:200,
        text:"我爱你中国" // 二维码中包含的信息
      });
    },
  }
};
</script>

2、准备工作

2.1 名词介绍
参数说明
appid微信公众帐号或开放平台APP的唯一标识
partner商户号(配置文件中的partner:账户)
partnerkey商户密钥(密码)
sign数字签名,根据微信官方提供的密钥和一套算法生成的一个加密信息,就是为了保 证交易安全

如果获得这些信息?

​ 需要注册认证公众号,费用300元/次

2.2 获取认证的流程
1) 注册公众号(类型:服务号)

根据营业执照类型选择以下主体注册:

​ 个体工商户 | 企业/公司 | 政府 | 媒体 | 其他类型

2) 认证公众号

公众号认证后才申请微信支付:300元/次

3) 提交材料申请微信支付

登录公众平台,左侧菜单【微信支付】,开始填写资料等待审核,审核时间1~5工作日

这里需要提交的资料有营业执照!

4) 开户成功,登录商户平台进行验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资 金数额,完成账户验证。

5) 在线签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

6) 查看自己的公众号的参数
public class PayConfig {
    //企业公众号ID
    public static String appid = "wx8397f8696b538317";
    // 财付通平台的商户帐号
    public static String partner = "1473426802";
    // 财付通平台的商户密钥
    public static String partnerKey = "8A627A4578ACE384017C997F12D68B23";
    // 回调URL
    public static String notifyurl ="http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify";
}

3、支付流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WZtI6lWh-1634474311291)(E:\MarkDown\拉勾笔记\微信支付流程)]

4、工具介绍

4.1 SDK

https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6HGFJvaB-1634474311297)(E:\MarkDown\拉勾笔记\微信支付开发文档)]

<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

主要使用sdk中的三个功能:

1、获取随机字符串(生成订单编号)

WXPayUtil.generateNonceStr();

2、将map转换成xml字符串(自动添加签名)

WXPayUtil.generateSignedXml(map,partnerKey);

3、将xml字符串转换整map

WXPayUtil.xmlToMap(result);
4.2 JFinal 框架

JFinal 是基于Java 语言的极速 web 开发框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展

取代HttpClient

<dependency>
    <groupId>com.jfinal</groupId>
    <artifactId>jfinal</artifactId>
    <version>3.5</version>
</dependency>

5、构建二维码

Course.vue

<script>
methods:{
    // 生成二维码
    createCode(){
      // 去获取支付链接
      this.axios
        .get("http://localhost:80/order/createCode",{
          params:{
            courseid:this.course.id,
            courseName:this.course.courseName,
            price:this.course.discounts
          }
        })
        .then((result) =>{
          console.log(result);
          // QRCode(存放二维码的dom元素的id,二维码的属性参数)
          let qrcode = new QRCode('qrcode',{
            width:200,
            height:200,
            text:result.data.code_url // 将返回的数据嵌入到二维码中
          });
        }).catch(error =>{
          this.$message.error("生成二维码失败!!!");
      });
    },
},
</script>

支付配置

public class PayConfig {
    // 企业公众号ID
    public static String appid = "wx8397f8696b538317";
    // 财付通平台的商户帐号
    public static String partner = "1473426802";
    // 财付通平台的商户密钥
    public static String partnerKey = "8A627A4578ACE384017C997F12D68B23";
    // 回调URL
    public static String notifyurl = "http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify";
}

createCodeController

/**
 * @auther wei
 * @date 2021/10/17 17:01
 * @description 微信支付相关控制
 */
@RestController
@RequestMapping("order")
public class WxPayController {

    @GetMapping("createCode")
    public Object createCode(String courseid,String courseName,String price) throws Exception {
        courseName = new String(courseName.getBytes("ISO-8859-1"),"UTF-8");
        // 1.编写商户信息
        HashMap<String,String> mm = new HashMap();
        mm.put("appid", PayConfig.appid);   // 公众号ID
        mm.put("mch_id",PayConfig.partner); // 商户号
        mm.put("nonce_str",WXPayUtil.generateNonceStr());  // 随机字符串
        mm.put("body",courseName);  // 商品描述
        mm.put("out_trade_no",WXPayUtil.generateNonceStr());  // 商户订单号
        mm.put("total_fee",price);   // 订单金额,订单总金额,单位为分,只能为整数
        mm.put("spbill_create_ip","127.0.0.1"); // 终端IP
        mm.put("notify_url",PayConfig.notifyurl);   // 通知地址
        mm.put("trade_type","NATIVE");    // 交易类型
        //System.out.println("商户信息:" + mm);

        // 2.生成数字签名,并把商户信息转换成xml格式
        String xml = WXPayUtil.generateSignedXml(mm, PayConfig.partnerKey);
        //System.out.println("商户的xml信息:" +xml);

        // 3.将xml数据发送给微信支付平台,从而生成订单
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        // 发送请求并返回一个xml格式的字符串
        String result = HttpKit.post(url, xml);

        // 4.微信平台返回xml格式的数据,将其转换为map格式并返回给前端
        Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
        //System.out.println("返回的xml,转换成map后:" +resultMap);
        resultMap.put("orderId",mm.get("out_trade_no"));
        
        return resultMap;
    }
}

解决商品描述乱码

courseName = new String(courseName.getBytes("ISO-8859-1"),"UTF-8");

6、检查支付状态

<!-- 微信支付二维码 -->
<el-dialog :visible.sync="dialogFormVisible" style="width:800px;margin:0px auto;" >
    <h1 style="font-size:30px;color:#00B38A">微信扫一扫支付</h1>
    <div id="qrcode" style="width:210px;margin:20px auto;"></div>
    <h2 id="statusText"></h2>
    <p id="closeText"></p>
</el-dialog>

<script>
  data() {
    return {
      activeName: "intro",
      course:null,
      totalLessons:0, // 本门课程的总节数
      commentList:null, // 所有留言
      isLogin:false,  // 未登录
      isBuy:false,  // 未购买
      user:null,  // 当前用户
      myCourseList:[],  // 当前用户购买过的所有课程
      comment:null, // 待发表的留言内容
      dialogFormVisible:false,  // 默认false:隐藏,true:显示
      time:null,  // 计时对象
    };
  },
  methods: {
    // 生成二维码
    createCode(){
      // 去获取支付链接
      this.axios
        .get("http://localhost:80/order/createCode",{
          params:{
            courseid:this.course.id,
            courseName:this.course.courseName,
            price:1 // 测试支付金额固定为1分钱,真实上线环境再改回此真实价格:this.course.discounts
          }
        })
        .then((result) =>{
          console.log(result);
          // QRCode(存放二维码的dom元素的id,二维码的属性参数)
          let qrcode = new QRCode('qrcode',{
            width:200,
            height:200,
            text:result.data.code_url // 将返回的数据嵌入到二维码中
          });
        }).catch(error =>{
          this.$message.error("生成二维码失败!!!");
      });

      // 检查支付状态
      this.axios
        .get("http://localhost:80/order/checkOrderStatus",{
          params:{
            orderId:result.data.orderId,  // 传递 订单编号 进行查询
          }
        })
        .then((result) =>{
          console.log(result);
          if(result.data.trade_state=="SUCCESS"){
            document.getElementById("statusText").innerHTML = "<i style='color:#00B38A' class='el-icon-success'></i> 支付成功";
            let s = 3;  // 倒计时的秒数
            this.closeQRForm(s); // 关闭二维码窗口
          }
        }).catch(error =>{
          this.$message.error("查询订单失败!!!");
      });
    },
    // 倒计时关闭二维码窗口
    closeQRForm(s){
      let that = this;
      this.time = setInterval(function(){
        document.getElementById("closeText").innerHTML = "( "+ s-- +" ) 秒后关闭本窗口";
        if(s == 0){
          clearInterval(that.time); // 停止计时器
          that.dialogFormVisible = false; // 二维码窗口隐藏
          that.isBuy = true;  // 修改购买状态(已购买)
          // 真实的修改数据库
        }
      },1000);
    },
</script>
@GetMapping("createCode")
public Object createCode(String courseid,String courseName,String price) throws Exception {
    courseName = new String(courseName.getBytes("ISO-8859-1"),"UTF-8");
    // 1.编写商户信息
    HashMap<String,String> mm = new HashMap();
    mm.put("appid", PayConfig.appid);   // 公众号ID
    mm.put("mch_id",PayConfig.partner); // 商户号
    mm.put("nonce_str",WXPayUtil.generateNonceStr());  // 随机字符串
    mm.put("body",courseName);  // 商品描述
    mm.put("out_trade_no",WXPayUtil.generateNonceStr());  // 商户订单号
    mm.put("total_fee",price);   // 订单金额,订单总金额,单位为分,只能为整数
    mm.put("spbill_create_ip","127.0.0.1"); // 终端IP
    mm.put("notify_url",PayConfig.notifyurl);   // 通知地址
    mm.put("trade_type","NATIVE");    // 交易类型
    //System.out.println("商户信息:" + mm);

    // 2.生成数字签名,并把商户信息转换成xml格式
    String xml = WXPayUtil.generateSignedXml(mm, PayConfig.partnerKey);
    //System.out.println("商户的xml信息:" +xml);

    // 3.将xml数据发送给微信支付平台,从而生成订单
    String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    // 发送请求并返回一个xml格式的字符串
    String result = HttpKit.post(url, xml);

    // 4.微信平台返回xml格式的数据,将其转换为map格式并返回给前端
    Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
    //System.out.println("返回的xml,转换成map后:" +resultMap);
    resultMap.put("orderId",mm.get("out_trade_no"));

    return resultMap;
}
@GetMapping("checkOrderStatus")
public Object checkOrderStatus(String orderId) throws Exception {
    // 1.编写商户信息
    HashMap<String,String> mm = new HashMap();
    mm.put("appid", PayConfig.appid);   // 公众号ID
    mm.put("mch_id",PayConfig.partner); // 商户号
    mm.put("out_trade_no",orderId);  // 商户订单号
    mm.put("nonce_str",WXPayUtil.generateNonceStr());  // 随机字符串

    // 2.生成数字签名
    String xml = WXPayUtil.generateSignedXml(mm, PayConfig.partnerKey);

    // 3.发送查询请求给微信支付平台
    String url = "https://api.mch.weixin.qq.com/pay/orderquery";

    // 查询订单状态的开始时间
    long beginTime = System.currentTimeMillis();
    // 不停的去微信后台询问是否支付
    while (true) {
        // 4.对微信支付平台返回的查询结果进行处理
        String result = HttpKit.post(url, xml);
        Map<String, String> resultMap = WXPayUtil.xmlToMap(result);

        // 已经支付成功
        if (resultMap.get("trade_state").equalsIgnoreCase("SUCCESS")){
            return resultMap;
        }

        // 超过30秒未支付,停止询问
        if (System.currentTimeMillis() - beginTime > 30000){
            return resultMap;
        }
        Thread.sleep(30000);    // 每隔3秒,询问一次微信支付平台
    }
}

7、保存订单并更新状态

<script>
  data() {
    return {
      activeName: "intro",
      course:null,
      totalLessons:0, // 本门课程的总节数
      commentList:null, // 所有留言
      isLogin:false,  // 未登录
      isBuy:false,  // 未购买
      user:null,  // 当前用户
      myCourseList:[],  // 当前用户购买过的所有课程
      comment:null, // 待发表的留言内容
      dialogFormVisible:false,  // 默认false:隐藏,true:显示
      time:null,  // 计时对象
      orderNo:"", // 订单编号
    };
  },
  methods: {
    // 生成二维码
    createCode(){
      // 去获取支付链接
      this.axios
        .get("http://localhost:80/order/createCode",{
          params:{
            courseid:this.course.id,
            courseName:this.course.courseName,
            price:1 // 测试支付金额固定为1分钱,真实上线环境再改回此真实价格:this.course.discounts
          }
        })
        .then((result) =>{
          console.log(result);
          // QRCode(存放二维码的dom元素的id,二维码的属性参数)
          let qrcode = new QRCode('qrcode',{
            width:200,
            height:200,
            text:result.data.code_url // 将返回的数据嵌入到二维码中
          });
        }).catch(error =>{
          this.$message.error("生成二维码失败!!!");
      });

      this.orderNo = result.data.orderId;

      // 保存订单 状态为:已创建 0
      this.saveOrder();

      // 检查支付状态
      this.axios
        .get("http://localhost:80/order/checkOrderStatus",{
          params:{
            orderId:result.data.orderId,  // 传递 订单编号 进行查询
          }
        })
        .then((result) =>{
          if(result.data.trade_state=="SUCCESS"){
            document.getElementById("statusText").innerHTML = "<i style='color:#00B38A' class='el-icon-success'></i> 支付成功";
            // 支付成功
            this.updateOrder(20);
          }
          /*
          else if(result.data.trade_state=="NOTPAY"){
            document.getElementById("statusText").innerHTML = "<i style='color:#00B38A' class='el-icon-success'></i> 未支付";
            this.updateOrder(10);
          }
          */

          // 3秒后关闭二维码窗口
          let s = 3;  // 倒计时的秒数
          this.closeQRForm(s); // 关闭二维码窗口
        }).catch(error =>{
          this.$message.error("查询订单失败!!!");
      });
    },
    // 倒计时关闭二维码窗口
    closeQRForm(s){
      let that = this;
      this.time = setInterval(function(){
        document.getElementById("closeText").innerHTML = "( "+ s-- +" ) 秒后关闭本窗口";
        if(s == 0){
          clearInterval(that.time); // 停止计时器
          that.dialogFormVisible = false; // 二维码窗口隐藏
          that.isBuy = true;  // 修改购买状态(已购买)
        }
      },1000);
    },
    // 保存订单
    saveOrder(){
      this.axios
        .get("http://localhost:80/order/saveOrder",{
          params:{
            orderNo:this.orderNo,
            user_id:this.user.content.id,
            course_id:this.course_id,
            activity_course_id:this.course_id,
            source_type:1,
          }
        })
        .then((result) =>{
          console.log(result);
        }).catch(error =>{
          this.$message.error("保存订单失败!!!");
      });
    },
    // 更新订单状态
    updateOrder(statusCode){
      return this.axios
        .get("http://localhost:80/order/updateOrder",{
          params:{
            orderNo:this.orderNo,
            status:statusCode,
          }
        })
        .then((result) =>{
          console.log("更新订单【"+this.orderNo+"】状态为:" + statusCode);
        }).catch(error =>{
          this.$message.error("更新订单失败!!!");
      });
    },
};
</script>

web消费方

@GetMapping("updateOrder")
public Integer saveOrder(String orderNo, Integer status){
    System.out.println("订单编号 = " + orderNo);
    System.out.println("状态编码 = " + status);
    Integer integer = orderService.updateOrder(orderNo, status);
    System.out.println("订单额更新 = " + integer);
    return integer;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值