【SpringBoot项目】SpringBoot项目-瑞吉外卖【day02】员工管理业务开发

🌕博客x主页:己不由心王道长🌕!
🌎文章说明:SpringBoot项目-瑞吉外卖【day02】员工管理业务开发🌎
✅系列专栏:SpringBoot项目
🌴本篇内容:对黑马的瑞吉外卖项目的day02进行笔记和项目实现🌴
☕️每日一语:人有退路,就有些许安全感。等到哪一天,你真没了退路,你就发现眼前哪条路都能走,也能通。☕️
🚩 交流社区:己不由心王道长(优质编程社区)

前言

今天是项目开发的第二天。当然,我不是第二天就写好了相应的功能,毕竟能力有限。照猫画虎也得自己思考思考再起笔吧!

员工管理业务开发

完善登录功能
问题分析

前面我们已经完成了后台系统的员工登录功能开发,但是还存在一个问题:用户如果不登录,直接访问系统首页面,照样可以正常访问。
这种设计并不合理,我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面。
那么,具体应该怎么实现呢?
答案就是使用过滤器或者拦截器,在过流器成者拦截器中判断用户是否已经完成登录,如果没有登录则跳转到登录页面。
那我们选择过滤器还是拦截器呢?现在不能全都要,所以我这里选择用的是过滤器。在后面优化的时候我们再试试拦截器。

代码实现

实现步骤如下:
一、创建自定义过滤器LoginCheckFilter

见名知意嘛,登录检查过滤器:

package com.example.filter;

/**
 * @author 不止于梦想
 * @date 2022/11/13 17:23
 */
public class LoginCheckFilter {
}

二、在启动类上加入注解@ServletComponentScan:

SpringBootApplication 上使用@ServletComponentScan 注解后
Servlet可以直接通过@WebServlet注解自动注册
Filter可以直接通过@WebFilter注解自动注册
Listener可以直接通过@WebListener 注解自动注册

其实就是组件扫描,而@ServletComponentScan顾名思义就是扫描Servlet技术相关的注解进行注册,并加载成bean。

在这里插入图片描述
在这里要提醒以下,SpringBoot的启动类要在所有其他包的同层或者父层,这样才能扫描到,不然是扫描不到的。

三、完善过滤器处理逻辑
1、获取本次请求的URI
2、判断本次请求是否需要处理
3、如果不需要处理,直接放行
4、需要处理的则判断登录状态,如果已经登录,则直接放行
5、如果未登录则返回登录结果
在这里插入图片描述
上面第五步返回登录结果时不能直接返回,还得看前端代码:

// 响应拦截器
  service.interceptors.response.use(res => {
      console.log('---响应拦截器---',res)
      // 未设置状态码则默认成功状态
      const code = res.data.code;
      // 获取错误信息
      const msg = res.data.msg
      console.log('---code---',code)
      if (res.data.code === 0 && res.data.msg === '未登录') {// 返回登录页面
        // MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
        //     confirmButtonText: '重新登录',
        //     cancelButtonText: '取消',
        //     type: 'warning'
        //   }
        // ).then(() => {
        // })
        console.log('---/backend/page/login/login.html---',code)
        localStorage.removeItem('userInfo')
        window.top.location.href = '/backend/page/login/login.html'
      } else {
        return res.data
      }
    },

上面是一个前端响应拦截器,就是我们发请求,后台处理给的响应信息会被响应拦截器截取。当我们未登录返回登录结果时,应当按照它给的要求格式返回,这样前端的代码才能正确处理并执行正确的操作(好鸡肋,感觉严重耦合在一起)。

这个过程中的难点是判断是否需要处理。那么我们来说说什么情况下需要处理,什么情况下需要放行。

一、前端向后台的controller层发送的请求需要处理。这个毋庸置疑。
二、静态资源应该放行。什么静态资源?所有静态资源,这样岂不是让别人都能看到你的资源了?看到就看到呗,真正有用的数据都要走后端的controller获取,他看到你的页面也没事,你的数据并不会被看到
三、退出、登录请求放行。要退出就已经说明人已经登录了,要登录当然放行,不然就死循环了。

上面分析已经做好了,现在应该把代码整一整

package com.example.filter;

import com.alibaba.fastjson.JSON;
import com.example.commons.R;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.util.AntPathMatcher;

import javax.management.modelmbean.RequiredModelMBean;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;

/**
 * @author 不止于梦想
 * @date 2022/11/13 17:23
 */
@WebFilter(filterName = "loginFilter" ,urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        //先把请求和响应转换为http格式的,因为这是协议
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
//        1、获取本次请求的URI
        String requestURI = httpServletRequest.getRequestURI();
//        2、定义不需要处理的请求路径
        String[] urls = new String[]{//这里定义需要放行的urls
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
//        3、如果不需要处理,直接放行
        if (urlCheck(requestURI,urls)) {
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
//        4、需要处理的则判断登录状态,如果已经登录,则直接放行
        if (httpServletRequest.getSession().getAttribute("employee")!=null) {
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
//        5、如果未登录则返回登录结果
        httpServletResponse.getWriter().write(JSON.toJSONString(R.error("未登录")));
    }
    public boolean urlCheck(String url,String[] urls){
        for (String s : urls) {
            if (PATH_MATCHER.match(s,url)) {
                return true;
            }
        }//如果请求与需要放行的请求不匹配,则返回false。
        return false;
    }
}

在第五步,是我们要重点注意的,因为前端的响应拦截器需要的信息是这样的

在这里插入图片描述
统一格式中的error中的code都统一为了0,msg是我们自己可以设置的,前端需要什么,我们就给他返回什么。
在这里插入图片描述

说明:

private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

这是一个路径匹配器,我们在设置"/backend/**",这样的路径过滤时,如果遇到/backend/index.html时,路径并不能匹配上,/ ** 是拦截所有的文件夹及里面的子文件夹,但是当前文件夹有静态页面时则不会拦截,而路径匹配器则会让器拦截所有属于它的东西。

功能测试

在这里插入图片描述
在每一步的后面根据实际情况进行日志输出,这样更能观察我们的程序到底走了哪些操作。

现在我不登录直接进行index.html页面试试:
在这里插入图片描述
可以清晰的看到,我只是发了一个index.html页面。我们在进行未登录时直接访问index.html页面,它应该给我判断未登录,然后返回登录界面。但是由于我们开发了所有的静态资源,所以不需要处理,但是在index界面上会自动发一个获取page的controller请求,这时候会判断用户是否已经登录,没有登录则回退到登录界面。

这里逻辑全部正确,就是视图并没有进行跳转,算是一个败笔,暂时没有找出解决办法。

新增员工
需求分析

我们在系统中可以管理员工的信息,可以通过新增员工来添加后台系统的用户。当我们点击【添加员工】按钮则视图进行相应跳转:
在这里插入图片描述
在这里插入图片描述
当我们输入数据以后,会在前端先进行一个格式校验,如手机号码和省份证号:
在这里插入图片描述
手机号码必须是11位数字,身份证则是18位。在以上信息都输入正确以后,点击保存按钮即可,如果保存以后还要继续添加,则点击保存并继续添加。

在这里插入图片描述
在保存、取消等按钮的后面都绑定了单击事件,我们看看保存的单击事件是什么样的。
在这里插入图片描述
过程我已经梳理了大概,所以主角是谁?是addEmployee,在我们单击保存后,由于表单已经绑定了这个事件,那么表单会作为一个数据去调用方法,传给这个方法。
在这里插入图片描述
可以看出来,这个函数其实也是发送一个axios请求,不同的是这个方法带有参数,就是把表单填的数据传给后端的controller,路径是/employee,类型是post。

数据模型

分析好了需求以后,我们看看数据模型,为什么呢?先看再说!!!
在这里插入图片描述
这里没有拿出id,因为id是利用雪花算法自动生成的。
上面的由用户填写的是name(用户名)、username(员工姓名)、phone(电话号码)、sex(性别)、id_number(身份证号)。
其他都是在添加用户时,由后端自动生成的、密码是统一的,后面由员工根据自己的需求进行更改。

代码开发

根据分析和模型,我们现在可以编写相应的方法了:

@PostMapping
    public R<String> addEmployee(HttpServletRequest request,@RequestBody Employee employee){
        log.info("新增员工,信息为 {}"+employee.toString());
        //设置初始密码123456,密码经过md5加密处理
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        //设置创建时间
        employee.setCreateTime(LocalDateTime.now());
        //设置更新时间,第一次创建就是第一次更新
        employee.setUpdateTime(LocalDateTime.now());
        //获取当前登录人员信息
        Long empId = (Long) request.getSession().getAttribute("employee");
        //添加创建者信息
       employee.setCreateUser(empId);
       //设置更新人员信息
       employee.setUpdateUser(empId);
       //调用添加方法
        employeeService.save(employee);
        //返回结果
        return R.success("新增员工成功");
    }
功能测试

在这里插入图片描述
可以看到,我们已经测试通过了,但是上面还是存在一些问题的,什么问题?异常的问题

统一处理异常

在这里插入图片描述
报的是后台500错误,我们查看后台
在这里插入图片描述
什么意思,不知道,百度查查:
在这里插入图片描述
说的是username重复了,原来是我们加入数据的时候,用户名重复了,这是不允许的。

所以要处理这种情况,这时候就需要我们的异常处理上场了。我们可以一个一个异常处理,也可以一类异常处理,当然是选择多的啦。

package com.example.commons;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.sql.SQLIntegrityConstraintViolationException;

/**
 * @author 不止于梦想
 * @date 2022/11/14 18:27
 */
@ControllerAdvice(annotations = {Controller.class, RestController.class})
@ResponseBody
@Slf4j
public class GlobalExcption {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
        //后台输出异常信息
        log.info(ex.getMessage());
        //Duplicate entry '123' for key 'employee.idx_username'
        //如果异常信息包括Duplicate entry,则可以确定是用户名字重复
        if(ex.getMessage().contains("Duplicate entry")){
            //以空格为分隔符分割异常信息
            String[] s = ex.getMessage().split(" ");
            //取出数组中的用户名,返回给客户端,提升他名字重复
            return R.error(s[2]+"已存在");
        }
        //否则,返回未知名错误
        return R.error("未知名错误,请重新输入信息");
    }
}

验证:
在这里插入图片描述

要说明的是,这里的功能还十分的不完善,比如手机号肯定不能重复,如果重复应该提示,该手机号已经绑定,身份证肯定不能重复吧,员工姓名可以重复,同名的人多了去了。这是后台的处理逻辑,那前台的呢?哪里出错你应该在哪提示,并且删除表格里的数据吧?只能说我们不是前端的,不能瞎动前端代码,但是这些代码漏洞很多。

员工信息分页查询
需求分析

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

首先我们要看的是,当我们点击登录并且进入index.html之后,页面会发送一个controller的请求查询page,还记得吧?
在这里插入图片描述
并且是通过这个请求走过滤器的controller判断路径判断是否已经登录,没有登录则回退到登录页面,但是现在不研究这个,要研究的是它发送的page请求。

无非就是下面几种情况
在这里插入图片描述
而它们都会走/employee/page这个controller路径。
需要注意的是,我们发送请求时,不一定会按条件查,比如我们不一定输入员工姓名。但是这个请求发送的时候,对应的页码和每页的条数是一定存在的,你不指定也有默认值。

整个过程梳理:
在这里插入图片描述

接下来看看前端的代码:
在这里插入图片描述
当我们进入到index界面,并且是在员工管理界面时,会自动加载一个页面,页面url如图,我们跟进去。
在这里插入图片描述

创建了一个vue,并且绑定了member-app,并且初始化一个init方法。
在这里插入图片描述
我们看看这个init方法
在这里插入图片描述
方法就是这个getMemberList,一样的,跟过去看看它要干嘛。
在这里插入图片描述
放松一个axios请求,方式是get方式,传入params参数,路径是/employee/page。
params参数有page、pageSize、name。

梳理思路:
员工管理界面,当我们进去的时候会有默认提供的参数请求后台并且查询数据,我们也可以手动的选择我们的查询条件,比如按姓名并且同时有每页多少条记录,或者直接点击第几页,输入每页多少条数。总得来说,就是按姓名不一定有,但是第几页和每页多少条记录是必然存在的。所以编写代码的时候要判断是否有姓名。

代码开发

上面已经把前端请求需要的东西和思路都理清了一遍,现在编写代码。
因为这里用的分页,而我们使用的MP为我们提供了分页插件,我们需要编写一个拦截器去拦截分页请求

package com.example.config;

/**
 * @author 不止于梦想
 * @date 2022/11/14 20:15
 */

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置MP的分页插件
 */
@Configuration//声明为配置类
public class MybatisPlusConfig {

    @Bean//交给Spring容器管理
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //创建MybatisPlus拦截器
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //添加分页拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //返回分页拦截器
        return mybatisPlusInterceptor;
    }
}

有了分页插件,现在我们使用分页方法就会被拦截,并进行处理
先创建分页构造器
在这里插入图片描述
代码如下:

@GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);

        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
        //添加过滤条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        //执行查询
        employeeService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

这里在页面展示顺序的时候,是按照更新时间降序输出的

功能测试

看看,不指定姓名能不能查询:
在这里插入图片描述
pageSize是10,但是明显不对,这应该是前端的问题,我们再看看。
在这里插入图片描述
不对的时候记得清理一下缓存就好了,啊哈哈。
在这里插入图片描述
不过缓存清理了,可能你的页面图片又没有了,好迷茫。

启用/禁用员工
需求分析

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户警录系统后启用禁用按钮不显示。
在这里插入图片描述
管理员admin登录系统可以对所有员工账号进行启用、禁用操作如果某个员工账号状态为正常,则按钮显示为“禁用”,如果员工账号状态为已禁用,则按钮显示为“启用(只有禁用了你才需要启用,只有启用的状态你才能禁用)。

看看前端代码吧:
在这里插入图片描述
当点击后面的禁用/启用按钮时,先调用一个statusHandle函数,并传入scope.row作为参数,在传参前前端判断你的账号是否时admin用户,是的话则弹出一个对话框,就判断你当前要调整的用户的状态,当前的用户是启用的,那么参数传的就是禁用,与之对应的是当前用户已经被禁用,那么参数就是启用(只有已经被禁用的用户需要启用),然后执行函数:
在这里插入图片描述
弹出一个选择框:
在这里插入图片描述
你选择了确定,那么久根据你传的参数对该用户进行相应的禁用和启用:
重点是enableOrDisableEmployee方法和它传入的参数:
在这里插入图片描述
这里通过传入被操作对象的id和被操作对象的状态的取反结果。
然后执行以下function:

在这里插入图片描述
发送一个put请求,并且路径是/employee,带传入的参数。

返回值是相应的状态码,我们的controller返回值应该是R
在这里插入图片描述

代码实现

前面忘了说,其实我们能不能看见后面操作有编辑,前端已经帮我们判断了,不是admin用户,你看都看不到。

@PutMapping
    public R<String> updateStatus(HttpServletRequest request,@RequestBody Employee employee) {
        /**
         * 就是一个判断语句,当id值相等时,把数据库的status,
         * 修改为参数的status,参数的status已经跟原本的status取反了。
         * 这里要注意的是,我们每次修改信息的时候,都会把修改人的信息和修改时间也进行更新,所以需要HttpServletRequest
         * 来获取当前操作人的session,从而获取修改人的id。
         */
        log.info(employee.toString());
        //获取操作人id
        Long empId = (Long) request.getSession().getAttribute("employee");
        //设置修改人
        employee.setUpdateUser(empId);
        //设置修改时间
        employee.setUpdateTime(LocalDateTime.now());
        //调用方法,修改user
        boolean b = employeeService.saveOrUpdate(employee);
        //判断修改是否成功
        if(b){
            return R.success("员工信息修改成功");
        }
        return R.error("员工信息修改失败");
测试

在这里插入图片描述
测试没有报错,但是有问题,什么问题?看到Parameters,是不是看起来跟我们的原有数据不一样?
因为长度太长了,前端处理时丢失精度了。怎么解决?
我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串,
这里直接引用官方的
JacksonObjectMapper:

package com.example.commons;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

有了对象转换器之后,在mvc配置文件种配置即可:

@Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器...");
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用Jackson将Java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0,messageConverter);
    }

add(0,messageConverter);表示我们自定义的转换器放在转换器首部位置,优先使用我们自定义的转换器。

再测试:
在这里插入图片描述
在这里插入图片描述
可以看到id已经正常的传过来了,至于其中原理,以后再慢慢深究吧,会用再说。

编辑员工信息
需求分析

在这里插入图片描述
当我们点击编辑时,程序应该通过被修改的用户的id,去后台调用查询方法然后在上面的页面进行回显。当我们点击保存的时候,其实就是一个更新方法,前面我们已经写过一个通用的更新方法。就是update方法。所以这里我们只需要编写一个通过id查询用户的方法即可。我们看看前端代码:

在这里插入图片描述
下面是函数:st是判断当前要走的方法,如果st不等于add,则进行修改操作。在add.html上并且携带被修改对象的id。
在这里插入图片描述
当我们点击编辑的时候,走的应该是修改员工,所以这个方法不用写了。
需要写的时通过id查询员工。
点击编辑跳出以下信息,说明我们需要回显的数据是通过request方法,并且携带参数的。
在这里插入图片描述

代码实现

上面已经分析清楚了,现在把代码完善:
selectById

  @RequestMapping("/{id}")
    public R<Employee> selectById(@PathVariable Long id){
        log.info("查询用户id");
        Employee employee = employeeService.getById(id);
        if(employee!=null){
            return R.success(employee);
        }
        return R.error("没有查询到用户");
    }
功能测试

当我们点击修改大朗时,跳转页面:
在这里插入图片描述

现在我更改其手机号码:13512345678
注意看:这个选项…
在这里插入图片描述
ok兄弟们,看它看它,已经完成了,证明我们的程序没有错误。

总结

这是本次项目的第二天,由于各种关系,其实我没有在第二天就写好,而是推迟了一天,在第三天晚上才把第二天的内容整理好,为什么这么慢呢?一是时间不充裕,尤其是现在临近期末,也得为期末早做打算。还有就是,咱写代码,总得自己思考吧。不能把老师的抄了就是自己的啦。
在做这个项目中,可以发现,基本上都是crud,但是有的地方又需要想象力。比如全局处理异常,要想想出现什么情况下用到它,这些都是需要经验的,而项目就是积累经验的过程,所以兄弟们,咱项目得好好做。

  • 53
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 56
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 56
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

己不由心王道长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值