谷粒学院项目实战——04后台讲师管理模块(二)前端

一. 搭建项目前端页面环境

1. vue-admin-template-master

1. 简介
  1. vue-element-admin是基于element-ui 的一套后台管理系统集成方案。

  2. 功能: https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能

  3. GitHub地址: https://github.com/PanJiaChen/vue-element-admin

  4. 项目在线预览:https://panjiachen.gitee.io/vue-element-admin

2. 安装
  • 命令

    # 解压压缩包
    # 进入目录
    cd vue-element-admin-master
    
    # 安装依赖
    npm install
    
    # 启动。执行后,浏览器自动弹出并访问http://localhost:9527/
    npm run dev
    

2. vue-admin-template

1. 简介
  1. vueAdmin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模板进行二次开发。

  2. **GitHub地址:**https://github.com/PanJiaChen/vue-admin-template

  3. **建议:**你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来。

2. 安装
  • 命令

    # 解压压缩包
    # 进入目录
    cd vue-admin-template-master
    
    # 安装依赖
    npm install
    
    # 启动。执行后,浏览器自动弹出并访问http://localhost:9528/
    # 使用dev配置文件: dev.env.js
    npm run dev
    
3. vue-admin-template结构说明
  1. 架构框架入口

    index.html

    main.js

  2. 前端页面环境使用框架

    vue-admin-template模板 = vue + element-ui

  3. 框架build目录

    放项目构建的脚本文件

  4. config目录

    index.js --> useEslint: true 修改为false

    自动整理代码格式, 规则十分严格, 所以这里不使用

    谷粒学院项目实战——04后台讲师管理模块(二)前端

    dev.env.js --> 开发环境, 修改访问后端接口地址

    prod.env.js --> 测试环境

  5. node_modules

    下载的依赖

4. src目录
  1. api: 定义调用方法
  2. assets: 静态资源
  3. components: 插件/组件
  4. icons: 图标
  5. router: 路由
  6. store: 脚本文件, 一般不改
  7. styles: 样式文件, 一般不改
  8. utils: 工具类, 一般不改
  9. views: 视图, 具体页面

3. 实现简易登录

只是为了能登录前端页面而实现的简单功能, 后面还要重新实现包括权限控制在内的功能.

1. 修改前端的访问路径
  1. 修改前端中dev.env.js中指定的访问路径

    # 把登录请求地址改为本地: http://localhost:8001
    BASE_API: '"http://localhost:8001"',
    
    谷粒学院项目实战——04后台讲师管理模块(二)前端谷粒学院项目实战——04后台讲师管理模块(二)前端
2. 后端实现登录功能
  1. 前端登录需要传递的参数

    guli_web\src\store\modules\user.js

    进行登录调用2个方法, Login登录操作方法, GetInfo登录后获取用户信息方法. 所以, 创建接口两个方法实现登录; 其中Login方法, 返回token值, GetInfo返回roles, name和avatar三个值
    谷粒学院项目实战——04后台讲师管理模块(二)前端

  2. 在后端中创建一个登录的controller

    根据前端需要的参数名, 传递指定的参数

    @RestController
    @RequestMapping("/eduservice/user")
    public class EduLoginController {
        // login
        @PostMapping("/login")
        public ReturnType login(){
            // Login方法, 返回token值
            return ReturnType.ok().data("token", "admin");
        }
    
        // info
        @GetMapping("/info")
        public ReturnType info(){
            //  GetInfo返回roles, name和avatar三个值
            return ReturnType.ok()
                .data("roles", "[admin]")
                .data("name", "admin")
                .data("avatar", "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
        }
    }
    

  1. 修改前端登录时的访问地址

    guli_web\src\api\login.js

    根据controller中指定的访问路径, 进行填写

     import request from '@/utils/request'
      
      export function login(username, password) {
        return request({
          // 当前请求地址题提交方式
          // 需要和后端Controller中指定的路径保持一致
          url: '/eduservice/user/login',
          method: 'post',
          data: {
            username,
            password
          }
        })
      }
      
      export function getInfo(token) {
        return request({
          // 需要和后端Controller中指定的路径保持一致
          url: '/eduservice/user/info',
          method: 'get',
          params: { token }
        })
      }
      
      export function logout() {
        return request({
          url: '/user/logout',
          method: 'post'
        })
      }
    
    谷粒学院项目实战——04后台讲师管理模块(二)前端

  1. 前端登录时遇到跨域问题

    谷粒学院项目实战——04后台讲师管理模块(二)前端

4. 跨域问题

1. 出现原因
  1. 通过一个地址去访问另一个地址, 这个过程中如果访问协议, IP地址和端口号三个中任何一个不一样, 都会出现跨域问题

  2. 前端访问的是http://localhost:9528/ , 但是后端访问的却是http://localhost:8001/ . 端口号不一样, 所以会出现跨域的问题

2. 解决方式
  1. 在后端接口controller中添加注解@CrossOrigin [最主要的方法]

    谷粒学院项目实战——04后台讲师管理模块(二)前端
  2. 使用网关解决 [后面会讲]

5. 框架的使用

  1. 在router中index.js中添加路由

    guli_web\src\router\index.js

  2. 点击具体路由时, 显示指定页面的信息

    index.js中的component指定路由的对应页面

    component: () => import('@/views/dashboard/index')
    
    谷粒学院项目实战——04后台讲师管理模块(二)前端
  3. 在api文件夹中创建js文件, 定义接口地址和参数

    export function login(username, password) {
      return request({
        url: '/eduservice/user/login',
        method: 'post',
        data: {
          username,
          password
        }
      })
    }
    
  4. 在创建vue页面引入js文件, 调用方法实现功能

    import { getList } from '@/api/table'
    
    export default {
      data() {
        ...
      },
      created() {
        ...
      },
      methods: {
        ...
      }
    

二. 讲师查询功能

1. 分页查询
  1. 在vue目录下创建list.vue和save.vue两个页面

    谷粒学院项目实战——04后台讲师管理模块(二)前端

    <!-- list.vue页面内容 -->
    <template>
      <div class="app-container">
        讲师列表
      </div>
    
    </template>
    <!-- save.vue页面内容 -->
    <template>
      <div class="app-container">
        添加讲师
      </div>
    </template>
    
  2. 添加路由

    {
       path: '/teacher',
       component: Layout,
       redirect: '/teacher/table',
       name: '讲师管理',
       meta: { title: '讲师管理', icon: 'example' },
       children: 
       [
         {
           path: 'table',
           name: '讲师列表',
           component: () => import('@/views/edu/teacher/list'),
           meta: { title: '讲师列表', icon: 'table' }
         },
         {
           path: 'tree',
           name: '添加讲师',
           component: () => import('@/views/edu/teacher/save'),
           meta: { title: '添加讲师', icon: 'tree' }
         }
       ]
    },
    

    谷粒学院项目实战——04后台讲师管理模块(二)前端

  3. 编写teacher.js

    在api文件夹中创建teacher.js, 定义访问的接口地址

    import request from '@/utils/request'
    
    export default {
        // 1. 讲师列表(条件查询分页)
        // current: 当前页; limit: 每页记录数; teacherQuery: 条件对象
        getTeacherListPage(current, limit, teacherQuery){
            return request({
                // url: '/eduservice/teacher/pageTeacherCondition/' + current + '/' + limit,
                url: `/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,
                method:'post',
                // teacherQuery条件对象, 后端使用Req1uestBody获取数据
                // data表示把对象转换成json进行传递到接口里面
                data: teacherQuery
            })
        }
    }
    
  4. list.vue

    <template>
          <div class = "app-container">
              <!-- 
                  :data="数据集"
                  stripe: 创建带斑马纹的表格
                  border: 添加竖直边框
                -->
              <el-table
              v-loading="listLoading"  
              :data="list"
              border
              stripe
              style="width: 80%">
              
              <!-- id -->
              <!-- 
                  prop属性对应对象中的键名即可填入数据 
                  label属性来定义表格的列名
                  width: 对应宽度像素
                  min-width: 对应宽度百分比
                  -->
                  <el-table-column prop="id" label="ID" min-width="10%" align="center">
                      <!-- 这里不是读取的数据库中存放的id, 而是通过页码计算出来的id -->
                      <template slot-scope="scope">
                          {{(page - 1) * limit + scope.$index + 1}}
                      </template>
                  </el-table-column>
      
                  <!-- 姓名 -->
                  <el-table-column prop="name" label="姓名" min-width="10%" align="center"/>
      
                  <!-- 头衔 -->
                  <el-table-column prop="level" label="头衔" min-width="15%" align="center">
                      <!-- 
                          scope: 整个表格范围
                          scope.row: 整行
                          scope.row.level: 每行的level属性
      
                          ==: 判断值
                          ===: 判断类型和值
                      -->
                      <template slot-scope="scope">
                          {{scope.row.level === 1? '高级讲师': '首席讲师'}}
                      </template>
                  </el-table-column>
      
                  <!-- 资历 -->
                  <el-table-column prop="intro" label="资历" min-width="15%" align="center" />
      
                      <!-- 添加时间 -->
                  <el-table-column prop="gmtCreate" label="添加时间" min-width="20%" align="center" />
      
                      <!-- 排序 -->
                  <el-table-column prop="sort" label="排序" min-width="10%" align="center" />
      
                      <!-- 操作 -->
                  <el-table-column label="操作" min-width="20%" align="center">
                      <template slot-scope="scope">
                          <router-link :to="'/edu/teacher/edit' + scope.row.id">
                              <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
                          </router-link>
                          <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button>
                      </template>
                  </el-table-column>
                  </el-table>
              <!-- 
                  :total:总页数
                  :current-page: 当前页
                  :page-size: 页码数
                  :pager-count: 显示页码数
                  @current-change: 页码跳转时执行的函数
                  layout: 表示需要显示的内容,用逗号分隔,布局元素会依次显示。
               -->
              <el-pagination 
                  :total="total"  
                  background
                  :current-page="page" 
                  :page-size="limit" 
                  :pager-count="7"
                  @current-change="getList" 
                  style="text-align: right; width: 80%"
                  layout="total, prev, pager, next, jumper">
              </el-pagination>
          </div>
      </template>
      
      <script>
      // 引入调用teacher.js文件
      import teacher from '@/api/edu/teacher'
      
      export default {
          // 核心代码
      
          // 定义变量和初始值
          // 方式1
          // data: {
          // },
          // 方式2
          data() {
              return {
                  page: 1,            // 当前页
                  limit: 5,           // 页面大小
                  teacherQuery: {},   // 条件封装对象
                  total: 0,           // 总记录数
                  list: null          // 查询之后接口返回集合
              }
          },
      
          // 在页面渲染之前执行
          created() {
              this.getList()
          },
          // 创建具体的方法, 调用teacher.js中定义的方法
          methods: {
              // 讲师列表的方法
              // 如果不传参数, 默认显示第一页的内容
              // 如果传参数, 则显示指定页的内容
              getList(page = 1){
                  this.page = page
                  teacher.getTeacherListPage(this.page, this.limit, this.teacherQuery)
                      .then(response => { // 请求成功
                          // response接口返回的数据
                          // console.log(response)
                          this.list = response.data.rows
                          this.total = response.data.total
                          console.log(this.list)
                          console.log(this.total)
                      })     
                      .catch( error => {  // 请求失败
                          console.log(error)
                      })    
              }
          }
      }
      </script>
    
  5. 显示效果

    注意: 别忘了在后端代码中添加跨域的注解: @CrossOrigin

    谷粒学院项目实战——04后台讲师管理模块(二)前端

2. 多条件组合查询带分页
  1. 在表格上面添加查询的表单

    <!-- :inline="true": 一行显示 -->
    <el-form :inline="true" class="demo-form-inline">
        <el-form-item label="姓名">
            <el-input v-model="teacherQuery.name" placeholder="讲师名"></el-input>
        </el-form-item>
        <el-form-item label="头衔">
            <el-select v-model="teacherQuery.level" clearable placeholder="讲师头衔">
                <el-option label="高级讲师" :value="1"></el-option>
                <el-option label="首席讲师" :value="2"></el-option>
            </el-select>
        </el-form-item>
        <el-form-item>
            <el-date-picker 
               v-model="teacherQuery.begin"
               type="datetime"
               placeholder="选择开始时间"
               value-format="yyyy-MM-dd HH:mm:ss"
               default-time="00:00:00"
            />
        </el-form-item>
        <el-form-item>
            <el-date-picker 
               v-model="teacherQuery.end"
               type="datetime"
               placeholder="选择截止时间"
               value-format="yyyy-MM-dd HH:mm:ss"
               default-time="00:00:00"
            />
        </el-form-item>
    
        <el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button>
        <el-button type="default" @click="resetData()">清空</el-button>
    </el-form>
    
  2. 在methods中创建方法

    resetData(){
        // 清空表单数据
        this.teacherQuery = {}
        // 查询所有讲师数据
        this.getList()
    }
    
  3. 结果

    谷粒学院项目实战——04后台讲师管理模块(二)前端

三. 讲师删除功能

  1. 添加删除按钮, 并为按钮绑定事件

    <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeTeacherById()">删除</el-button>
    
  2. 在绑定事件的方法传递删除讲师的ID

    scope.row.id: 获取id

    <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeTeacherById(scope.row.id)">删除</el-button>
    
  3. teacher.js中编写删除讲师的函数

    // 2. 删除讲师
    delTeacherById(id){
        return request({
            url: `/eduservice/teacher/del/${id}`,
            method:'delete'
        })
    }
    
  4. 调用removeTeacherById方法进行删除

    removeTeacherById(id){
        this.$confirm('此操作将永久删除该讲师, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
        })
            .then(() => {
            // 调用删除的方法
            teacher.delTeacherById(id)
                .then(response => { // 删除成功
                // 提示信息
                this.$message({
                    type: 'success',
                    message: '删除成功!'
                });
                // 回到列表页面
                this.getList()
            })         
        });
    }
    

四. 讲师添加功能

  1. 在teacher.js中创建添加讲师的方法

    // 3. 添加讲师
    addTeacher(teacher){
        return request({
            url: `/eduservice/teacher/addTeacher`,
            method: 'post',
            data: teacher
        })
    }
    
  2. save.vue

    <template>
        <div class="app-container">
            <el-form label-width="120px">
            <el-form-item label="讲师名称">
                <el-input v-model="teacher.name"></el-input>
            </el-form-item>
    
            <el-form-item label="讲师排序">
                <el-input-number v-model="teacher.sort" controls-position="right" />
            </el-form-item>
    
            <el-form-item label="讲师头衔">
                <el-select v-model="teacher.level" clearable placeholder="请选择">
                    <el-option label="高级讲师" :value="1"></el-option>
                    <el-option label="首席讲师" :value="2"></el-option>
                </el-select>
            </el-form-item>
    
            <el-form-item label="讲师资历">
                <el-input v-model="teacher.career"></el-input>
            </el-form-item>
    
            <el-form-item label="讲师简介">
                <el-input v-model="teacher.intro" :rows="10" type="textarea"></el-input>
            </el-form-item>
                    <el-form-item>
                <!-- disabled="saveBtnDisabled": 按钮是否禁用, 防止多次提交 -->
                <el-button type="primary" @click="saveOrUpdate()" :disabled="saveBtnDisabled">保存</el-button>
                <el-button @click="resetData()">重置</el-button>
            </el-form-item>
            </el-form>
        </div>
    </template>
    
    <script>
    // 引入调用teacher.js文件
    import teacher from '@/api/edu/teacher'
    
    export default {
        data(){
            return {
                // 封装初始化值
                teacher: {
                    name: '',
                    sort: 0,
                    level: 1,
                    career: '',
                    intro: '',
                    avatar: ''
                },
                saveBtnDisabled: false
            }
        },
        methods: {
            saveOrUpdate(){
                // 添加
                this.saveTeacher()
            },
            // 添加讲师
            saveTeacher(){
                teacher.addTeacher(this.teacher)
                    .then(response => {
                        // 1. 提示信息
                        this.$message({
                            type: 'success',
                            message: '添加成功'
                        })
    
                        // 2. 回到列表页面, 路由跳转
                        this.$router.push({path: '/teacher/list'})
                    })
                    .catch(error => {
                        console.log(error + "添加失败")
                    })
            },
           resetData(){
                this.teacher = {}
            }
        }
    }
    </script>
    

五. 讲师修改功能

1. 在router中添加编辑讲师的路由
  1. 代码

    {
        // :id相当于是占位符
        path: 'edit/:id',
        name: '编辑讲师',
        component: () => import('@/views/edu/teacher/save'),
        meta: { title: '编辑讲师', noCache: true },
        // 隐藏不显示
        hidden: true
    }
    
  2. 截图

    谷粒学院项目实战——04后台讲师管理模块(二)前端

2. list.vue页面添加一个修改按钮, 并通过路由方法跳转
  1. 代码

    <!-- 路由方法 -->
    <!-- 这里的路径要和router中指定的路由保持一致 -->
    <router-link :to="'/teacher/edit/' + scope.row.id">
        <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
    </router-link>
    
  2. 截图​ 谷粒学院项目实战——04后台讲师管理模块(二)前端

3. 在表单页面实现数据回显
  1. teacher.js中定义根据id查询的接口

    // 4. 通过id查询讲师(用于修改)
    getTeacherInfo(id) {
        return request({
            url: `/eduservice/teacher/getTeacher/${id}`,
            method: 'get'
        })
    }
    
  2. save.vue页面中执行, 通过id查询出讲师信息的方法

    // 根据讲师id查询
    getInfo(id){
        teacher.getTeacherInfo(id)
            .then(response => {
            // 将查询到的讲师信息, 赋值给teacher
            this.teacher = response.data.teacher
        })
    }
    
  3. 调用

    因为添加和修改都使用save页面,

    区别添加还是修改:

    添加时路径中没有id, 不调用回显

    修改时路径中有id, 调用回显

    所以可通过判断路径中是否包含id来判断是否调用回显

    在save.vue中创建created方法, 当路径中包含id时, 创建调用之前定义的方法

    created(){
        if(this.$route.params && this.$route.params.id) {
            // 得到路由中的id参数值
            const id = this.$route.params.id
            this.getInfo(id)
        }
    },
    
4. 修改讲师信息
  1. teacher.js中创建修改讲师的方法

    // 5. 修改讲师
    updateTeacherInfo(teacher) {
        return request({
            url: `/eduservice/teacher/updateTeacher`,
            method: 'post',
            data: teacher
        })
    }
    
  2. save.vue中创建执行修改操作

    // 修改讲师
    updateTeacher(){
        teacher.updateTeacherInfo(this.teacher)
            .then(response => {
            // 1. 提示信息
            this.$message({
                type: 'success',
                message: '修改成功'
            })
    
            // 2. 回到列表页面, 路由跳转
            this.$router.push({path: '/teacher/list'})
        })
    }
    
  3. 表单提交时判断是添加讲师还是修改讲师

    saveOrUpdate(){
        // 判断是修改还是添加
        // 根据teacher中是否有id, 来判断是修改还是添加
        if(!this.teacher.id){
            this.saveTeacher()
        } else {
            this.updateTeacher()
        }           
    },
    
5. 存在的问题
  1. 问题

    在修改页面时, 点击添加页面, 虽然路径变成了添加的路径, 但是表单中依然保留着修改页面中查询出来的值

  2. 错误的解决办法(如果时添加讲师, 则在created中将teacher清空)

    created(){
        // 如果路径中有id, 做修改
        if(this.$route.params && this.$route.params.id) {
            // 得到路由中的id参数值
            const id = this.$route.params.id
            this.getInfo(id)
        } else {
            // 路径中没有id, 做添加
            this.teacher = {}
        }
    },
    
  3. 错误原因

    多次路由跳转到同一页面了, 在页面中created方法只会在第一次执行, 后面再进行跳转并不会执行

5. 最终的解决方法(监听)

当路由发生变化时, 监听方法就会被执行

  1. 将上述的create的函数体抽取出来, 定义再methods中

    init(){
        // 如果路径中有id, 做修改
        if(this.$route.params && this.$route.params.id) {
            // 得到路由中的id参数值
            const id = this.$route.params.id
            this.getInfo(id)
        } else {
            // 路径中没有id, 做添加
            this.teacher = {
        }
    },
    
  2. 在created中调用

    created(){
        this.init()
    },
    
  3. 在监听方法中调用

    // 监听
    watch: {
        // 路由变化的方式
        // 当路由发生变化时, 方法就会执行
        $route(to, from) {
            this.init()
        }
    },
    

    谷粒学院项目实战——04后台讲师管理模块(二)前端

六. 上传头像到阿里云OSS

准备工作: 创建操作阿里云OSS许可证(阿里云颁发id和密钥)

1. 新建云存储微服务

1. 创建service_oss模块
  1. service模块中创建service_oss子模块

    谷粒学院项目实战——04后台讲师管理模块(二)前端

  2. 添加依赖

    https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.939.aeb746a1bEOIBh

    <!-- 阿里云OSS依赖 -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
    </dependency>
    <!-- 日期工具栏依赖 -->
    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
    </dependency>
    
  3. 创建application.properties配置文件

    在 https://usercenter.console.aliyun.com/#/manage/ak里查看自己AccessKey信息

    # 服务端口
      server.port=8002
      
      # 服务名
      spring.application.name=service_oss
      
      # 环境设置: dev, test, prod
      spring.profiles.active=dev
      
      # 阿里云OSS
      # 前后都不能有多余的空格
      # 不同的服务器, 地址不同
      # 地域节点
      aliyun.oss.file.endpoint=oss-cn-hangzhou.aliyuncs.com
      aliyun.oss.file.keyid=LTAI4GEp9bvmCBpFukScHVAv
      aliyun.oss.file.keysecret=6osNI3UUgGrN96exJCzREMtPNn37zc
      
      # bucket可以在控制台创建, 也可以使用java代码创建
      aliyun.oss.file.bucketname=guli-avatar-warehouse
    
2. 启动模块
  1. 效果谷粒学院项目实战——04后台讲师管理模块(二)前端

  2. 原因

    因为现在的模块不需要操作数据库, 只做上传到OSS功能, 没有配置数据库

  3. 解决方法

    在启动类上面添加属性, 默认不去加载数据库配置

    // exclude = DataSourceAutoConfiguration.class: 不自动加载数据源
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    public class OssApplication {
        public static void main(String[] args) {
            SpringApplication.run(OssApplication.class, args);
        }
    }
    

2. 实现文件上传

1. 从配置文件读取常量
  1. com.hjf.utils包下创建一个创建常量读取工具类:ConstantPropertiesUtils

    package com.hjf.utils;
    
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    // 常量类,读取配置文件application.properties中的配置
    @Component
    // 用spring的 InitializingBean 的 afterPropertiesSet 来初始化配置信息,这个方法将在所有的属性被初始化后调用。
    public class ConstantPropertiesUtils implements InitializingBean {
        // 使用@Value读取application.properties里的配置内容
        @Value("${aliyun.oss.file.endpoint}")
        private String endpoint;    // 地域节点
        @Value("${aliyun.oss.file.keyid}")
        private String keyId;
        @Value("${aliyun.oss.file.keysecret}")
        private String keySecret;   // 密钥
        @Value("${aliyun.oss.file.bucketname}")
        private String bucketName;
    
        // 定义公开的静态常量
        public static String END_POINT;
        public static String KEY_ID;
        public static String KEY_SECRET;
        public static String BUCKET_NAME;
    
        // 属性值被初始化后, 会执行此方法
        @Override
        public void afterPropertiesSet() throws Exception {
            END_POINT = endpoint;
            KEY_ID = keyId;
            KEY_SECRET = keySecret;
            BUCKET_NAME = bucketName;
        }
    }
    
2. 实现Service层
  1. OssService

    public interface OssService {
        // 上传头像到OSS
        public String uploadFileAvatar(MultipartFile file);
    }
    
  2. OssServiceImpl

    https://help.aliyun.com/document_detail/84781.htm?spm=a2c4g.11186623.2.2.5d437a74XlvD2f#concept-84781-zh

    @Service
    public class OssServiceImpl implements OssService {
        @Override
        public String uploadFileAvatar(MultipartFile file) {
            // 工具类获取值
            String endpoint = ConstantPropertiesUtils.END_POINT;
            String accessKeyId = ConstantPropertiesUtils.KEY_ID;
            String accessKeySecret = ConstantPropertiesUtils.KEY_SECRET;
            String bucketName = ConstantPropertiesUtils.BUCKET_NAME;
    
            try {
                // 创建OSSClient实例。
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    
                // 获取上传文件输入流。
                InputStream inputStream = file.getInputStream();
    
                // 获取文件名称
                String fileName = file.getOriginalFilename();
    
                /**
                 * 调用oss方法实现上传
                 *  1. 第一个参数: Bucket名称
                 *  2. 第二个参数: 上传到oss文件路径和文件名称
                 *  3. 第三个参数: 上传文件输入流
                 */
                ossClient.putObject(bucketName, fileName, inputStream);
    
                // 关闭OSSClient。
                ossClient.shutdown();
    
                // 把上传之后的文件路径返回
                // 需要把上传到阿里云OSS路径手动拼接起来
                // https://guli-avatar-warehouse.oss-cn-hangzhou.aliyuncs.com/01.png
                String url = "https://" + bucketName + "." + endpoint + "/" + fileName;
                return url;
            } catch (IOException e) {
                e.printStackTrace();
                return "";
            }
        }
    }
    
3. 实现controller层
  • 代码

    @RestController
    @RequestMapping("/eduoss/fileoss")
    @CrossOrigin
    public class OssController {
        @Autowired
        private OssService ossService;
    
        // 上传头像
        @PostMapping
        public ReturnType uploadOssFile(MultipartFile file){
            // 获取上传的文件
            // 返回上传到OSS的路径
            String url = ossService.uploadFileAvatar(file);
    
            return ReturnType.ok().data("url", url);
        }
    }
    
5. 效果
  1. 通过swagger上传图片

    谷粒学院项目实战——04后台讲师管理模块(二)前端

  2. 上传到阿里云OSS

    谷粒学院项目实战——04后台讲师管理模块(二)前端

6. 存在的问题
  1. 多次上传相同名称的文件时, 后面上传的文件会覆盖之前同名的文件

    在文件名称添加随机唯一值, 让每个文件名称不同

  2. 把文件进行分类管理

    根据日期进行分类(根据年月日)

  3. 实现

    // 1. 在文件名称里面添加随机唯一的的值
    String uuid = UUID.randomUUID().toString().replace("-", "");
    
    // 2. 把文件按照日期进行分类
    // 获取当前日期: 2020/10/06
    String datePath = new DateTime().toString("yyyy/MM/dd");
    // 拼接
    fileName = datePath + "/" + uuid + fileName;
    

    谷粒学院项目实战——04后台讲师管理模块(二)前端

3. 前端实现

1. 创建上传组件,

在添加讲师页面, 创建上传组件, 实现上传

  1. 复制源码中的ImageCropperPanThumb组件到src/components

    谷粒学院项目实战——04后台讲师管理模块(二)前端

  2. 添加讲师页面使用这个上传组件

    <!-- 讲师头像 TODO -->
    <el-form-item label="讲师头像">
        <!-- 头衔缩略图 -->
        <pan-thumb :image="teacher.avatar"/>
        <!-- 文件上传按钮 -->
        <el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
        </el-button>
    
        <!--
    v-show:是否显示上传组件
    :key:类似于id,如果一个页面多个图片上传控件,可以做区分
    :url:后台上传的url地址
    field="file": 文件上传
    @close:关闭上传组件
    @crop-upload-success:上传成功后的回调 
    -->
        <image-cropper
           	v-show="imagecropperShow"
      		:width="300"
            :height="300"
            :key="imagecropperKey"
            :url="BASE_API+'/admin/oss/file/upload'"
            field="file"
            @close="close"
            @crop-upload-success="cropSuccess"/>
    </el-form-item>
    
2. 使用上传组件
  1. data()中定义变量和赋初始值

    saveBtnDisabled: false,
    // 上传弹框组件是否显示
    imagecropperShow: false,
    // 上传组件唯一标识
    imagecropperKey: 0,
    // 获取dev.env.js里面的地址
    BASE_API: process.env.BASE_API,
    
    谷粒学院项目实战——04后台讲师管理模块(二)前端
  2. 引入组件

    // 引入组件
    import ImageCropper from '@/components/ImageCropper'
    import PanThumb from '@/components/PanThumb'
    
  3. 声明组件

    // 声明组件
    components: { ImageCropper, PanThumb },
    

    谷粒学院项目实战——04后台讲师管理模块(二)前端

  4. 修改上传接口地址

    谷粒学院项目实战——04后台讲师管理模块(二)前端
  5. 关闭弹窗的方法

    // 关闭上传弹框的方法
    close(){
        this.imagecropperShow = false
    },
    
  6. 上传成功的方法

    // 上传成功方法
    cropSuccess(data){
        this.close()
        // 上传之后接口返回图片地址
        this.teacher.avatar = data.url
    }
    
3. 存在的问题
  1. 问题

    点击更换头像后, 如果要再次更换头像, 此时之前选中的头像会直接上传到阿里云的OSS, 然后再次点击更换头像, 才能生效

  2. 原因

    因为上传组件的唯一标识imagecropperShow没有更改的原因

  3. 解决方法

    每次关闭弹窗后(调用close方法时), 都修改唯一标识

    这里选择每次调用close时就将 imagecropperShow+ 1

    close(){
        this.imagecropperShow = false
        this.imagecropperKey = this.imagecropperKey + 1
    },
    

七. Nginx

1. 功能
  1. 请求转发
  2. 负载均衡
  3. 动静分离
2. 配置
  1. 修改nginx默认端口号 80 --> 81

    server {
        # listen       80;
        listen       81;
        server_name  localhost;
    
    
  2. 配置nginx转发规则

    server {
        # 对外监听端口 
        listen       9001;
        # 主机地址
        server_name  localhost;
    
        # 匹配路径
        location ~ /eduservice/ {
            # 转发服务器地址
            proxy_pass http://localhost:8001;
        }
    
        location ~ /eduoss/ {
            proxy_pass http://localhost:8002;
        }
    }
    
  3. 将dev.env.js中BASE_API改为nginx的地址

    谷粒学院项目实战——04后台讲师管理模块(二)前端

3. 启动项目
  1. 登录

    谷粒学院项目实战——04后台讲师管理模块(二)前端
  2. 修改

    谷粒学院项目实战——04后台讲师管理模块(二)前端
上一篇:2021年华中师范大学计算机学院复试回忆


下一篇:数组和元素