项目实战-瑞吉外卖day02(B站)持续更新

瑞吉外卖-Day02


课程内容


  • 完善登录功能

  • 新增员工

  • 员工信息分页查询

  • 启用/禁用员工账号

  • 编辑员工信息

分析前端页面效果是如何实现的

为什么点击左边 右边会根着变化

首先 我们先来看一下菜单是如何展示出来的 在来看一下 为啥点击菜单时 右边会跟着变

第一 :菜单是如何展示出来的

我们在创建一个VUE对象的时候 在我们的 data() 的位置 我们准备了 menuList: [] 的一个数据 menuList: [] 是一个数组对象 数组对象里面放着一个个JSON数据 而每一个JSON数据里面又放着id name url icon(图标) 以上只是数据的准备 并不是定义就可以展示到菜单的一个位置

如果要展示到菜单的位置

请到第39行 用到了el-menu组件 下面的v-for表示在遍历 遍历menuList

<el-submenu :index="item.id" v-if="item.children && item.children.length>0">这里面的 v-if是不会成立的 但是会执行第64行<el-menu-item v-else :index="item.id" @click="menuHandle(item,false)">这个分支 然后就可以把<span slot="title">{{item.name}}</span> item.name给展示出来 如果我们把id修改成Id

重启

刷新

为什么是Id 因为由这个对象数组menuList:[]决定

`new Vue({
        el: '#app',
        data() {
          return {
            defAct: '2',
            menuActived: '2',
            userInfo: {},
            menuList: [
              // {
              //   id: '1',
              //   name: '门店管理',
              //   children: [
                  {
                    id: '2',
                    name: '员工管理',
                    url: 'page/member/list.html',
                    icon: 'icon-member'
                  },
                  {
                    id: '3',
                    name: '分类管理',
                    url: 'page/category/list.html',
                    icon: 'icon-category'
                  },
                  {
                    id: '4',
                    name: '菜品管理',
                    url: 'page/food/list.html',
                    icon: 'icon-food'
                  },
                  {
                    id: '5',
                    name: '套餐管理',
                    url: 'page/combo/list.html',
                    icon: 'icon-combo'
                  },
                  {
                    id: '6',
                    name: '订单明细',
                    url: 'page/order/list.html',
                    icon: 'icon-order'
                  }
              //   ],
              // },
            ],

<div class="app" id="app">
      <div class="app-wrapper openSidebar clearfix">
        <!-- sidebar -->
        <div class="sidebar-container">
          <div class="logo">
            <!-- <img src="images/logo.png" width="122.5" alt="" /> -->
            <img src="images/login/login-logo.png" alt="" style="width: 117px; height: 32px" />
          </div>

          <el-scrollbar wrap-class="scrollbar-wrapper">
            <el-menu
              :default-active="defAct"
              :unique-opened="false"
              :collapse-transition="false"
              background-color="#343744"
              text-color="#bfcbd9"
              active-text-color="#f4f4f5"
            >
              <div v-for="item in menuList" :key="item.id">
                <el-submenu :index="item.id" v-if="item.children && item.children.length>0">
                  <template slot="title">
                    <i class="iconfont" :class="item.icon"></i>
                    <span>{{item.name}}</span>
                  </template>
                  <el-menu-item
                    v-for="sub in item.children"
                    :index="sub.id"
                    :key="sub.id"
                    @click="menuHandle(sub,false)"
                    >
                    <i :class="iconfont" :class="sub.icon"></i>
                    <span slot="title">{{sub.name}}</span>
                    </el-menu-item
                  >
                </el-submenu>
                <el-menu-item v-else :index="item.id" @click="menuHandle(item,false)">
                  <i class="iconfont" :class="item.icon"></i>
                  <span slot="title">{{item.name}}</span>
                </el-menu-item>
              </div>
            </el-menu>
          </el-scrollbar>
        </div>

第二 : 点击菜单时 右边会跟着变

http://localhost:8080/backend/index.html

这些菜单实际上都加了一个@click事件 当我们点击菜单的事情 他会执行menuHandle() 方法

  <el-menu-item v-else :index="item.id" @click="menuHandle(item,false)">

然后把item,false传过来 接下来我们来把menuHandle() 方法 在202行

       menuHandle(item, goBackFlag) {
            this.loading = true
            this.menuActived = item.id
            this.iframeUrl = item.url
            this.headTitle = item.name
            this.goBackFlag = goBackFlag
            this.closeLoading()
          },

当我们点击菜单的时候 就会执行menuHandle() 方法 这里面最重要的是 this.iframeUrl = item.url 这里面的url就是以下url 也就是点击菜单的url

 menuList: [
              // {
              //   id: '1',
              //   name: '门店管理',
              //   children: [
                  {
                    id:'2',
                    name : '员工管理',
                    url: 'page/member/list.html',
                    icon: 'icon-member'
                  },
                  {
                    id: '3',
                    name: '分类管理',
                    url: 'page/category/list.html',
                    icon: 'icon-category'
                  },
                  {
                    id: '4',
                    name: '菜品管理',
                    url: 'page/food/list.html',
                    icon: 'icon-food'
                  },
                  {
                    id: '5',
                    name: '套餐管理',
                    url: 'page/combo/list.html',
                    icon: 'icon-combo'
                  },
                  {
                    id: '6',
                    name: '订单明细',
                    url: 'page/order/list.html',
                    icon: 'icon-order'
                  }
              //   ],
              // },
            ],

这里面是通过iframeUrl的方式来显示新的页面

iframeUrl在第164行定义的

iframeUrl: 'page/member/list.html',

在第95行用到<iframe></iframe> 这个是用来展示一个新的页面的 而这个页面从哪里来呢

 :src="iframeUrl"

iframeUrl传进来什么数据 就展示什么数据 为什么我们登录之后 展示的是员工管理 而不是分类管理或是其他

这是因为我们给iframeUrl一个初始值

 iframeUrl: 'page/member/list.html',

而我们在点击菜单的时候 其实是在切换url

      menuHandle(item, goBackFlag) {
            this.loading = true
            this.menuActived = item.id
            this.iframeUrl = item.url
            this.headTitle = item.name
            this.goBackFlag = goBackFlag
            this.closeLoading()
          },

比如说我们现在初始页面给修改成以下

 iframeUrl: 'http://www.itcast.c

重启 刷新

所以当我们点击菜单的时候 其实他是在切换url地址

    menuHandle(item, goBackFlag) {
            this.loading = true
            this.menuActived = item.id
            this.iframeUrl = item.url
            this.headTitle = item.name
            this.goBackFlag = goBackFlag
            this.closeLoading()
          },

而这个url地址在数据准备的时候就已经写好了

 menuList: [
              // {
              //   id: '1',
              //   name: '门店管理',
              //   children: [
                  {
                    id:'2',
                    name : '员工管理',
                    url: 'page/member/list.html',
                    icon: 'icon-member'
                  },
                  {
                    id: '3',
                    name: '分类管理',
                    url: 'page/category/list.html',
                    icon: 'icon-category'
                  },
                  {
                    id: '4',
                    name: '菜品管理',
                    url: 'page/food/list.html',
                    icon: 'icon-food'
                  },
                  {
                    id: '5',
                    name: '套餐管理',
                    url: 'page/combo/list.html',
                    icon: 'icon-combo'
                  },
                  {
                    id: '6',
                    name: '订单明细',
                    url: 'page/order/list.html',
                    icon: 'icon-order'
                  }
              //   ],
              // },
            ],

第64行

<el-menu-item v-else :index="item.id" @click="menuHandle(item,false)">

第202行-第209行

      menuHandle(item, goBackFlag) {
            this.loading = true
            this.menuActived = item.id
            this.iframeUrl = item.url
            this.headTitle = item.name
            this.goBackFlag = goBackFlag
            this.closeLoading()
          },

第164行

 iframeUrl: 'page/member/list.html',

第95行-104行

         <iframe
              id="cIframe"
              class="c_iframe"
              name="cIframe"
              :src="iframeUrl"
              width="100%"
              height="auto"
              frameborder="0"
              v-show="!loading"
            ></iframe>

1. 完善登录功能


1.1 问题分析

前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。

1). 目前现状

用户如果不登录,直接访问系统首页面,照样可以正常访问。

2). 理想效果

上述这种设计并不合理,我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。

那么,具体应该怎么实现呢?

可以使用我们之前讲解过的 过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。

1.2 思路分析

过滤器具体的处理逻辑如下:

A. 获取本次请求的URI

B. 判断本次请求, 是否需要登录, 才可以访问

C. 如果不需要,则直接放行

D. 判断登录状态,如果已登录,则直接放行

E. 如果未登录, 则返回未登录结果

如果未登录,我们需要给前端返回什么样的结果呢? 这个时候, 我们可以去看看前端是如何处理的 ?

1.3 代码实现

1). 定义登录校验过滤器

自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口, 在doFilter方法中完成校验的逻辑。 那么接下来, 我们就根据上述分析的步骤, 来完成具体的功能代码实现:

所属包: com.itheima.reggie.filter

/**
 * 检查用户是否已经完成登录
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //强转
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        log.info("拦截到请求:{}",request.getRequestURI());
        //放行
  

重启 刷新

package com.itheima.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 检查用户是否已经完成登录
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //1、获取本次请求的URI
        String requestURI = request.getRequestURI();// /backend/index.html

        log.info("拦截到请求:{}",requestURI);

        //定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };


        //2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3、如果不需要处理,则直接放行
        if(check){
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }

        //4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response);
            return;
        }

        log.info("用户未登录");
        //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;

    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */
    public boolean check(String[] urls,String requestURI){
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if(match){
                return true;
            }
        }
        return false;
    }}

AntPathMatcher 拓展:
介绍: Spring中提供的路径匹配器 ;
通配符规则:

符号

含义

?

匹配一个字符

*

匹配0个或多个字符

**

匹配0个或多个目录/字符

2). 开启组件扫描

需要在引导类上, 加上Servlet组件扫描的注解, 来扫描过滤器配置的@WebFilter注解, 扫描上之后, 过滤器在运行时就生效了。

@ServletComponentScan 的作用:
在SpringBoot项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的@WebServlet , @WebFilter , @WebListener 注解, 自动注册Servlet的相关组件 ;
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
    public static void main(String[] args) {

        SpringApplication.run(ReggieApplication.class, args);
        log.info("项目启动成功...");

    }
}

1.4 功能测试

代码编写完毕之后,我们需要将工程重启一下,然后在浏览器地址栏直接输入系统管理后台首页(http://localhost:8080/backend/index.html),然后看看是否可以跳转到登录页面即可。我们也可以通过debug的形式来跟踪一下代码执行的过程。

对于前端的代码, 也可以进行debug调试。

F12打开浏览器的调试工具, 找到我们前面提到的request.js, 在request.js的响应拦截器位置打上断点。

2. 新增员工


2.1 需求分析

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面,如下:

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

2.2 数据模型

新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。employee表中的status字段已经设置了默认值1,表示状态正常。

需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的。

2.3 程序执行流程

在开发代码之前,我们需要结合着前端页面发起的请求, 梳理一下整个程序的执行过程:

A. 点击"保存"按钮, 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端, 请求方式POST, 请求路径 /employee

B. 服务端Controller接收页面提交的数据并调用Service将数据进行保存

C. Service调用Mapper操作数据库,保存数据

2.4 代码实现

在EmployeeController中增加save方法, 用于保存用户员工信息。

A. 在新增员工时, 按钮页面原型中的需求描述, 需要给员工设置初始默认密码 123456, 并对密码进行MD5加密。

B. 在组装员工信息时, 还需要封装创建时间、修改时间,创建人、修改人信息(从session中获取当前登录用户)。

2.5 功能测试

代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮, 输入员工基本信息, 然后点击 "保存" 进行数据保存, 保存完毕后, 检查数据库中是否录入员工数据。

当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现错误提示信息:

而此时,服务端已经报错了, 报错信息如下:

出现上述的错误, 主要就是因为在 employee 表结构中,我们针对于username字段,建立了唯一索引,添加重复的username数据时,违背该约束,就会报错。但是此时前端提示的信息并不具体,用户并不知道是因为什么原因造成的该异常,我们需要给用户提示详细的错误信息 。

2.6 全局异常处理

2.6.1 思路分析

要想解决上述测试中存在的问题,我们需要对程序中可能出现的异常进行捕获,通常有两种处理方式:

A. 在Controller方法中加入 try...catch 进行异常捕获

形式如下:

如果采用这种方式,虽然可以解决,但是存在弊端,需要我们在保存其他业务数据时,也需要在Controller方法中加上try...catch进行处理,代码冗余,不通用。

B. 使用异常处理器进行全局异常捕获

采用这种方式来实现,我们只需要在项目中定义一个通用的全局异常处理器,就可以解决本项目的所有异常。

2.6.2 全局异常处理器

在项目中自定义一个全局异常处理器,在异常处理器上加上注解 @ControllerAdvice,可以通过属性annotations指定拦截哪一类的Controller方法。 并在异常处理器的方法上加上注解 @ExceptionHandler 来指定拦截的是那一类型的异常。

异常处理方法逻辑:
指定捕获的异常类型为 SQLIntegrityConstraintViolationException
解析异常的提示信息, 获取出是那个值违背了唯一约束
组装错误信息并返回

所属包: com.itheima.reggie.common

注解说明:
上述的全局异常处理器上使用了的两个注解 @ControllerAdvice , @ResponseBody , 他们的作用分别为:
@ControllerAdvice : 指定拦截那些类型的控制器;
@ResponseBody: 将方法的返回值 R 对象转换为json格式的数据, 响应给页面;

上述使用的两个注解, 也可以合并成为一个注解 @RestControllerAdvice

2.6.3 测试

全局异常处理器编写完毕之后,我们需要将项目重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮。当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现如下错误提示信息:

3. 员工分页查询


3.1 需求分析

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中, 除了分页条件以外,还有一个查询条件 "员工姓名"。

  • 请求参数

  • 搜索条件: 员工姓名(模糊查询)

  • 分页条件: 每页展示条数 , 页码

  • 响应数据

  • 总记录数

  • 结果列表

3.2 程序执行流程

3.2.1 页面流程分析

在开发代码之前,需要梳理一下整个程序的执行过程。

A. 点击菜单,打开员工管理页面时,执行查询:

B. 搜索栏输入员工姓名,回车,执行查询:

1). 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端

2). 服务端Controller接收页面提交的数据, 并组装条件调用Service查询数据

3). Service调用Mapper操作数据库,查询分页数据

4). Controller将查询到的分页数据, 响应给前端页面

5). 页面接收到分页数据, 并通过ElementUI的Table组件展示到页面上

3.2.2 前端代码介绍

1). 访问员工列表页面/member/list.html时, 会触发Vuejs中的钩子方法, 在页面初始化时调用created方法

从上述的前端代码中我们可以看到, 执行完分页查询, 我们需要给前端返回的信息中需要包含两项 : records 中封装结果列表, total中封装总记录数 。

而在组装请求参数时 , page、pageSize 都是前端分页插件渲染时的参数;

2). 在getMemberList方法中, 通过axios发起异步请求

axios发起的异步请求会被声明在 request.js 中的request拦截器拦截, 在其中对get请求进行进一步的封装处理

最终发送给服务端的请求为 : GET请求 , 请求链接 /employee/page?page=1&pageSize=10&name=xxx

3.3 代码实现

3.3.1 分页插件配置

当前我们要实现的分页查询功能,而在MybatisPlus要实现分页功能,就需要用到MybatisPlus中提供的分页插件,要使用分页插件,就要在配置类中声明分页插件的bean对象。

3.3.2 分页查询实现

在上面我们已经分析了,页面在进行分页查询时, 具体的请求信息如下:

请求

说明

请求方式

GET

请求路径

/employee/page

请求参数

page , pageSize , name

那么查询完毕后我们需要给前端返回什么样的结果呢?

在上述我们也分析了, 查询返回的结果数据data中应该封装两项信息, 分别为: records 封装分页列表数据, total 中封装符合条件的总记录数。 那么这个时候, 在定义controller方法的返回值类型R时, 我们可以直接将 MybatisPlus 分页查询的结果 Page 直接封装返回, 因为Page中的属性如下:

那么接下来就依据于这些已知的需求和条件完成分页查询的代码实现。 具体的逻辑如下:

A. 构造分页条件

B. 构建搜索条件 - name进行模糊匹配

C. 构建排序条件 - 更新时间倒序排序

D. 执行查询

E. 组装结果并返回

具体的代码实现如下:

3.4 功能测试

代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 默认就会打开员工管理的列表页面, 我们可以查看列表数据是否可以正常展示, 也可以通过分页插件来测试分页功能, 及员工姓名的模糊查询功能。

在进行测试时,可以使用浏览器的监控工具查看页面和服务端的数据交互细节。 并借助于debug的形式, 根据服务端参数接收及逻辑执行情况。

测试过程中可以发现,对于员工状态字段(status)服务端返回的是状态码(1或者0),但是页面上显示的则是“正常”或者“已禁用”,这是因为页面中在展示数据时进行了处理。

4. 启用/禁用员工账号


4.1 需求分析

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 "禁用",如果员工账号状态为已禁用,则按钮显示为"启用"。

==需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。==

A. admin 管理员登录

B. 普通用户登录

4.2 程序执行流程

4.2.1 页面按钮动态展示

在上述的需求中,我们提到需要实现的效果是 : 只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示 , 页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?

1). 在列表页面(list.html)加载时, 触发钩子函数created, 在钩子函数中, 会从localStorage中获取到用户登录信息, 然后获取到用户名

2). 在页面中, 通过Vue指令v-if进行判断,如果登录用户为admin将展示 启用/禁用 按钮, 否则不展示

4.2.2 执行流程分析

1). 当管理员admin点击 "启用" 或 "禁用" 按钮时, 调用方法statusHandle

scope.row : 获取到的是这一行的数据信息 ;

2). statusHandle方法中进行二次确认, 然后发起ajax请求, 传递id、status参数

最终发起异步请求, 请求服务端, 请求信息如下:

请求

说明

请求方式

PUT

请求路径

/employee

请求参数

{"id":xxx,"status":xxx}

{...params} : 三点是ES6中出现的扩展运算符。作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中。

4.3 代码实现

在开发代码之前,需要梳理一下整个程序的执行过程:

1). 页面发送ajax请求,将参数(id、status)提交到服务端

2). 服务端Controller接收页面提交的数据并调用Service更新数据

3). Service调用Mapper操作数据库

启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作。在Controller中创建update方法,此方法是一个通用的修改员工信息的方法。

4.4 功能测试

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 进行 "启用" 或 "禁用" 的测试。

测试过程中没有报错,但是功能并没有实现,查看数据库中的数据也没有变化。但是从控制台输出的日志, 可以看出确实没有更新成功。

而在我们的数据库表结构中, 并不存在该ID, 数据库中 风清扬 对应的ID为 1420038345634918401

4.5 代码修复

4.5.1 原因分析

通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事呢?

在分页查询时,服务端会将返回的R对象进行json序列化,转换为json格式的数据,而员工的ID是一个Long类型的数据,而且是一个长度为 19 位的长整型数据, 该数据返回给前端是没有问题的。

那么具体的问题出现在哪儿呢?

问题实际上, 就出现在前端JS中, js在对长度较长的长整型数据进行处理时, 会损失精度, 从而导致提交的id和数据库中的id不一致。 这里,我们也可以做一个简单的测试,代码如下:

4.5.2 解决方案

要想解决这个问题,也很简单,我们只需要让js处理的ID数据类型为字符串类型即可, 这样就不会损失精度了。同样, 大家也可以做一个测试:

那么在我们的业务中, 我们只需要让分页查询返回的json格式数据库中, long类型的属性, 不直接转换为数字类型, 转换为字符串类型就可以解决这个问题了 , 最终返回的结果为 :

4.5.3 代码修复

由于在SpringMVC中, 将Controller方法返回值转换为json对象, 是通过jackson来实现的, 涉及到SpringMVC中的一个消息转换器MappingJackson2HttpMessageConverter, 所以我们要解决这个问题, 就需要对该消息转换器的功能进行拓展。

具体实现步骤:

1). 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)

2). 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换

1). 引入JacksonObjectMapper

2). 在WebMvcConfig中重写方法extendMessageConverters

5. 编辑员工信息


5.1 需求分析

在员工管理列表页面点击 "编辑" 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 "保存" 按钮完成编辑操作。

那么从上述的分析中,我们可以看出当前实现的编辑功能,我们需要实现两个方法:

A. 根据ID查询, 用于页面数据回显

B. 保存修改

5.2 程序执行流程

在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:

1). 点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]

2). 在add.html页面获取url中的参数[员工id]

3). 发送ajax请求,请求服务端,同时提交员工id参数

4). 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面

5). 页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显

6). 点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端

7). 服务端接收员工信息,并进行处理,完成后给页面响应

8). 页面接收到服务端响应信息后进行相应处理

注意:add.html页面为公共页面,新增员工和编辑员工都是在此页面操作

5.3 代码实现

5.3.1 根据ID查询

经过上述的分析,我们看到,在根据ID查询员工信息时,请求信息如下:

请求

说明

请求方式

GET

请求路径

/employee/{id}

代码实现:

在EmployeeController中增加方法, 根据ID查询员工信息。

5.3.2 修改员工

经过上述的分析,我们看到,在修改员工信息时,请求信息如下:

请求

说明

请求方式

PUT

请求路径

/employee

请求参数

{.......} json格式数据

代码实现:

在EmployeeController中增加方法, 根据ID更新员工信息。

5.4 功能测试

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 按照前面分析的操作流程进行测试,查看数据是否正常修改即可。

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

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

相关文章

【剑指offer-C++】JZ32:从上往下打印二叉树

题目描述 描述&#xff1a;不分行从上往下打印出二叉树的每个节点&#xff0c;同层节点从左至右打印。例如输入{8,6,10,#,#,2,1}&#xff0c;如以下图中的示例二叉树&#xff0c;则依次打印8,6,10,2,1(空节点不打印&#xff0c;跳过)&#xff0c;请你将打印的结果存放到一个数…

我用Python写了一个下载网站所有内容的软件,可见即可下,室友表示非常好用

Python 写一个下载网站内容的GUI工具&#xff0c;所有内容都能下载&#xff0c;真的太方便了&#xff01;前言本次要实现的功能效果展示代码实战获取数据GUI部分最后前言 哈喽大家好&#xff0c;我是轻松。 今天我们分享一个用Python写下载视频弹幕评论的代码。 之前自游写了…

计算机网络复习

什么是DHCP和DNS DNS(Domain Name System&#xff0c;域名系统)&#xff0c;因特网上作为域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使用户更方便的访问互联网&#xff0c;而不用去记住能够被机器直接读取的IP数串。通过主机名&#xff0c;最终得到该主机名对应的…

ARM uboot 的移植4 -从 uboot 官方标准uboot开始移植

一、添加DDR初始化1 1、分析下一步的移植路线 (1) cpu_init_crit 函数成功初始化串口、时钟后&#xff0c;转入 _main 函数&#xff0c;函数在 arch/arm/lib/crt0.S 文件中。 (2) 在 crt0.S 中首先设置栈&#xff0c;将 sp 指向 DDR 中的栈地址&#xff1b; #if defined(CONF…

被骗进一个很隐蔽的外包公司,入职一个月才发现,已经有了社保记录,简历污了,以后面试有影响吗?...

职场的套路防不胜防&#xff0c;一不留神就会掉坑&#xff0c;一位网友就被“骗”进了外包公司&#xff0c;他说公司非常隐蔽&#xff0c;入职一个月才发现是外包&#xff0c;但已经有了社保记录&#xff0c;简历污了&#xff0c;不知道对以后面试有影响吗&#xff1f;楼主说&a…

【含源码】用python做游戏有多简单好玩

有很多同学问我还有其他什么小游戏吗&#xff0c;游戏是怎么做的&#xff0c;难不难。我就用两篇文章来介绍一下&#xff0c;如何使用Python做游戏。 兔子与灌 俄罗斯方块 休闲五子棋 走迷宫 推箱子 消消乐 超多小游戏玩转不停↓ 更多小游戏可以评论区讨论哦&#xff0c;喜欢…

保姆级使用PyTorch训练与评估自己的Replknet网络教程

文章目录前言0. 环境搭建&快速开始1. 数据集制作1.1 标签文件制作1.2 数据集划分1.3 数据集信息文件制作2. 修改参数文件3. 训练4. 评估5. 其他教程前言 项目地址&#xff1a;https://github.com/Fafa-DL/Awesome-Backbones 操作教程&#xff1a;https://www.bilibili.co…

【C++】模板进阶

模板进阶 文章目录模板进阶1、非类型模板参数2、模板的特化2.1.概念2.2.函数模板特化2.3.类模板特化2.3.1.全特化2.3.2.偏特化2.4.类模板特化示例3、模板总结1、非类型模板参数 模板参数分类类型形参与非类型形参。类型模板参数&#xff1a;出现在模板参数列表中&#xff0c;跟…

技术人的管理学-团队管理

主要内容前言团队管理&#xff0c;如何让猪上树&#xff1f;选人育人用人留人总结前言 本周报名参加了 风 变 科 技推出的管理学MTP培训课&#xff0c;花了点小钱&#xff0c;感觉还是有用的&#xff0c;可惜时间安排都在8点以后&#xff0c;刚好是看孩子的时间&#xff0c;只…

io流概述

public static void main(String[] args) throws IOException {//1.与文件建立联系 File->数据源File src new File("D://AAA/test.txt");//2.创建文件字节输入流->管道//InputStream is new FileInputStream(src);InputStream is new FileInputStream(&quo…

中科亿海微FPGA

国产FPGA中&#xff0c;紫光、安路、高云称得上是三小龙&#xff0c;其他的半斤八两&#xff0c;中科亿海微也算是其中之一。 其产品为亿海神针系列&#xff0c;如下&#xff1a; 可见其最小规模也有9.2KLUT&#xff0c;最大竟有136K之多了&#xff0c;对比其他国产&#xff0…

18、江科大stm32视频学习笔记——USART串口发送串口发送和接收

目录 一、USART串口发送 1、电路图 2、printf函数的移植方法 3、serial.c 4、main.c 5、解决直接写汉字&#xff0c;编译器报错 二 、USART串口发送和接收 1、查询实现 2、中断实现 &#xff08;1&#xff09;在Serial.c中添加的代码 &#xff08;2&#xff09;主函…

【Spring从入门到实战】第 4 讲:SpringAOP实现以及原理

本文已收录于专栏&#x1f332;《Spring从入门到实战》&#x1f332;专栏前言 大家好&#xff0c;我是执梗。本专栏将从Spring入门开始讲起&#xff0c;详细讲解各类配置的使用以及原因&#xff0c;到使用SpringBoot进行开发实战&#xff0c;旨在记录学习生活的同时也希望能帮到…

IDEA常用插件列表

一 背景 IDEA常用插件列表&#xff0c;用来提供工作效率。你都安装了吗 IntelliJ IDEA 默认安装并提供了非常多的工具&#xff0c;比如 Maven Integration、Markdown support、SSH Remote Run 等。其中有很多好用&#xff0c;但是不为人知的工具。 二 插件列表 阿里代码规约…

Linux基础命令大全(上)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…

pytorch-在竞赛中去摸索用法,用房价预测比赛了解数据处理流程

实战Kaggle比赛&#xff1a;房价预测 让我们动手实战一个Kaggle比赛&#xff1a;房价预测House Prices - Advanced Regression Techniques | Kaggle。本文将提供未经调优的数据的预处理、模型的设计和超参数的选择。通过动手操作、仔细观察实验现象、认真分析实验结果并不断调…

【算法入门】字符串基础

目录 一.字符串引言1.字符串基础二.洛谷P5734详解1.字符串相关库函数&#x1f4ab;&#xff08;1&#xff09; strcpy函数 &#x1f4ab;&#x1f4ab;&#xff08;2&#xff09; strcat函数 &#x1f4ab;&#x1f4ab;&#xff08;3&#xff09;strstr函数 &#x1f4ab;2.题…

JavaScript的事件传播机制

你在学习和编写JavaScript时可能听说过事件冒泡&#xff08;event bubbling&#xff09;。它会发生在多个元素存在嵌套关系&#xff0c;并且这些元素都注册了同一事件(例如click)的监听器时。 但是事件冒泡只是事件机制的一部分。它经常与事件捕获(event capturing)和事件传播…

ChatGPT的N种用法(持续更新中。。。)

目录前言一、语法更正二、文本翻译三、语言转换3-1、Python-->JAVA四、代码解释-1五、代码解释-2六、修复代码错误六、作为百科全书七、信息提取七、好友聊天八、创意生成器8-1、VR和密室结合8-2、再结合AR九、采访问题9-1、采访问题清单9-2、采访问题清单并给出相应答案十、…

2分钟彻底搞懂“高内聚,低耦合”

&#x1f497;推荐阅读文章&#x1f497; &#x1f338;JavaSE系列&#x1f338;&#x1f449;1️⃣《JavaSE系列教程》&#x1f33a;MySQL系列&#x1f33a;&#x1f449;2️⃣《MySQL系列教程》&#x1f340;JavaWeb系列&#x1f340;&#x1f449;3️⃣《JavaWeb系列教程》…