这里开始是前台部分
 
搭建项目前台环境 
使用服务端渲染技术NuxtJS框架
 
.png)
搭建NuxtJS 
先赋值template至工作区 
修改.eslintrc.js(复制后台的即可) 
修改package.json,必须修改其中的name,version,decription,author等 
修改nuxt.config.js 
安装依赖npm install 
测试运行npm run dev 
 
 
NUXT目录结构 
资源目录 assets 用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。 
组件目录 components 用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性。 
布局目录 layouts 用于组织应用的布局组件。
default.vue:设置头尾信息 
 
 
页面目录 pages 用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。 
插件目录 plugins 用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。 
nuxt.config.js 文件 nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。 
 
 
引入基础资源和默认头尾视图 
Nuxt本身基于vue,但是没有引入element-ui,所以需要引入
这里还引入了其他需要的资源,直接cv即可
assets 
layout -> default.vue 
pages -> index.vue 
 
 
default.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 <template>   <div class="in-wrap">     <!-- 公共头引入 -->     <header id="header">       <section class="container">         <h1 id="logo">           <a href="#" title="谷粒学院">             <img src="~/assets/img/logo.png" width="100%" alt="谷粒学院" />           </a>         </h1>         <div class="h-r-nsl">           <ul class="nav">             <!-- 固定路由 -->             <router-link to="/" tag="li" active-class="current" exact>               <a>首页</a>             </router-link>             <router-link to="/course" tag="li" active-class="current">               <a>课程</a>             </router-link>             <router-link to="/teacher" tag="li" active-class="current">               <a>名师</a>             </router-link>             <router-link to="/article" tag="li" active-class="current">               <a>文章</a>             </router-link>             <router-link to="/qa" tag="li" active-class="current">               <a>问答</a>             </router-link>           </ul>           <!-- / nav -->           <ul class="h-r-login">             <li v-if="!loginInfo.id" id="no-login">               <a href="/login" title="登录">                 <em class="icon18 login-icon"> </em>                 <span class="vam ml5">登录</span>               </a>               |               <a href="/register" title="注册">                 <span class="vam ml5">注册</span>               </a>             </li>             <li v-if="loginInfo.id" id="is-login-one" class="mr10">               <a id="headerMsgCountId" href="#" title="消息">                 <em class="icon18 news-icon"> </em>               </a>               <q class="red-point" style="display: none"> </q>             </li>             <li v-if="loginInfo.id" id="is-login-two" class="h-r-user">               <a href="/ucenter" title>                 <img                   :src="loginInfo.avatar"                   width="30"                   height="30"                   class="vam picImg"                   alt                 />                 <span id="userName" class="vam disIb">{{                   loginInfo.nickname                 }}</span>               </a>               <a                 href="javascript:void(0);"                 title="退出"                 @click="logout()"                 class="ml5"                 >退出</a               >             </li>             <!-- /未登录显示第1 li;登录后显示第2,3 li -->           </ul>           <aside class="h-r-search">             <form action="#" method="post">               <label class="h-r-s-box">                 <input                   type="text"                   placeholder="输入你想学的课程"                   name="queryCourse.courseName"                   value                 />                 <button type="submit" class="s-btn">                   <em class="icon18"> </em>                 </button>               </label>             </form>           </aside>         </div>         <aside class="mw-nav-btn">           <div class="mw-nav-icon"></div>         </aside>         <div class="clear"></div>       </section>     </header>     <!-- /公共头引入 -->     <nuxt />     <!-- 公共底引入 -->     <footer id="footer">       <section class="container">         <div class>           <h4 class="hLh30">             <span class="fsize18 f-fM c-999">友情链接</span>           </h4>           <ul class="of flink-list">             <li>               <a href="http://www.atguigu.com/" title="尚硅谷" target="_blank"                 >尚硅谷</a               >             </li>           </ul>           <div class="clear"></div>         </div>         <div class="b-foot">           <section class="fl col-7">             <section class="mr20">               <section class="b-f-link">                 <a href="#" title="关于我们" target="_blank">关于我们</a>|                 <a href="#" title="联系我们" target="_blank">联系我们</a>|                 <a href="#" title="帮助中心" target="_blank">帮助中心</a>|                 <a href="#" title="资源下载" target="_blank">资源下载</a>|                 <span>服务热线:010-56253825(北京) 0755-85293825(深圳)</span>                 <span>Email:info@atguigu.com</span>               </section>               <section class="b-f-link mt10">                 <span>©2018课程版权均归谷粒学院所有 京ICP备17055252号</span>               </section>             </section>           </section>           <aside class="fl col-3 tac mt15">             <section class="gf-tx">               <span>                 <img src="~/assets/img/wx-icon.png" alt />               </span>             </section>             <section class="gf-tx">               <span>                 <img src="~/assets/img/wb-icon.png" alt />               </span>             </section>           </aside>           <div class="clear"></div>         </div>       </section>     </footer>     <!-- /公共底引入 -->   </div> </template> <script>   import '~/assets/css/reset.css'   import '~/assets/css/theme.css'   import '~/assets/css/global.css'   import '~/assets/css/web.css'   export default {   } </script> 
 
index.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 <template>   <div>     <!-- 幻灯片 开始 -->     <div v-swiper:mySwiper="swiperOption">       <div class="swiper-wrapper">         <div           v-for="banner in bannerList"           :key="banner.id"           class="swiper-slide"           style="background: #040b1b"         >           <a target="_blank" :href="banner.linkUrl">             <img :src="banner.imageUrl" :alt="banner.title" />           </a>         </div>       </div>       <div class="swiper-pagination swiper-pagination-white"></div>       <div         class="swiper-button-prev swiper-button-white"         slot="button-prev"       ></div>       <div         class="swiper-button-next swiper-button-white"         slot="button-next"       ></div>     </div>     <!-- 幻灯片 结束 -->     <div id="aCoursesList">       <!-- 网校课程 开始 -->       <div>         <section class="container">           <header class="comm-title">             <h2 class="tac">               <span class="c-333">热门课程</span>             </h2>           </header>           <div>             <article class="comm-course-list">               <ul class="of" id="bna">                 <li v-for="course in courseList" :key="course.id">                   <div class="cc-l-wrap">                     <section class="course-img">                       <img                         :src="course.cover"                         class="img-responsive"                         :alt="course.title"                       />                       <div class="cc-mask">                         <a href="#" title="开始学习" class="comm-btn c-btn-1"                           >开始学习</a                         >                       </div>                     </section>                     <h3 class="hLh30 txtOf mt10">                       <a                         href="#"                         :title="course.title"                         class="course-title fsize18 c-333"                         >{{ course.title }}</a                       >                     </h3>                     <section class="mt10 hLh20 of">                       <span                         class="fr jgTag bg-green"                         v-if="Number(course.price) === 0"                       >                         <i class="c-fff fsize12 f-fA">免费</i>                       </span>                       <span class="fl jgAttr c-ccc f-fA">                         <i class="c-999 f-fA">9634人学习</i>                         |                         <i class="c-999 f-fA">9634评论</i>                       </span>                     </section>                   </div>                 </li>               </ul>               <div class="clear"></div>             </article>             <section class="tac pt20">               <a href="#" title="全部课程" class="comm-btn c-btn-2">全部课程</a>             </section>           </div>         </section>       </div>       <!-- /网校课程 结束 -->       <!-- 网校名师 开始 -->       <div>         <section class="container">           <header class="comm-title">             <h2 class="tac">               <span class="c-333">名师大咖</span>             </h2>           </header>           <div>             <article class="i-teacher-list">               <ul class="of">                 <li v-for="teacher in teacherList" :key="teacher.id">                   <section class="i-teach-wrap">                     <div class="i-teach-pic">                       <a href="/teacher/1" :title="teacher.name">                         <img :alt="teacher.name" :src="teacher.avatar" />                       </a>                     </div>                     <div class="mt10 hLh30 txtOf tac">                       <a                         href="/teacher/1"                         :title="teacher.name"                         class="fsize18 c-666"                         >{{ teacher.name }}                       </a>                     </div>                     <div class="hLh30 txtOf tac">                       <span class="fsize14 c-999">{{ teacher.career }}</span>                     </div>                     <div class="mt15 i-q-txt">                       <p class="c-999 f-fA">                         {{ teacher.intro }}                       </p>                     </div>                   </section>                 </li>               </ul>               <div class="clear"></div>             </article>             <section class="tac pt20">               <a href="#" title="全部讲师" class="comm-btn c-btn-2">全部讲师</a>             </section>           </div>         </section>       </div>       <!-- /网校名师 结束 -->     </div>   </div> </template> 
 
轮播图组件 
本项目需要用到一个轮播图组件awesome-swiper(这里使用3.1.3版本)
 
安装  npm install vue-awesome-swiper@3.1.3
配置 
在 plugins 文件夹下新建文件 nuxt-swiper-plugin.js
 
1 2 3 import  Vue from  'vue' import  VueAwesomeSwiper from  'vue-awesome-swiper/dist/ssr' Vue.use(VueAwesomeSwiper) 
 
在 nuxt.config.js 文件中配置插件
 
1 2 3 4 5 6 7 8 9 10 module .exports = {        plugins : [     { src : '~/plugins/nuxt-swiper-plugin.js' , ssr : false  }   ],   css : [     'swiper/dist/css/swiper.css'    ] } 
 
axios依赖与封装 
npm install axios
 
创建utils -> request.js
注意这里的baseURL要与nginx对应
 
1 2 3 4 5 6 7 import  axios from  'axios' const  service = axios.create({  baseURL : 'http://localhost:9001' ,    timeout : 20000   }) export  default  service
 
element-ui 安装 
npm install element-ui
 
配置 
plugins -> nuxt-swiper-plugin.js
 
1 2 3 4 5 6 7 8 import  Vue from  'vue' import  VueAwesomeSwiper from  'vue-awesome-swiper/dist/ssr' import  VueQriously from  'vue-qriously' import  ElementUI from  'element-ui'  import  'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)  Vue.use(VueQriously) Vue.use(VueAwesomeSwiper) 
 
测试 
必看注意,直接启动的话eslint语法检查十分严格,类似于空格是2个就不能是4个,所以要把严格检查关闭
解决vue/cli3.0 语法验证规则 ESLint: Expected indentation of 2 spaces but found 4. (indent) - 走看看 (zoukankan.com) 
仔细参考上述文章内容,注意两个步骤都要执行才行(弄了我十几分钟有点烦躁,好在解决了)
 
首页基础搭建 讲师和课程视图模板搭建 固定路由 
注意default.js中设置了头导航的固定路由,所以需要在pages文件夹下创建对应的视图
注意其中的router-link(即路由导航),其中的to对应的地址就是视图的地址,值为文件夹的路径,to会自动找到pages文件夹下对应名字的文件夹中的index.vue
按照这个思路创建对应视图即可,然后复制资料中已经有的视图模板
 
动态路由 
动态路由编写有一定的规范
例如需要根据id查询一条记录,需要在对应pages的文件夹下创建_id.vue文件,(必须是以下划线开头,参数名为下划线后面的文件名)
 
创建视图 
根据上述条件创建即可,然后内容模板cv即可
 
当前项目结构 
后端环境搭建 创建子模块 
service_cms
 
创建并编写配置文件application.properties 
注意这里的DataSource配置,如果不对可能会报springboot找不到bean的错误
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 server.port =8004 spring.application.name =service-cms spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username =root spring.datasource.password =123456789 spring.jackson.date-format =yyyy-MM-dd HH:mm:ss spring.jackson.time-zone =GMT+8 mybatis-plus.mapper-locations =classpath:com/atguigu/educms/mapper/xml/*.xml spring.redis.host =192.168.44.132 spring.redis.port =6379 spring.redis.database =0 spring.redis.timeout =1800000 spring.redis.lettuce.pool.max-active =20 spring.redis.lettuce.pool.max-wait =-1 spring.redis.lettuce.pool.max-idle =5 spring.redis.lettuce.pool.min-idle =0 mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl spring.cloud.nacos.discovery.server-addr =127.0.0.1:8848 
 
这里记得去nginx.conf里配置一下
 
创建数据库表 
导入资料的sql文件即可
 
编写启动类 1 2 3 4 5 6 7 8 9 @SpringBootApplication @ComponentScan(basePackages = {"com.atguigu"}) @MapperScan("com.atguigu.educms.mapper") @EnableDiscoveryClient   public  class  CmsApplication   {    public  static  void  main (String[] args)   {         SpringApplication.run(CmsApplication.class, args);     } } 
 
代码生成器 
老样子改一下就行
生成之后跨域注释加一下、mapperscan加一下、配置文件中扫描mapper.xml路径加一下
一些实体类字段的注释也要加一下,比如说自动添加和逻辑删除
 
1 2 3 @MapperScan("com.atguigu.educms.mapper") public  class  CmsApplication   {} 
 
1 2 3 4 @CrossOrigin public  class  CrmBannerController   {} 
 
1 2 mybatis-plus.mapper-locations =classpath:com/atguigu/educms/mapper/xml/*.xml 
 
当前项目结构 
首页轮播图(后端) 
后台应该也有轮播图的管理模块,所以需要两个bannerController
 
编写BannerAdminController 
用于后台的banner管理接口,TODO:前端页面自己写(我选择不写使用swagger测试嘿嘿😙)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @RestController @RequestMapping("/educms/banneradmin") @CrossOrigin public  class  BannerAdminController   {    @Autowired      private  CrmBannerService bannerService;          @GetMapping("pageBanner/{pageNo}/{limit}")      public  R pageBanner (@PathVariable  Long limit, @PathVariable  Long pageNo)   {         Page<CrmBanner> pageBanner = new  Page<>(pageNo, limit);         bannerService.page(pageBanner, null );         return  R.ok().data("records" , pageBanner.getRecords()).data("total" , pageBanner.getTotal());     }          @PostMapping("addBanner")      public  R addBanner (@RequestBody  CrmBanner crmBanner)   {         bannerService.save(crmBanner);         return  R.ok();     }     @ApiOperation(value = "获取Banner")      @GetMapping("get/{id}")      public  R get (@PathVariable  String id)   {         CrmBanner banner = bannerService.getById(id);         return  R.ok().data("item" , banner);     }     @ApiOperation(value = "修改Banner")      @PutMapping("update")      public  R updateById (@RequestBody  CrmBanner banner)   {         bannerService.updateById(banner);         return  R.ok();     }     @ApiOperation(value = "删除Banner")      @DeleteMapping("remove/{id}")      public  R remove (@PathVariable  String id)   {         bannerService.removeById(id);         return  R.ok();     } } 
 
编写BannerFrontController 
用于前台的banner接口,主要用于显示banner
 
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController @RequestMapping("/educms/bannerfront") @CrossOrigin public  class  BannerFrontController   {    @Autowired      private  CrmBannerService bannerService;        @GetMapping("getBannerList")      public  R getBannerList ()   {         List<CrmBanner> list = bannerService.selectAllBanner();         return  R.ok().data("list" , list);     } } 
 
编写Service 
这里自己写了个service,结果啥也没实现,纯属脱裤子放屁,我改了
我发现我错了,我应该看完整个视频才说话,这个是为了后面的Redis做准备的
写Mapper里最好(弹幕:感谢这段代码,昨天写了,今天老板让我走)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public  class  CrmBannerServiceImpl  extends  ServiceImpl <CrmBannerMapper , CrmBanner > implements  CrmBannerService   { 	     @Override      public  List<CrmBanner> selectAllBanner ()   {                  QueryWrapper<CrmBanner> wrapper = new  QueryWrapper<>();                  wrapper.orderByDesc("id" );                  wrapper.last("limit 2" );         List<CrmBanner> crmBanners = baseMapper.selectList(wrapper);         return  crmBanners;     } } 
 
首页热门课程和名师(后端) 
注意这里,controller这些又写到service_edu模块中了
 
编写Mapper 
需求:根据id进行降序排序(或者根据表中专门的热度排序),显示排序之后的前8条数据
 
EduTeacherMapper 1 2 3 4 5 6 <select  id ="selectIndexList"  resultType ="com.atguigu.eduservice.entity.EduTeacher" >     select *     from edu_teacher     order by id desc     limit 4 </select > 
 
EduCourseMapper 1 2 3 4 5 6 <select  id ="selectIndexList"  resultType ="com.atguigu.eduservice.entity.EduCourse" >     select *     from edu_course     order by id desc     limit 8 </select > 
 
编写service 
就直接调用baseMapper中的接口即可
 
编写controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RestController @RequestMapping("/eduservice/index") @CrossOrigin public  class  IndexController   {    @Autowired      private  EduCourseService courseService;     @Autowired      private  EduTeacherService teacherService;          @GetMapping("index")      public  R getIndex ()   {         List<EduCourse> courseList = courseService.getIndexList();         List<EduTeacher> teacherList = teacherService.getIndexList();         return  R.ok().data("courseList" , courseList).data("teacherList" , teacherList);     } } 
 
首页轮播图和热门讲师课程(前端) API 
api -> banner.js 
api -> index.js 
 
 
banner.js 1 2 3 4 5 6 7 8 9 10 11 import  request from  '@/utils/request' export  default  {     getBannerList ( )  {     return  request({       url : '/educms/bannerfront/getBannerList' ,       method : 'get'      })   } } 
 
index.js 1 2 3 4 5 6 7 8 9 10 11 import  request from  '@/utils/request' export  default  {     getIndexList ( )  {     return  request({       url : '/eduservice/index/getIndexList' ,       method : 'get'      })   } } 
 
调用接口 
注意这里的response需要两个data,应为这个模板没有帮忙封装response的一些东西,之前的直接获得的是response.data
 
轮播图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <template>     <div>         <!-- 幻灯片 开始 -->         <div v-swiper:mySwiper="swiperOption">             <div class="swiper-wrapper">                 <div                     v-for="banner in bannerList"                     :key="banner.id"                     class="swiper-slide"                     style="background: #040b1b"                 >                     <a target="_blank" :href="banner.linkUrl">                         <img :src="banner.imageUrl" :alt="banner.title" />                     </a>                 </div>             </div>             <div class="swiper-pagination swiper-pagination-white"></div>             <div                 class="swiper-button-prev swiper-button-white"                 slot="button-prev"             ></div>             <div                 class="swiper-button-next swiper-button-white"                 slot="button-next"             ></div>         </div>         <!-- 幻灯片 结束 -->     </div> </template> <script>     import bannerApi from '@/api/banner'     export default {       data() {         return {           swiperOption: {             // 配置分页             pagination: {               el: '.swiper-pagination' // 分页的dom节点             },             // 配置导航             navigation: {               nextEl: '.swiper-button-next', // 下一页dom节点               prevEl: '.swiper-button-prev' // 前一页dom节点             }           },           // 数组           bannerList: [],           courseList: [],           teacherList: []         }       },       created() {         // 初始化数据         this.getBannerList()       },       methods: {         // 初始化首页轮播图课程讲师列表         getBannerList() {           bannerApi.getBannerList().then(result => {             this.bannerList = result.data.data.list           })         }       }     } </script> 
 
热门课程讲师 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 <template>     <div>         <div id="aCoursesList">             <!-- 网校课程 开始 -->             <div>                 <section class="container">                     <header class="comm-title">                         <h2 class="tac">                             <span class="c-333">热门课程</span>                         </h2>                     </header>                     <div>                         <article class="comm-course-list">                             <ul class="of" id="bna">                                 <li                                     v-for="course in courseList"                                     :key="course.id"                                 >                                     <div class="cc-l-wrap">                                         <section class="course-img">                                             <img                                                 :src="course.cover"                                                 class="img-responsive"                                                 :alt="course.title"                                             />                                             <div class="cc-mask">                                                 <a                                                     href="#"                                                     title="开始学习"                                                     class="comm-btn c-btn-1"                                                     >开始学习</a                                                 >                                             </div>                                         </section>                                         <h3 class="hLh30 txtOf mt10">                                             <a                                                 href="#"                                                 :title="course.title"                                                 class="                                                     course-title                                                     fsize18                                                     c-333                                                 "                                                 >{{ course.title }}</a                                             >                                         </h3>                                         <section class="mt10 hLh20 of">                                             <span                                                 class="fr jgTag bg-green"                                                 v-if="                                                     Number(course.price) === 0                                                 "                                             >                                                 <i class="c-fff fsize12 f-fA"                                                     >免费</i                                                 >                                             </span>                                             <span class="fl jgAttr c-ccc f-fA">                                                 <i class="c-999 f-fA"                                                     >9634人学习</i                                                 >                                                 |                                                 <i class="c-999 f-fA"                                                     >9634评论</i                                                 >                                             </span>                                         </section>                                     </div>                                 </li>                             </ul>                             <div class="clear"></div>                         </article>                         <section class="tac pt20">                             <a                                 href="#"                                 title="全部课程"                                 class="comm-btn c-btn-2"                                 >全部课程</a                             >                         </section>                     </div>                 </section>             </div>             <!-- /网校课程 结束 -->             <!-- 网校名师 开始 -->             <div>                 <section class="container">                     <header class="comm-title">                         <h2 class="tac">                             <span class="c-333">名师大咖</span>                         </h2>                     </header>                     <div>                         <article class="i-teacher-list">                             <ul class="of">                                 <li                                     v-for="teacher in teacherList"                                     :key="teacher.id"                                 >                                     <section class="i-teach-wrap">                                         <div class="i-teach-pic">                                             <a                                                 href="/teacher/1"                                                 :title="teacher.name"                                             >                                                 <img                                                     :alt="teacher.name"                                                     :src="teacher.avatar"                                                 />                                             </a>                                         </div>                                         <div class="mt10 hLh30 txtOf tac">                                             <a                                                 href="/teacher/1"                                                 :title="teacher.name"                                                 class="fsize18 c-666"                                                 >{{ teacher.name }}</a                                             >                                         </div>                                         <div class="hLh30 txtOf tac">                                             <span class="fsize14 c-999">{{                                                 teacher.career                                             }}</span>                                         </div>                                         <div class="mt15 i-q-txt">                                             <p class="c-999 f-fA">                                                 {{ teacher.intro }}                                             </p>                                         </div>                                     </section>                                 </li>                             </ul>                             <div class="clear"></div>                         </article>                         <section class="tac pt20">                             <a                                 href="#"                                 title="全部讲师"                                 class="comm-btn c-btn-2"                                 >全部讲师</a                             >                         </section>                     </div>                 </section>             </div>             <!-- /网校名师 结束 -->         </div>     </div> </template> <script>     import bannerApi from '@/api/banner'     import indexApi from '@/api/index'     export default {       data() {         return {           swiperOption: {             // 配置分页             pagination: {               el: '.swiper-pagination' // 分页的dom节点             },             // 配置导航             navigation: {               nextEl: '.swiper-button-next', // 下一页dom节点               prevEl: '.swiper-button-prev' // 前一页dom节点             }           },           // 数组           bannerList: [],           courseList: [],           teacherList: []         }       },       created() {         // 初始化数据         this.getBannerList()         this.getCourseTeacher()       },       methods: {         // 初始化首页课程讲师列表         getCourseTeacher() {           indexApi.getIndexList().then(result => {             this.courseList = result.data.data.courseList             this.teacherList = result.data.data.teacherList           })         }       }     } </script> 
 
测试 
这里我没有把url对应起来,所以报了Access to XMLHttpRequest at 'http://localhost:9001/educms/bannerfront/getBannerList' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.的错误
这个错误还有可能是nginx没有配置,或者跨域注解没有加
 
单点登录SSO 
Redis 介绍 
整合 
该项目在common模块中配置
 
依赖 1 2 3 4 5 6 7 8 9 10 11 12 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-data-redis</artifactId >  </dependency > <dependency >     <groupId > org.apache.commons</groupId >      <artifactId > commons-pool2</artifactId >      <version > 2.6.0</version >  </dependency > 
 
创建配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Configuration   @EnableCaching   public  class  RedisConfig  extends  CachingConfigurerSupport   {    @Bean      public  RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory)   {         RedisTemplate<String, Object> template = new  RedisTemplate<>();         RedisSerializer<String> redisSerializer = new  StringRedisSerializer();         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new  Jackson2JsonRedisSerializer(Object.class);         ObjectMapper om = new  ObjectMapper();         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);         jackson2JsonRedisSerializer.setObjectMapper(om);         template.setConnectionFactory(factory);                  template.setKeySerializer(redisSerializer);                  template.setValueSerializer(jackson2JsonRedisSerializer);                  template.setHashValueSerializer(jackson2JsonRedisSerializer);         return  template;     }     @Bean      public  CacheManager cacheManager (RedisConnectionFactory factory)   {         RedisSerializer<String> redisSerializer = new  StringRedisSerializer();         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new  Jackson2JsonRedisSerializer(Object.class);                  ObjectMapper om = new  ObjectMapper();         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);         jackson2JsonRedisSerializer.setObjectMapper(om);                  RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()                 .entryTtl(Duration.ofSeconds(600 ))                 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))                 .disableCachingNullValues();         RedisCacheManager cacheManager = RedisCacheManager.builder(factory)                 .cacheDefaults(config)                 .build();         return  cacheManager;     } } 
 
springboot缓存注解 
在相对应接口中添加Redis缓存使用注解即可使用Redis缓存
@Cacheable:一般用在查询方法上,根据方法对其返回结果进行缓存,下次请求时如果缓存存在,name直接读取缓存数据返回 
@CachePut:一般用在新增方法上,使用该注解的方法每次都会执行,并将结果存入指定的缓存中 
@CacheEvict:一般用在更新或者删除方法上,会清空指定的缓存 
 
 
使用Redis服务 安装Redis并启动 
这里我去谷粒商城看了
 
配置文件 
在需要使用redis的模块的配置文件application.properties中添加配置
 
1 2 3 4 5 6 7 8 spring.redis.host =192.168.128.129 spring.redis.port =6379 spring.redis.database =0 spring.redis.timeout =1800000 spring.redis.lettuce.pool.max-active =20 spring.redis.lettuce.pool.max-wait =-1 
 
首页缓存 
在相关的service方法上添加注释(注意这里为什么要在service中加,因为注释自动添加到redis的话,是从返回值获取的,controller中定义了统一返回格式,不适用)
 
1 2 3 4 5 6 7 8 @Service public  class  CrmBannerServiceImpl  extends  ServiceImpl <CrmBannerMapper , CrmBanner > implements  CrmBannerService   {         @Override      @Cacheable(key = "'selectIndexList'", value = "banner") 	     public  List<CrmBanner> selectAllBanner ()   {     } } 
 
redis相关命令 
使用root权限:su root 
查看VMwarehost:ifconfig 
修改配置文件:vi /mydata/redis/conf/redis.conf 
启动redis:docker start redis 
进入redis:docker exec -it redis redis-cli 
查看所有keyvalue:keys * 
设置查看:set/get xxx 
 
 
JWT(Json Web Token) 
依赖 
先引入依赖,common_utils模块即可 
 
1 2 3 4 5 6 7 <dependencies >          <dependency >          <groupId > io.jsonwebtoken</groupId >          <artifactId > jjwt</artifactId >      </dependency >  </dependencies > 
 
utils类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 import  io.jsonwebtoken.Claims;import  io.jsonwebtoken.Jws;import  io.jsonwebtoken.Jwts;import  io.jsonwebtoken.SignatureAlgorithm;import  org.springframework.util.StringUtils;import  javax.servlet.http.HttpServletRequest;import  java.util.Date;public  class  JwtUtils   {    public  static  final  long  EXPIRE = 1000  * 60  * 60  * 24 ;     public  static  final  String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO" ;     public  static  String getJwtToken (String id, String nickname)   {         String JwtToken = Jwts.builder()                 .setHeaderParam("typ" , "JWT" )                 .setHeaderParam("alg" , "HS256" )                 .setSubject("guli-user" )                 .setIssuedAt(new  Date())                 .setExpiration(new  Date(System.currentTimeMillis() + EXPIRE))                 .claim("id" , id)                 .claim("nickname" , nickname)                 .signWith(SignatureAlgorithm.HS256, APP_SECRET)                 .compact();         return  JwtToken;     }          public  static  boolean  checkToken (String jwtToken)   {         if  (StringUtils.isEmpty(jwtToken)) return  false ;         try  {             Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);         } catch  (Exception e) {             e.printStackTrace();             return  false ;         }         return  true ;     }          public  static  boolean  checkToken (HttpServletRequest request)   {         try  {             String jwtToken = request.getHeader("token" );             if  (StringUtils.isEmpty(jwtToken)) return  false ;             Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);         } catch  (Exception e) {             e.printStackTrace();             return  false ;         }         return  true ;     }          public  static  String getMemberIdByJwtToken (HttpServletRequest request)   {         String jwtToken = request.getHeader("token" );         if  (StringUtils.isEmpty(jwtToken)) return  "" ;         Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);         Claims claims = claimsJws.getBody();         return  (String) claims.get("id" );     } } 
 
短信微服务 阿里云短信服务 
开启之后,申请签名管理和模板管理
???必须要备案网站好家伙mua的
TODO,没有域名下次再说吧
 
创建子模块 
service_msm模块
 
配置文件application.properties 
基本上都是一样的写法
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 server.port =8005 spring.application.name =service-msm spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username =root spring.datasource.password =123456789 spring.redis.host =192.168.128.129 spring.redis.port =6379 spring.redis.database =0 spring.redis.timeout =1800000 spring.redis.lettuce.pool.max-active =20 spring.redis.lettuce.pool.max-wait =-1 spring.redis.lettuce.pool.max-idle =5 spring.redis.lettuce.pool.min-idle =0 spring.jackson.date-format =yyyy-MM-dd HH:mm:ss spring.jackson.time-zone =GMT+8 mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl spring.cloud.nacos.discovery.server-addr =127.0.0.1:8848 mybatis-plus.mapper-locations =classpath:com/atguigu/educms/mapper/xml/*.xml 
 
启动类 1 2 3 4 5 6 7 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @ComponentScan(basePackages = {"com.atguigu"}) public  class  MsmApplication   {    public  static  void  main (String[] args)   {         SpringApplication.run(MsmApplication.class, args);     } } 
 
controller 
TODO
 
service 
TODO
 
登录注册(后端) 基础工作 创建模块 
service_ucenter
 
数据库 
ucenter_member
 
代码生成器 
老样子
 
配置文件 
老样子
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 server.port =8006 spring.application.name =service-ucenter spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username =root spring.datasource.password =123456789 spring.redis.host =192.168.128.129 spring.redis.port =6379 spring.redis.database =0 spring.redis.timeout =1800000 spring.redis.lettuce.pool.max-active =20 spring.redis.lettuce.pool.max-wait =-1 spring.redis.lettuce.pool.max-idle =5 spring.redis.lettuce.pool.min-idle =0 spring.jackson.date-format =yyyy-MM-dd HH:mm:ss spring.jackson.time-zone =GMT+8 mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl spring.cloud.nacos.discovery.server-addr =127.0.0.1:8848 mybatis-plus.mapper-locations =classpath:com/atguigu/educenter/mapper/xml/*.xml 
 
启动类 1 2 3 4 5 6 7 8 @SpringBootApplication @ComponentScan({"com.atguigu"}) @MapperScan("com.atguigu.educenter.mapper") public  class  UcenterApplication   {    public  static  void  main (String[] args)   {         SpringApplication.run(UcenterApplication.class, args);     } } 
 
还有nginx配置,跨域配置等
 
登录 MD5Utils 
加密工具
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package  com.atguigu.educenter.utils;import  java.security.MessageDigest;import  java.security.NoSuchAlgorithmException;public  final  class  MD5   {    public  static  String encrypt (String strSrc)   {         try  {             char  hexChars[] = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' ,                     '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f'  };             byte [] bytes = strSrc.getBytes();             MessageDigest md = MessageDigest.getInstance("MD5" );             md.update(bytes);             bytes = md.digest();             int  j = bytes.length;             char [] chars = new  char [j * 2 ];             int  k = 0 ;             for  (int  i = 0 ; i < bytes.length; i++) {                 byte  b = bytes[i];                 chars[k++] = hexChars[b >>> 4  & 0xf ];                 chars[k++] = hexChars[b & 0xf ];             }             return  new  String(chars);         } catch  (NoSuchAlgorithmException e) {             e.printStackTrace();             throw  new  RuntimeException("MD5加密出错!!+"  + e);         }     }     public  static  void  main (String[] args)   {         System.out.println(MD5.encrypt("111111" ));     } } 
 
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @RequestMapping("/educenter/member") @CrossOrigin public  class  UcenterMemberController   {    @Autowired      private  UcenterMemberService memberService;          @PostMapping("login")      public  R loginUser (@RequestBody  UcenterMember member)   {                  String token = memberService.login(member);         return  R.ok().data("token" , token);     } } 
 
service 
注意使用MD5工具加密再比较密码
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Service public  class  UcenterMemberServiceImpl  extends  ServiceImpl <UcenterMemberMapper , UcenterMember > implements  UcenterMemberService   {         @Override      public  String login (UcenterMember member)   {                  String mobile = member.getMobile();         String password = member.getPassword();                  if  (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {             throw  new  GuliException(20001 , "手机或密码为空" );         }                  QueryWrapper<UcenterMember> wrapper = new  QueryWrapper<>();         wrapper.eq("mobile" , mobile);         UcenterMember mmember = baseMapper.selectOne(wrapper);         if  (mmember == null ) {             throw  new  GuliException(20001 , "该账号不存在" );         }                                    if  (!mmember.getPassword().equals(MD5.encrypt(password))) {             throw  new  GuliException(20001 , "密码错误" );         }                  if  (mmember.getIsDisabled()) {             throw  new  GuliException(20001 , "账号禁用" );         }                  String token = JwtUtils.getJwtToken(mmember.getId(), mmember.getNickname());         return  token;     } } 
 
swagger测试 
初始值随便复制一个mobile,密码统一为111111
 
注册 vo实体类 1 2 3 4 5 6 7 8 9 10 11 @Data public  class  RegisterVo   {    @ApiModelProperty(value = "昵称")      private  String nickname;     @ApiModelProperty(value = "手机号")      private  String mobile;     @ApiModelProperty(value = "密码")      private  String password;           } 
 
controller 1 2 3 4 5 6 @PostMapping("register") public  R registerUser (@RequestBody  RegisterVo registerVo)   {    memberService.register(registerVo);     return  R.ok(); } 
 
service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override public  void  register (RegisterVo registerVo)   {              String mobile = registerVo.getMobile();      String nickname = registerVo.getNickname();      String password = registerVo.getPassword();           if  (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password) || StringUtils.isEmpty(nickname)) {         throw  new  GuliException(20001 , "输入为空" );     }          QueryWrapper<UcenterMember> wrapper = new  QueryWrapper<>();     wrapper.eq("mobile" , mobile);     Integer count = baseMapper.selectCount(wrapper);     if  (count > 0 ) {         throw  new  GuliException(20001 , "手机号重复" );     }          UcenterMember member = new  UcenterMember();     member.setMobile(mobile);     member.setNickname(nickname);     member.setPassword(MD5.encrypt(password));     member.setIsDisabled(false );   member.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132" );     baseMapper.insert(member); } 
 
回显数据 
根据token获得用户信息
 
controller 1 2 3 4 5 6 7 8 9 10 @GetMapping("getMemberInfo") public  R getMemberInfo (HttpServletRequest request)   {         String memberId = JwtUtils.getMemberIdByJwtToken(request);          UcenterMember member = memberService.getById(memberId);          return  R.ok().data("member" , member); } 
 
登录注册(前端) 注册 layout 
创建sign.vue然后cv
这个是专门的注册登录页面的布局
 
api 
api中创建对应js即可
 
1 2 3 4 5 6 7 8 9 10 11 import  request from  '@/utils/request' export  default  {     register (registerVo )  {     return  request({       url : '/educenter/member/register' ,       method : 'post' ,       data : registerVo     })   } } 
 
vue页面 
先去layout -> default.vue中的地址该为登录注册页面的地址
然后在pages里创建vue视图
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 <template>   <div class="main">     <div class="title">       <a href="/login">登录</a>       <span>·</span>       <a class="active" href="/register">注册</a>     </div>     <div class="sign-up-container">       <el-form ref="userForm" :model="params">         <el-form-item           class="input-prepend restyle"           prop="nickname"           :rules="[             { required: true, message: '请输入你的昵称', trigger: 'blur' },           ]"         >           <div>             <el-input               type="text"               placeholder="你的昵称"               v-model="params.nickname"             />             <i class="iconfont icon-user" />           </div>         </el-form-item>         <el-form-item           class="input-prepend restyle no-radius"           prop="mobile"           :rules="[             { required: true, message: '请输入手机号码', trigger: 'blur' },             { validator: checkPhone, trigger: 'blur' },           ]"         >           <div>             <el-input               type="text"               placeholder="手机号"               v-model="params.mobile"             />             <i class="iconfont icon-phone" />           </div>         </el-form-item>         <el-form-item           class="input-prepend restyle no-radius"           prop="code"           :rules="[             { required: true, message: '请输入验证码', trigger: 'blur' },           ]"         >           <div             style="width: 100%; display: block; float: left; position: relative"           >             <el-input type="text" placeholder="验证码" v-model="params.code" />             <i class="iconfont icon-phone" />           </div>           <div             class="btn"             style="position: absolute; right: 0; top: 6px; width: 40%"           >             <a               href="javascript:"               type="button"               @click="getCodeFun()"               :value="codeTest"               style="border: none; background-color: none"               >{{ codeTest }}</a             >           </div>         </el-form-item>         <el-form-item           class="input-prepend"           prop="password"           :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"         >           <div>             <el-input               type="password"               placeholder="设置密码"               v-model="params.password"             />             <i class="iconfont icon-password" />           </div>         </el-form-item>         <div class="btn">           <input             type="button"             class="sign-up-button"             value="注册"             @click="submitRegister()"           />         </div>         <p class="sign-up-msg">           点击 “注册” 即表示您同意并愿意遵守简书           <br />           <a target="_blank" href="http://www.jianshu.com/p/c44d171298ce"             >用户协议</a           >           和           <a target="_blank" href="http://www.jianshu.com/p/2ov8x3">隐私政策</a>           。         </p>       </el-form>       <!-- 更多注册方式 -->       <div class="more-sign">         <h6>社交帐号直接注册</h6>         <ul>           <li>             <a               id="weixin"               class="weixin"               target="_blank"               href="http://huaan.free.idcfengye.com/api/ucenter/wx/login"               ><i class="iconfont icon-weixin"             /></a>           </li>           <li>             <a id="qq" class="qq" target="_blank" href="#"               ><i class="iconfont icon-qq"             /></a>           </li>         </ul>       </div>     </div>   </div> </template> <script>   import '~/assets/css/sign.css'   import '~/assets/css/iconfont.css'   export default {     layout: 'sign',     data() {       return {         params: {           // 封装注册输入数据           mobile: '',           code: '', // 验证码           nickname: '',           password: ''         },         sending: true, // 是否发送验证码         second: 60, // 倒计时间         codeTest: '获取验证码'       }     },     methods: {}   } </script> 
 
vue方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 submitRegister ( )  {  registerApi.register(this .params).then(result  =>  {          this .$message({       type : 'success' ,       message : '注册成功'      })          this .$router.push({ path : '/login'  })   }) }, getCodeFun ( )  {  if  ((this .sending = false )) {     this .$message({       type : 'error' ,       message : '请勿频繁点击'      })     return    }      this .sending = false       this .timeDown() }, checkPhone (rule, value, callback )  {     if  (!/^1[34578]\d{9}$/ .test(value)) {     return  callback(new  Error ('手机号码格式不正确' ))   }   return  callback() }, timeDown ( )  {  const  result = setInterval (() =>  {     --this .second     this .codeTest = this .second     if  (this .second < 1 ) {       clearInterval (result)       this .sending = true        this .second = 60        this .codeTest = '获取验证码'      }   }, 1000 ) } 
 
TODO:这里有很多地方需要完善:比如点击验证码之后再次点击数字需要弹窗警告,但是现在不会而且还会加速计时器
 
登录 cookie插件 
这里需要用到cookie,需要下载
npm install js-cookie
 
api 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import  request from  '@/utils/request' export  default  {     login (member )  {     return  request({       url : '/educenter/member/login' ,       method : 'post' ,       data : member     })   },      getLoginMemberInfo ( )  {     return  request({       url : '/educenter/member/getMemberInfo' ,       method : 'get'      })   } } 
 
vue视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 <template>   <div class="main">     <div class="title">       <a class="active" href="/login">登录</a>       <span>·</span>       <a href="/register">注册</a>     </div>     <div class="sign-up-container">       <el-form ref="userForm" :model="user">         <el-form-item           class="input-prepend restyle"           prop="mobile"           :rules="[             { required: true, message: '请输入手机号码', trigger: 'blur' },             { validator: checkPhone, trigger: 'blur' },           ]"         >           <div>             <el-input type="text" placeholder="手机号" v-model="user.mobile" />             <i class="iconfont icon-phone" />           </div>         </el-form-item>         <el-form-item           class="input-prepend"           prop="password"           :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"         >           <div>             <el-input               type="password"               placeholder="密码"               v-model="user.password"             />             <i class="iconfont icon-password" />           </div>         </el-form-item>         <div class="btn">           <input             type="button"             class="sign-in-button"             value="登录"             @click="submitLogin()"           />         </div>       </el-form>       <!-- 更多登录方式 -->       <div class="more-sign">         <h6>社交帐号登录</h6>         <ul>           <li>             <a               id="weixin"               class="weixin"               target="_blank"               href="http://qy.free.idcfengye.com/api/ucenter/weixinLogin/login"               ><i class="iconfont icon-weixin"             /></a>           </li>           <li>             <a id="qq" class="qq" target="_blank" href="#"               ><i class="iconfont icon-qq"             /></a>           </li>         </ul>       </div>     </div>   </div> </template> <script>   import '~/assets/css/sign.css'   import '~/assets/css/iconfont.css'   export default {     layout: 'sign',     data() {       return {         user: {           mobile: '',           password: ''         },         loginInfo: {}       }     },     methods: {}   } </script> <style>   .el-form-item__error {     z-index: 9999999;   } </style> 
 
拦截器 
在下面的方法中有说明登录token的步骤,其中需要用到拦截器
拦截器写在utils文件夹下的request.js文件中
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  cookie from  'js-cookie' service.interceptors.request.use(   config  =>  {     if  (cookie.get('guli_token' )) {              config.headers['token' ] = cookie.get('guli_token' );     }     return  config   },   err  =>  {     return  Promise .reject(err)   } ) 
 
vue方法 
调用接口登录获取token 
将token放入cookie 
前端拦截器:判断cookie里是否有token,如果有则放到header中 
根据header中的token调用接口获取用户信息 
把获取的用户信息放入cookie 
 
 
login.vue中
注意这里把对象放入cookie中是要使用JSON.stringify转换为字符串才行,要不然就是object,回报错(cookie获得的json对象为undefined)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 submitLogin ( )  {     loginApi.login(this .user).then(result  =>  {               cookieApi.set('guli_token' , result.data.data.token, {       domain : 'localhost'      })          loginApi.getLoginMemberInfo().then(result  =>  {              console .log(response.data.data.member)       cookieApi.set('guli_ucenter' , JSON .stringify(result.data.data.member), {         domain : 'localhost'        })     })          this .$router.push({ path : '/'  })   }) }, checkPhone (rule, value, callback )  {  if  (!/^1[34578]\d{9}$/ .test(value)) {     return  callback(new  Error ('手机号码格式不正确' ))   }   return  callback() } 
 
default.vue中编写根据token获得用户信息
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 created ( )  {      this .showInfo()     },     methods : {              showInfo ( )  {                  console .log('从cookie中获得用户信息:'  + cookieApi.get('guli_ucenter' ))         var  loginInfoStr = cookieApi.get('guli_ucenter' )         console .log(cookieApi.get('guli_ucenter' ))                  if  (loginInfoStr) {           this .loginInfo = JSON .parse(loginInfoStr)         }       },              logout ( )  {                  cookieApi.set('guli_token' , '' , { domain : 'localhost'  })         cookieApi.set('guli_ucenter' , '' , { domain : 'localhost'  })                  this .token = ''          this .loginInfo = {}                  this .$router.push({ path : '/'  })       }     } 
 
TODO:这里需要完善的地方有很多,登录校验(JWT strings must contain exactly 2 period characters. Found: 0)如果输入错误页面竟然显示cookie undefined,而且重新登录也不行,还有前端的信息提示
 
微信扫码登录(后端+前端) OAuth2 
一种方案,解决两个问题:
开放系统间授权 
分布式访问问题 
 
 

微信登录准备工作 
没事了要付钱,鹅肠真有你的
没事了老师共享账号,好耶!
 
配置文件 
service_ucenter模块的配置文件即可(只用于登录注册)
 
1 2 3 4 5 6 wx.open.app_id =wxed9954c01bb89b47 wx.open.app_secret =a7482517235173ddb4083788de60b90e wx.open.redirect_url =http://localhost:8160/api/ucenter/wx/callback 
 
常量类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package  com.atguigu.educenter.utils;import  org.springframework.beans.factory.InitializingBean;import  org.springframework.beans.factory.annotation.Value;import  org.springframework.stereotype.Component;@Component public  class  ConstantWxUtils  implements  InitializingBean   {    @Value("${wx.open.app_id}")      private  String appId;     @Value("${wx.open.app_secret}")      private  String appSecret;     @Value("${wx.open.redirect_url}")      private  String redirectUrl;     public  static  String WX_OPEN_APP_ID;     public  static  String WX_OPEN_APP_SECRET;     public  static  String WX_OPEN_REDIRECT_URL;     @Override      public  void  afterPropertiesSet ()  throws  Exception  {         WX_OPEN_APP_ID = appId;         WX_OPEN_APP_SECRET = appSecret;         WX_OPEN_REDIRECT_URL = redirectUrl;     } } 
 
生成二维码 controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Controller @CrossOrigin @RequestMapping("/api/ucenter/wx") public  class  WxApiController   {         @GetMapping("login")      public  String getWxCode ()  throws  UnsupportedEncodingException  {                  String baseUrl = "https://open.weixin.qq.com/connect/qrconnect"  +                 "?appid=%s"  +                 "&redirect_uri=%s"  +                 "&response_type=code"  +                 "&scope=snsapi_login"  +                 "&state=%s"  +                 "#wechat_redirect" ;                  String redirectUrl = URLEncoder.encode(ConstantWxUtils.WX_OPEN_REDIRECT_URL, "UTF-8" );                  String url = String.format(baseUrl, ConstantWxUtils.WX_OPEN_APP_ID, redirectUrl, "atguigu" );                  return  "redirect:"  + url;     } } 
 
注意这里需要修改端口号,因为用的是老师的url,所以要对应,nginx里也要改
这里我明白了一点nginx的作用,前端只需要访问nginx提供的9001端口号,而9001能够转发到8001等其他端口号,所以后端端口号改了只要该nginx就行,神奇!
 
获取扫描人信息 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  <dependencies >           <dependency >          <groupId > org.apache.httpcomponents</groupId >          <artifactId > httpclient</artifactId >      </dependency >           <dependency >          <groupId > commons-io</groupId >          <artifactId > commons-io</artifactId >      </dependency >           <dependency >          <groupId > com.google.code.gson</groupId >          <artifactId > gson</artifactId >      </dependency >  </dependencies > 
 
HttpClientUtils 
太长不复制了
 
controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Autowired private  UcenterMemberService memberService;@GetMapping("callback") public  String callback (String code, String status)  throws  Exception  {              String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token"  +             "?appid=%s"  +             "&secret=%s"  +             "&code=%s"  +             "&grant_type=authorization_code" ;          String accessTokenUrl = String.format(baseAccessTokenUrl, ConstantWxUtils.WX_OPEN_APP_ID, ConstantWxUtils.WX_OPEN_APP_SECRET, code);     String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);          Gson gson = new  Gson();     HashMap accessTokenMap = gson.fromJson(accessTokenInfo, HashMap.class);     String access_token = (String) accessTokenMap.get("access_token" );     String openid = (String) accessTokenMap.get("openid" );               UcenterMember member = memberService.getOpenIdMember(openid);     if  (member == null ) {                           String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo"  +                 "?access_token=%s"  +                 "&openid=%s" ;                  String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);         String userInfo = HttpClientUtils.get(userInfoUrl);                  HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);         String nickname = (String) userInfoMap.get("nickname" );         String headimgurl = (String) userInfoMap.get("headimgurl" );                  member = new  UcenterMember();         member.setOpenid(openid);         member.setNickname(nickname);         member.setAvatar(headimgurl);         memberService.save(member);     }          String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());          return  "redirect:http://localhost:3000?token="  + jwtToken; } 
 
service 1 2 3 4 5 6 7 8 @Override public  UcenterMember getOpenIdMember (String openid)   {    QueryWrapper<UcenterMember> wrapper = new  QueryWrapper<>();     wrapper.eq("openid" , openid);     UcenterMember member = baseMapper.selectOne(wrapper);     return  member; } 
 
前端 
取地址栏中的带名称的参数值(而不是xx/xx/xx这种使用this.$route.params.xx)使用this.$route.query.xxx
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 created ( )  {     this .token = this .$route.query.token   if  (this .token) {     this .wxLogin()   }   this .showInfo() }, methods : {     wxLogin ( )  {          cookieApi.set('guli_token' , this .token, { domain : 'localhost'  })          loginApi.getLoginMemberInfo().then(result  =>  {              cookieApi.set('guli_ucenter' , JSON .stringify(result.data.data.member), {         domain : 'localhost'        })     })          this .$router.push({ path : '/'  })   },      showInfo ( )  {     var  loginInfoStr = cookieApi.get('guli_ucenter' )          if  (loginInfoStr) {       this .loginInfo = JSON .parse(loginInfoStr)     }   } } 
 
注意把进入微信扫码的href改为http://localhost:8160/api/ucenter/wx/login即可