若依项目源码阅读

源码阅读

前端代码分析

代码生成器生成的前端代码有两个,分别是course.js用于向后端发送ajax请求的接口代码,另一个是index.vue,用于在浏览器展示课程管理的视图组件。前端的代码是基于vue3+elementplus。

template用于展示前端组件别的标签,script用于编写组件的逻辑代码。

在这里插入图片描述

在这里插入图片描述

搜索表单:
在这里插入图片描述

课程编码:

搜索表单区域用到了elementplus的el表单,:model是用于双向绑定,将前端录入的条件封装给详细的对象queryParams。v-show是控制搜索栏显示还是隐藏。

第一个表单项为课程编码,里面包含一个input文本框,v-model是queryParams.code编码的双向绑定,用户输入的值会绑定到code中。第二个placeholder是灰色文本占位符的提示信息,clearable是用于清理用户在文本框中输入的内容,恢复到占位符提示信息。最后一个keyup.enter是键盘的回车事件,用户点击键盘的回车就会调用handleQuery方法完成搜索,等同于搜索按钮。

在这里插入图片描述

课程学科:

第二个表单项为课程学科,这里面涉及到下拉框和数据字典。下拉框用到select标签,v-model用于双向绑定,placeholder用于灰色显示提示信息,clearable用于清理文本框的内容恢复提示信息。el-option标签是下拉选项,v-for用于遍历学科的数据字典列表,展示的信息是label属性,用户选择完提交的就是value属性,一个是字典标签一个是字典值。

在这里插入图片描述

在这里插入图片描述

课程名称和适用人群也属于文本框,可以参考课程编码。

搜索和重置按钮:

在这里插入图片描述

按钮区域:

在这里插入图片描述

新增按钮:

type、plain和icon是控制按钮的颜色样式和图标的。@click是点击事件会调用相应的方法。v-hasPermi是一个自定义的属性,会结合RBAC权限控制模型来完成菜单按钮的显示和隐藏。

在这里插入图片描述

修改按钮:

修改按钮会比新增按钮多一个属性disable,这个属性是控制该按钮是否可用的,会结合下面的js代码,选择对应的对话框,这个按钮就会可用,否则不可用,来做到动态的控制。

在这里插入图片描述

删除和导出按钮可以参考新增和修改。

自定义组件按钮:

right-toolbar,该组件控制两个按钮,点击第一个按钮会触发showSearch属性的修改,可以控制搜索栏是否显示或者隐藏。点击第二个按钮会触发queryTable事件,会调用getList事件重新去加载表格展示的数据。

数据展示表格:

使用的是el-table标签,第一个属性v-loading是一个指令用于控制表格的加载状态,如果后台网络非常慢没有返回,前端就会显示一个表格的加载状态来给用户一个友好的提示。可以点击浏览器的开发者工具,找到网络属性,将网速调慢。data是绑定属性用于指定表格的数据源,比如返沪的课程列表,所有的数据都会封装到courseList中,表格就会遍历展示每一条数据。selection-change是事件监听器用于监听选中行的变化,比如选中第一行复选框,事件就会触发调用handleSelectionChange方法来处理后续的业务逻辑。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

复选框:

第一列展示的并不是数据的本身,通过type的属性设置为selection来指定为复选框,用户勾中其中一个复选框,就会触发事件监听调用handleSelectionChange方法来处理后续的业务逻辑,第二个属性指定列的宽度55像素,align用于指定对其方式为居中。

在这里插入图片描述

在这里插入图片描述

从第二列开始就是展示具体的数据源内容,通过label属性来指定列的标题,prop展示该列具体展示的内容。

课程学科:

数据库存放的是数据字典的值,但是页面展示的字典标签,使用模板插槽做到的,通过scope获取整个表格的数据,取出当前行拿到字典值value,将字典值交给dict-tag组件,该组件就拿字典值匹配字典数据列表,找到该字典值对应的字典标签,将其显示到前端页面。

在这里插入图片描述

操作:

也使用了模板插槽,修改按钮的前三个属性是用来控制样式和图标的,click是绑定点击事件,会将当前行的数据传给对应的方法执行相应的业务逻辑。c-hasPermi是自定义权限相关的属性。

分页栏:

分页栏用的是pagination标签,v-show是做判断的,如果返回的数据大于0条,分页栏就会显示,反之就会隐藏。:total展示总条数,是从后端查询返回。page会展示分页页码,并对当前页进行高亮。limit限制一页显示的条数,默认是10条。@pagination是分页事件,当用户进行分页操作时,也就是一页10条改为一页20条,或者从第一页跳转到第二页,就会调用getList方法完成新数据的查询。

在这里插入图片描述

添加或修改课程对话框:

在这里插入图片描述

对话框使用的是el-dialog标签,该标签默认是隐藏的,当用户点击新增按钮,通过双向绑定v-model将对话框的值open改为true,对话框就能弹出了,默认将对话框元素添加到body元素上,就可以在页面进行展示。对话框的标题不是写死的,而是通过属性动态绑定的,因为新增和修改用的是同一个对话框。对话框内部提供了表单元素,通过model进行双向绑定,用户输入的数据课程编码、学科等都会封装给表单对象form。rules是表单的规则,比如前面都加*,用户没填会进行校验。

在这里插入图片描述

代码:
course.js:

// 引入request请求工具类,工具类内部封装了axios基础代码,
// 包括请求的拦截器和响应的拦截器,每隔方法只需要调用这个工具类即可。
import request from '@/utils/request'

// 查询课程管理列表
// 用户输入的参数,封装给query对象。
export function listCourse(query) {
  // 调用工具类将参数传过去,项后台发送请求,完成数据列表查询,并返回给前端展示。
  return request({
    url: '/course/course/list',
    method: 'get',
    params: query
  })
}

// 查询课程管理详细
// 当用户点击修改按钮时,需要根据id去查询
export function getCourse(id) {
  return request({
    url: '/course/course/' + id,
    method: 'get'
  })
}

// 新增课程管理
// 当用户点击新增按钮时,弹筐里输入每个课程的信息,将数据封装给data对象。
export function addCourse(data) {
  return request({
    url: '/course/course',
    method: 'post',
    data: data
  })
}

// 修改课程管理
// 当用户修改之后,会将修改后的值传给data,与新增相比会多一个i,以id为条件去更新数据库
export function updateCourse(data) {
  return request({
    url: '/course/course',
    method: 'put',
    data: data
  })
}

// 删除课程管理
// 支持接收一个或多个选中的课程id
export function delCourse(id) {
  return request({
    url: '/course/course/' + id,
    method: 'delete'
  })
}

index.vue:

<template>
  <div class="app-container">
    <!-- 搜索表单-start -->
    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="课程编码" prop="code">
        <el-input
          v-model="queryParams.code"
          placeholder="请输入课程编码"
          clearable  
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="课程学科" prop="subject">
        <el-select v-model="queryParams.subject" placeholder="请选择课程学科" clearable>
          <el-option
            v-for="dict in course_subject"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="课程名称" prop="name">
        <el-input
          v-model="queryParams.name"
          placeholder="请输入课程名称"
          clearable
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="适用人群" prop="applicablePerson">
        <el-select v-model="queryParams.applicablePerson" placeholder="请选择适用人群" clearable>
          <el-option
            v-for="dict in range"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- 搜索表单-end -->

    <!-- 按钮区域-start-->
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="Plus"
          @click="handleAdd"
          v-hasPermi="['course:course:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="Edit"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['course:course:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="Delete"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['course:course:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="Download"
          @click="handleExport"
          v-hasPermi="['course:course:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <!-- 按钮区域-end-->

    <!-- 数据展示表格区域-start -->
    <el-table v-loading="loading" :data="courseList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="课程id" align="center" prop="id" />
      <el-table-column label="课程编码" align="center" prop="code" />
      <el-table-column label="课程学科" align="center" prop="subject">
        <template #default="scope">
          <dict-tag :options="course_subject" :value="scope.row.subject"/>
        </template>
      </el-table-column>
      <el-table-column label="课程名称" align="center" prop="name" />
      <el-table-column label="价格" align="center" prop="price" />
      <el-table-column label="适用人群" align="center" prop="applicablePerson">
        <template #default="scope">
          <dict-tag :options="range" :value="scope.row.applicablePerson"/>
        </template>
      </el-table-column>
      <el-table-column label="课程介绍" align="center" prop="info" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['course:course:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['course:course:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 数据展示表格区域-end -->

    <!-- 分页区域-start-->
    <pagination
      v-show="total>0"
      :total="total"
      v-model:page="queryParams.pageNum"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- 分页区域-end-->

    <!-- 添加或修改课程管理对话框 -->
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="courseRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="课程编码" prop="code">
          <el-input v-model="form.code" placeholder="请输入课程编码" />
        </el-form-item>
        <el-form-item label="课程学科" prop="subject">
          <el-select v-model="form.subject" placeholder="请选择课程学科">
            <el-option
              v-for="dict in course_subject"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="课程名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入课程名称" />
        </el-form-item>
        <el-form-item label="价格" prop="price">
          <el-input v-model="form.price" placeholder="请输入价格" />
        </el-form-item>
        <el-form-item label="适用人群" prop="applicablePerson">
          <el-select v-model="form.applicablePerson" placeholder="请选择适用人群">
            <el-option
              v-for="dict in range"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="课程介绍" prop="info">
          <el-input v-model="form.info" placeholder="请输入课程介绍" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确 定</el-button>
          <el-button @click="cancel">取 消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup name="Course">
// 引入后端api接口
import { listCourse, getCourse, delCourse, addCourse, updateCourse } from "@/api/course/course";

// 获取当前实例的代理对象,用于访问组件数据、方法。proxy就是实例的代理对象。
const { proxy } = getCurrentInstance();
// 获取课程学科的数据字典,调用查询课程学科的数据字典,将列表封装给course_subject对象
const { range, course_subject } = proxy.useDict('range', 'course_subject');

// 通过ref定义简单类型的响应式数据
// 列表数据,接收后端返回的列表
const courseList = ref([]);
// 是否显示弹框,默认是隐藏的
const open = ref(false);
// 是否显示加载状态
const loading = ref(true);
// 是否显示搜索栏
const showSearch = ref(true);
// 复选框,被选中id的数组
const ids = ref([]);
// 复选框,是否单选,用于高亮修改、删除按钮
const single = ref(true);
// 复选框,是否多选,仅高亮删除按钮
const multiple = ref(true);
// 总记录数
const total = ref(0);
// 用于区分新增、修改对话框标题
const title = ref("");

// 定义reactive响应式对象
const data = reactive({
  // 新增或修改表单双向绑定的数据
  form: {},
  //搜索条件参数的双向绑定数据
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    code: null,
    subject: null,
    name: null,
    applicablePerson: null,
  },
  // 表单校验规则的制定
  rules: {
    code: [
      { required: true, message: "课程编码不能为空", trigger: "blur" }
    ],
    subject: [
      { required: true, message: "课程学科不能为空", trigger: "change" }
    ],
    name: [
      { required: true, message: "课程名称不能为空", trigger: "blur" }
    ],
    price: [
      { required: true, message: "价格不能为空", trigger: "blur" }
    ],
    applicablePerson: [
      { required: true, message: "适用人群不能为空", trigger: "change" }
    ],
    info: [
      { required: true, message: "课程介绍不能为空", trigger: "blur" }
    ],
  }
});

// 为了方便上面三个属性form、queryParams和 rules的操作,把reactive对象转为ref响应式对象,单独操作这三个部分。
const { queryParams, form, rules } = toRefs(data);

// 展示每个方法的作用
/** 查询课程管理列表 */
function getList() {
  // 显示表格加载状态
  loading.value = true;
  // 调用api接口的listCourse,将前端录入的参数作为条件传过去,向后台发送请求进行查询
  // 后台返回的结果会封装到response对象,包含课程列表和总记录数,
  listCourse(queryParams.value).then(response => {
    courseList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}

// 取消按钮
function cancel() {
  open.value = false;
  // 调用下面的reset方法
  // 表单重置会将双向绑定的内容清空,再把表单显示的内容重置掉。
  reset();
}

// 表单重置
function reset() {
  form.value = {
    id: null,
    code: null,
    subject: null,
    name: null,
    price: null,
    applicablePerson: null,
    info: null,
    createTime: null,
    updateTime: null
  };
  proxy.resetForm("courseRef");
}

/** 搜索按钮操作 */
function handleQuery() {
  // 先将本页设置为第一页,因为上传的搜索条件可能与本次不一样。
  queryParams.value.pageNum = 1;
  //调用getList方法向后台发送请求。
  getList();
}

/** 重置按钮操作 */
// 将搜索框的全部内容清空,再调用handleQuery完成无条件搜索。
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}

// 多选框选中数据
// 用户点击复选框的勾选会触发事件执行该方法,将选中的复选框对象传递过来,拿到复选框对象select
function handleSelectionChange(selection) {
  // 调用对象的map方法遍历取每个复选框的id,封装给ids的响应式数组对象
  ids.value = selection.map(item => item.id);
  // 检查selection数组的长度是否不等于1。如果不等于1,说明选中的复选框数量不是单个,那么single.value就会被设置为true,表示当前没有选中单个复选框。如果selection的长度等于1,那么single.value就会被设置为false,表示当前选中了单个复选框。
  single.value = selection.length != 1;
  // 检查selection数组是否为空。如果数组为空,即没有复选框被选中,那么multiple.value就会被设置为true,表示当前没有选中任何复选框。如果数组不为空,即至少有一个复选框被选中,那么multiple.value就会被设置为false,表示当前选中了至少一个复选框。
  multiple.value = !selection.length;
}

/** 新增按钮操作 */
function handleAdd() {
  // 清空原有表单数据
  reset();
  // 打开对话框
  open.value = true;
  title.value = "添加课程管理";
}

/** 修改按钮操作 */
// 接收当前的行对象
function handleUpdate(row) {
  // 重置表单
  reset();
  // 取出当前行的id或选中其中一个的id
  const _id = row.id || ids.value
  // 以id为条件进行后端的查询,将课程对象封装给response对象
  getCourse(_id).then(response => {
    //在表单中进行数据回显
    form.value = response.data;
    //打开对话框
    open.value = true;
    title.value = "修改课程管理";
  });
}

/** 提交按钮 */
// 对应新增和修改的确定按钮
function submitForm() {
// 对表单进行校验,正则规则、是否必填项等。校验通过valid为true
  proxy.$refs["courseRef"].validate(valid => {
    if (valid) {
      if (form.value.id != null) { 
        // 如果表单对象中包含id属性,代表修改操作,调用api接口的修改方法。
        updateCourse(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          // 关闭对话框
          open.value = false;
          // 再执行一次查询操作,展示最新的内容。
          getList();
        });
      } else {
        // 如果表单对象中包含id属性,代表新增操作,调用api接口的修改方法。
        addCourse(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}

/** 删除按钮操作 */
// 支持单个和批量删除,
function handleDelete(row) {
  // row对象可能是一行中取出一个id也可能是数组
  const _ids = row.id || ids.value;
  // 防止用户的误操作,提供一个确认框让用户进行二次确认。
  proxy.$modal.confirm('是否确认删除课程管理编号为"' + _ids + '"的数据项?').then(function() {
    return delCourse(_ids);
  }).then(() => {
    getList();
    proxy.$modal.msgSuccess("删除成功");
  }).catch(() => {});
}

/** 导出按钮操作 */
function handleExport() {
  proxy.download('course/course/export', {
    ...queryParams.value
  }, `course_${new Date().getTime()}.xlsx`)
}
// 页面加载时执行-查询课程管理列表
getList();
</script>
后端源代码分析

后端代码结构:
在这里插入图片描述

CourseController:

Controller主要是接收前端的请求,调用Service处理业务逻辑并返回结果。

ruoyi-admin模块下找到这个类:com.ruoyi.web.controller.course.CourseController里面有5个对应的方法接口,这写接口都遵循了Rest风格,get查询、post新增、put修改和delete删除详细代码如下:

package com.sky.course.controller;

import com.sky.common.annotation.Log;
import com.sky.common.core.controller.BaseController;
import com.sky.common.core.domain.AjaxResult;
import com.sky.common.core.page.TableDataInfo;
import com.sky.common.enums.BusinessType;
import com.sky.common.utils.poi.ExcelUtil;
import com.sky.course.domain.Course;
import com.sky.course.service.ICourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 课程管理Controller
 *
 * @author itheima
 */
@RestController
@RequestMapping("/course/course")
public class CourseController extends BaseController
{
    @Autowired
    private ICourseService courseService;

    /**
     * 查询课程管理列表
     */
    @PreAuthorize("@ss.hasPermi('course:course:list')")
    @GetMapping("/list")
    public TableDataInfo list(Course course)
    {
        //1. 开启分页
        startPage();
        //2. 查询课程列表
        List<Course> list = courseService.selectCourseList(course);
        //3. 返回表格分页数据对象
        return getDataTable(list);
    }

    /**
     * 导出课程管理列表
     */
    @PreAuthorize("@ss.hasPermi('course:course:export')")
    @Log(title = "课程管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, Course course)
    {
        List<Course> list = courseService.selectCourseList(course);
        ExcelUtil<Course> util = new ExcelUtil<Course>(Course.class);
        util.exportExcel(response, list, "课程管理数据");
    }

    /**
     * 获取课程管理详细信息
     */
    @PreAuthorize("@ss.hasPermi('course:course:query')")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    {
        return success(courseService.selectCourseById(id));
    }

    /**
     * 新增课程管理
     */
    @PreAuthorize("@ss.hasPermi('course:course:add')")
    @Log(title = "课程管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody Course course)
    {
        return toAjax(courseService.insertCourse(course));
    }

    /**
     * 修改课程管理
     */
    @PreAuthorize("@ss.hasPermi('course:course:edit')")
    @Log(title = "课程管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody Course course)
    {
        return toAjax(courseService.updateCourse(course));
    }

    /**
     * 删除课程管理
     */
    @PreAuthorize("@ss.hasPermi('course:course:remove')")
    @Log(title = "课程管理", businessType = BusinessType.DELETE)
	@DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids)
    {
        return toAjax(courseService.deleteCourseByIds(ids));
    }
}

BaseController:

Controller继承了BaseController,其中BaseController是web层通用数据处理,com.ruoyi.common.core.controller 详细定义如下图:

在这里插入图片描述
分页插件实现的原理:
在这里插入图片描述
startPage(),开启分页,调用分页工具类。
在这里插入图片描述
PageUtils类下的startPage():
在这里插入图片描述

TableDataInfo:

分页查询统一返回对象:表格分页数据对象

在这里插入图片描述

AjaxResult:

非分页的查询结果,增删改查统一返回对象:操作消息提醒

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BaseEntity:

所有实体类默认继承的BaseEntity基类

在这里插入图片描述

权限注解:

@PreAuthorize 注解是 Spring Security 框架中用来做权限检查的。

它在运行方法前先验证权限,权限够就放行,不够就拦截。

根据权限表,用户登录后就可以查看自己的权限标识了,在执行该方法时就可以去匹配这个方法所需要的权限这个用户是否拥有。

在这里插入图片描述
演示操作:

原来小智用户是可以查看课程管理查询的
在这里插入图片描述
登录超级管理员账号,去掉课程管理的权限字符串。
在这里插入图片描述
再次登录小智账号,没有权限。
在这里插入图片描述

权限控制流程图:

在这里插入图片描述

在这里插入图片描述

前后端交互流程

查询课程管理列表

接口文档:

在这里插入图片描述
视图组件加载完成后调用getList方法:
在这里插入图片描述在这里插入图片描述
getList方法会调用listCourse方法,传入一个查询的对象。listCourse方法在course.js文件下为了简化axios请求的发送,直接调用request工具类,给工具类传递请求路径、参数和方式。这就是按照api接口文档进行指定的。

// 引入request请求工具类,工具类内部封装了axios基础代码,
// 包括请求的拦截器和响应的拦截器,每隔方法只需要调用这个工具类即可。
import request from '@/utils/request'

// 查询课程管理列表
// 用户输入的参数,封装给query对象。
export function listCourse(query) {
  // 调用工具类将参数传过去,项后台发送请求,完成数据列表查询,并返回给前端展示。
  return request({
    url: '/course/course/list',
    method: 'get',
    params: query
  })
}

找到request.js文件,代码非常多,只介绍部分重要的。

首先通过axios创建实例对象,创建的时候有两个参数,baseURL作用是Ajax在发送请求之前为地址会拼接一个前缀,这个前缀并没有写死,而是读取当下的环境文件。

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: import.meta.env.VITE_APP_BASE_API,
  // 超时
  timeout: 10000
})

项目中的三个环境文件如下开发环境文件、生产环境文件和测试环境文件。启动命令npm run dev中的dev就是开发环境文件,将来的开发环境测试环境等可能会改变,所有没有写死。
在这里插入图片描述
在这里插入图片描述
创建好实例之后还为请求和响应增加了拦截器:

// request拦截器
service.interceptors.request.use(config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  // 是否需要防止数据重复提交
  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  // get请求映射params参数
  if (config.method === 'get' && config.params) {
    let url = config.url + '?' + tansParams(config.params);
    url = url.slice(0, -1);
    config.params = {};
    config.url = url;
  }
    // 防止表单重复提交
  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
    const requestObj = {
      url: config.url,
      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
      time: new Date().getTime()
    }
    const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
    const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
    if (requestSize >= limitSize) {
      console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
      return config;
    }
    const sessionObj = cache.session.getJSON('sessionObj')
    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
      cache.session.setJSON('sessionObj', requestObj)
    } else {
      const s_url = sessionObj.url;                // 请求地址
      const s_data = sessionObj.data;              // 请求数据
      const s_time = sessionObj.time;              // 请求时间
      const interval = 1000;                       // 间隔时间(ms),小于此时间视为重复提交
      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
        const message = '数据正在处理,请勿重复提交';
        console.warn(`[${s_url}]: ` + message)
        return Promise.reject(new Error(message))
      } else {
        cache.session.setJSON('sessionObj', requestObj)
      }
    }
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(res => {
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // 二进制数据则直接返回
    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
      return res.data
    }
    if (code === 401) {
      if (!isRelogin.show) {
        isRelogin.show = true;
        ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
          isRelogin.show = false;
          useUserStore().logOut().then(() => {
            location.href = '/index';
          })
      }).catch(() => {
        isRelogin.show = false;
      });
    }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    } else if (code === 500) {
      ElMessage({ message: msg, type: 'error' })
      return Promise.reject(new Error(msg))
    } else if (code === 601) {
      ElMessage({ message: msg, type: 'warning' })
      return Promise.reject(new Error(msg))
    } else if (code !== 200) {
      ElNotification.error({ title: msg })
      return Promise.reject('error')
    } else {
      return  Promise.resolve(res.data)
    }
  },
  error => {
    console.log('err' + error)
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
    return Promise.reject(error)
  }
)

前端工程通过Axios工具向后端发送请求,发送请求之前会通过拦截器对请求进行增强,将增强后的内容交给后端进行业务结果的处理,后端的响应结果返回给前端之前,响应拦截器也能对响应的数据进行增强。

在这里插入图片描述
在这里插入图片描述

跨域:

在前端开发中,跨域是一个常见的问题,特别是在使用Vue框架进行开发时。跨域是指在浏览器中发送的AJAX请求的目标地址与当前页面的地址不在同一个域下,这会导致浏览器的同源策略产生限制,从而阻止了跨域请求的发送。然而,我们可以通过代理服务器来解决这个问题。

在这里插入图片描述

代理服务器是位于客户端和目标服务器之间的一台服务器,它接收客户端发送的请求,并将请求转发给目标服务器。通过在代理服务器上进行请求转发,可以绕过浏览器的同源策略限制,从而实现跨域请求。

在vue.config.js文件中添加以下内容:

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/927285.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【算法】时间复杂度空间复杂度

0.前言 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏&#xff0c;一般是从时间和空间两个维度来衡量的&#xff0c;即时间复杂度和空间复杂度。时间复杂度主要衡量一个算法的运行快慢&#xff0c;而空间复杂度主要衡…

java全栈day10--后端Web基础(基础知识)

引言&#xff1a;只要能通过浏览器访问的网站全是B/S架构&#xff0c;其中最常用的服务器就是Tomcat 在浏览器与服务器交互的时候采用的协议是HTTP协议 一、Tomcat服务器 1.1介绍 官网地址&#xff1a;Apache Tomcat - Welcome! 1.2基本使用(网上有安装教程&#xff0c;建议…

webrtc ios h264 硬编解码

webrtc ios h264 硬编解码 一 ios 系统支持 从ios8开始&#xff0c;苹果公司开放了硬解码和硬编码API&#xff08;即 VideoToolbox.framework API&#xff09; 二 主要api 1 主要解码函数 VTDecompressionSessionCreate // 创建解码 session VTDecompressionSession…

【JavaEE】JavaEE、web 开发、框架(Spring) 、Maven

文章目录 一、JavaEE 发展历程二、什么是 web 开发1、什么是 web 开发&#xff1f;2、web 网站的工作流程 三、框架1、什么是框架&#xff1f;2、为什么要学框架&#xff1f;3、框架的优点&#xff08;Spring Boot VS Servlet&#xff09; 四、Maven 一、JavaEE 发展历程 Java…

【RISC-V CPU debug 专栏 2 -- Debug Module (DM), non-ISA】

文章目录 调试模块(DM)功能必须支持的功能可选支持的功能兼容性要求规模限制Debug Module Interface (DMI)总线类型地址与操作地址空间控制机制Debug Module Interface Signals请求信号响应信号信号流程Reset Control复位控制方法全局复位 (`ndmreset`)Hart 复位 (`hartreset…

Scala学习记录,全文单词统计

package test32 import java.io.PrintWriter import scala.io.Source //知识点 // 字符串.split("分隔符"&#xff1a;把字符串用指定的分隔符&#xff0c;拆分成多个部分&#xff0c;保存在数组中) object test {def main(args: Array[String]): Unit {//从文件1.t…

使用 Certbot 为 Nginx 自动配置 SSL 证书

1.安装Certbot和Nginx插件 sudo apt-get update sudo apt-get install certbot python3-certbot-nginx 2.获取和安装证书 运行Certbot自动安装SSL证书。注意替换 your_domain sudo certbot --nginx -d your_domain Certbot将自动与Lets Encrypt的服务器通信&#xff0c;验证域…

Java之深入理解HashMap

Java之深入理解HashMap 引言 HashMap是Java程序员使用频率最高的一种映射&#xff08;<Key,Value>键值对&#xff09;数据结构&#xff0c;它继承自AbstractMap&#xff0c;又实现了Map类。 本文将深入源码解析一下HashMap的底层原理。 数据结构 HashMap底层通过维护…

HTTP 探秘之旅:从入门到未来

文章目录 导言&#xff1a;目录&#xff1a;第一篇&#xff1a;HTTP&#xff0c;互联网的“快递员”第二篇&#xff1a;从点开网页到看到内容&#xff0c;HTTP 究竟做了什么&#xff1f;第三篇&#xff1a;HTTP 的烦恼与进化史第四篇&#xff1a;HTTP 的铠甲——HTTPS 的故事第…

Docker 容器网络创建网桥链接

一、网络&#xff1a;默认情况下&#xff0c;所有的容器都以bridge方式链接到docker的一个虚拟网桥上&#xff1b; 注意&#xff1a;“172.17.0.0/16”中的“/16”表示子网掩码的长度为16位&#xff0c;它表示子网掩码中有16个连续的1&#xff0c;后面跟着16个连续的0。用于区分…

springboot366高校物品捐赠管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 高校物品捐赠管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff…

upload-labs 靶场(11~21)

免责声明 本博客文章仅供教育和研究目的使用。本文中提到的所有信息和技术均基于公开来源和合法获取的知识。本文不鼓励或支持任何非法活动&#xff0c;包括但不限于未经授权访问计算机系统、网络或数据。 作者对于读者使用本文中的信息所导致的任何直接或间接后果不承担任何…

jenkins+github+springboot自动部署

背景&#xff1a; 最近看流水线有点意思&#xff0c;就说自己也搞一套。 预期效果&#xff1a; idea提交代码后&#xff0c;GitHub接收&#xff0c;jenkins自动部署。【后续加个自动部署时的代码检查、单元测试、安全测试、sonarqube】 思路分析: idea上的spring代码push到gi…

kafka数据在服务端时怎么写入的

学习背景 接着上篇&#xff0c;我们来聊聊kafka数据在服务端怎么写入的 服务端写入 在介绍服务端的写流程之前&#xff0c;我们先要理解服务端的几个角色之间的关系。 假设我们有一个由3个broker组成的kafka集群&#xff0c;我们在这个集群上创建一个topic叫做shitu-topic&…

Springboot——SseEmitter流式输出

文章目录 前言SseEmitter 简介测试demo注意点异常一 ResponseBodyEmitter is already set complete 前言 最近做AI类的开发&#xff0c;看到各大AI模型的输出方式都是采取的一种EventStream的方式实现。 不是通常的等接口处理完成后&#xff0c;一次性返回。 而是片段式的处理…

【深度学习|目标跟踪】StrongSORT 详解(以及StrongSORT++)

StrongSort详解 1、论文及源码2、DeepSORT回顾3、StrongSORT的EMA4、StrongSORT的NSA Kalman5、StrongSORT的MC6、StrongSORT的BOT特征提取器7、StrongSORT的AFLink8、StrongSORT的GSI模块 1、论文及源码 论文地址&#xff1a;https://arxiv.org/pdf/2202.13514 源码地址&#…

AntFlow 0.20.0版发布,增加多数据源多租户支持,进一步助力企业信息化,SAAS化

传统老牌工作流引擎比如activiti,flowable或者camunda等虽然功能强大&#xff0c;也被企业广泛采用&#xff0c;然后也存着在诸如学习曲线陡峭&#xff0c;上手难度大&#xff0c;流程设计操作需要专业人员&#xff0c;普通人无从下手等问题。。。引入工作流引擎往往需要企业储…

【设计模式系列】工厂方法模式(二十一)

一、什么是工厂方法模式 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;其核心目的是定义一个创建对象的接口&#xff0c;但让实现这个接口的子类来决定实例化哪一个类。工厂方法模式让类的实例化推迟到子类中进行&#xff0c;…

HCIE:详解OSPF,从基础到高级特性再到深入研究

目录 前言 一、OSPF协议基本原理 简介 基本原理 OSPF路由器类型 OSPF网络类型 OSPF报文类型和封装 OSPF邻居的建立的维护 DR和BDR的选举 伪节点 LSDB的更新 OSPF的配置 二、OSPF的高级特性 虚连接&#xff08;Virtual-Link&#xff09; OSPF的LSA和路由选择 OSPF…

分享一款 Vue 图片编辑插件 (推荐)

&#x1f4a5;本篇文章给大家分享一款强大到没朋友的Vue图片编辑插件&#xff0c;可以对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本等&#xff0c;快来试试并收藏吧&#xff01;&#x1f495; 这是一款对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本在线处理的图片处…