创建数据库
导入sql文件
2.Maven项目搭建导入依赖
pom.xml
4.0.0 org.springframework.boot spring-boot-starter-parent2.4.5 org.example reggie1.0-SNAPSHOT 8 8 org.springframework.boot spring-boot-starterorg.springframework.boot spring-boot-starter-testtest org.springframework.boot spring-boot-starter-webcompile com.baomidou mybatis-plus-boot-starter3.4.2 org.projectlombok lombok1.18.20 com.alibaba fastjson1.2.76 commons-lang commons-lang2.6 mysql mysql-connector-javaruntime com.alibaba druid-spring-boot-starter1.1.23 org.springframework.boot spring-boot-maven-plugin2.4.5
配置application.yml
server:
port: 8080
spring:
application:
name: reggie
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 128730
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 主键生成策略
id-type: ASSIGN_ID
ReggieApplication 项目启动测试
package com.reggie.takeout;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j //打印日志
@SpringBootApplication
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
log.info("项目启动成功...");
}
}
3.静态资源导入
设置静态资源访问--映射路径 com.reggie.takeout.config
WebMvcConfig
package com.reggie.takeout.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("静态资源开始映射...");
registry.addResourceHandler("/backend
@Data
public class Employee{
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
private String idNumber;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
}
mapper
package com.reggie.takeout.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.reggie.takeout.pojo.Employee; import org.apache.ibatis.annotations.Mapper; @Mapper public interface EmployeeMapper extends BaseMapper{ }
service和serviceImpl
package com.reggie.takeout.service; import com.baomidou.mybatisplus.extension.service.IService; import com.reggie.takeout.pojo.Employee; public interface EmployeeService extends IService{ }
package com.reggie.takeout.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.reggie.takeout.mapper.EmployeeMapper; import com.reggie.takeout.pojo.Employee; import com.reggie.takeout.service.EmployeeService; import org.springframework.stereotype.Service; @Service public class EmployeeServiceImpl extends ServiceImplimplements EmployeeService { }
controller
package com.reggie.takeout.conrtoller;
import com.reggie.takeout.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService=null;
@PostMapping("/login")
public R login(HttpServletRequest request, @RequestBody Employee employee){
//将页面提交的密码进行MD5加密
String password = employee.getPassword();
password= DigestUtils.md5DigestAsHex(password.getBytes());
//根据用户名查数据库(查不到返回结果)
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
//查不到用户名 返回失败
if (emp==null){
return R.error("登录失败");
}
//比对密码(密码错误返回结果)
if (!emp.getPassword().equals(password)){
return R.error("登录失败");
}
//查询员工状态,员工状态禁用下不可登录
if (emp.getStatus()==0){
return R.error("账户已锁定");
}
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
}
配置返回通用结果类
此类是个通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端
package com.reggie.takeout.common; import lombok.Data; import java.util.HashMap; import java.util.Map; @Data public class R退出功能{ private Integer code; private String msg; private T data; private Map map = new HashMap(); public static R success(T object) { R r = new R (); r.data = object; r.code = 1; return r; } public static R error(String msg) { R r = new R(); r.msg = msg; r.code = 0; return r; } public R add(String key, Object value) { this.map.put(key, value); return this; } }
点击退出
退出 移除session
//退出
@PostMapping("/logout")
public R loginOut(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("用户退出成功");
}
分析
点击登录
退出之后 userInfo消失
登录过滤器步骤:
1.创建自定义过滤器LoginCheckFilter 加入注解@WebFilter
2.在SpringBoot启动类加入注解@ServletComponentScan
3.完善过滤器的处理逻辑
获取本次处理请求的URL
判断本次请求是否需要处理
若不需要,直接放行
判断登陆状态,若已经登陆直接放行
若未登陆则返回未登陆结果
LoginCheckFilter
package com.reggie.takeout.filter;
import com.alibaba.fastjson.JSON;
import com.reggie.takeout.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;
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "
public boolean check(String urls[],String requestURL){
for (String url:urls){
boolean match = PATH_MATCHER.match(url, requestURL);
if (match){
return true;
}
}
//如果遍历完 没有返回true 证明没有匹配上 返回false
return false;
}
}
前端 request.js通过匹配“NOTLOGIN”进行响应 进行页面跳转
5.新增员工需求:添加员工信息到员工表 用户名username唯一 步骤: 1.页面发送ajax请求,将新增员工页面输入的数据以json的形式提交到服务器 2.服务端Controller接受数据并调用service,Service调用Mapper操作数据库,保存数据
@PostMapping
public R addEmployee(HttpServletRequest request,@RequestBody Employee employee){
//设置默认密码 使用md5加密
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//设置创建时间 更新时间
employee.setCreateTime(String.valueOf(LocalDateTime.now()));
employee.setUpdateTime(String.valueOf(LocalDateTime.now()));
//设置创建者 修改者
Long userId = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(userId);
employee.setUpdateUser(userId);
//调用service
employeeService.save(employee);
log.info("新增员工信息{}",employee.toString());
return R.success("添加成功");
}
全局异常捕获
由于用户名设置了主键唯一,若员工用户名相同就会报错,解决报错有两种方式。
1.try catch捕获
2.全局的异常捕获
第一种方式后续捕获类似异常需要大量的重复代码,所以使用第二种
GlobalExceptionHandler
package com.reggie.takeout.common;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLIntegrityConstraintViolationException;
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R exceptionHandler(SQLIntegrityConstraintViolationException exception){
log.info(exception.getMessage());
//错误信息 Duplicate entry 'guwen' for key 'idx_username'
if (exception.getMessage().contains("Duplicate entry ")){
String[] split = exception.getMessage().split(" ");//以空格分隔开
//Duplicate entry 'guwen' for key 'idx_username'对应
//0 1 2 3 4 5
String msg="用户"+split[2]+"已存在";
return R.error(msg);
}
return R.error("未知错误");
}
}
测试
6.员工信息展示 分析
步骤: 1.页面发送Ajax请求,将分页查询参数(page,pagesize,name)提交到服务端 2.服务端Controller接收页面提交的数据并调用查询的数据 3.Service调用Mapper操作数据库,查询分页数据 4.Controller将查询到的分页数据响应到页面 5.页面接收到分页的数据并通过ElementUI的Table组件展示到页面上配置Mybatis-plus分页插件
package com.reggie.takeout.config;
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;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
controller层
@GetMapping("/page")
public R page(int page, int pageSize, String name){
log.info("page={},pageSize={},name={}",page,pageSize,name);
//1.构造分页构造器
Page pageInfo = new Page(page, pageSize);
//2.构造条件
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasText(name),Employee::getName,name);
//3.添加排序 按照账户创建时间排序
queryWrapper.orderByAsc(Employee::getCreateTime);
//4.执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
启用禁用员工账户
管理员可以对普通用户进行账户启用和禁用,普通用户无相关权限,所以对普通用户不显示启用禁用按钮
页面初始化时获取账户用户名
created() {
this.init()
this.user = JSON.parse(localStorage.getItem('userInfo')).username
},
判断登录账户是否是管理员
{{ scope.row.status == '1' ? '禁用' : '启用' }}
向后端传递json数据,将要启用禁用的员工id与状态传递给后端
//状态修改
statusHandle (row) {
this.id = row.id
this.status = row.status
this.$confirm('确认调整该账号的状态?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {
console.log('enableOrDisableEmployee',res)
if (String(res.code) === '1') {
this.$message.success('账号状态更改成功!')
this.handleQuery()
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
// 修改---启用禁用接口
function enableOrDisableEmployee (params) {
return $axios({
url: '/employee',
method: 'put',
data: { ...params }
})
}
分析:
1. 页面发送ajax请求,将参数(id、status)提交到服务端 2. 服务端Controller接收页面提交的数据并调用Service更新数据 3. Service调用Mapper操作数据库
Controller
@PutMapping
public R update(HttpServletRequest request,@RequestBody Employee employee){//if (String(res.code) === '1') 返回json
//获取员工id
Long empId = (Long) request.getSession().getAttribute("employee");
//更新 修改者和修改时间
employee.setUpdateUser(empId);
employee.setUpdateTime(String.valueOf(LocalDateTime.now()));
//执行修改
employeeService.updateById(employee);
return R.success("信息修改成功");
}
问题:
数据库中的id与前端传递的id不相同,是因为涉及一个JS的精度问题,JS识别Long类型只精确到16位,而ID有19位,导致ID精度丢失。
代码修复
步骤: 1.使用JacksonObjectMapper对JSON数据进行转换 2.在WebConfig配置类中扩展SringMVC的消息转换器,镜像Java对象到JSON数据的转换
JacksonObjectMapper
package com.reggie.takeout.common;
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 org.springframework.stereotype.Component;
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;
@Component
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);
}
}
将id更改为字符串,就不会损失精度,并且修改时间格式
WebMvcConfig
@Override
protected void extendMessageConverters(List> converters) {
log.info("扩展消息转换器...");
//创建消息转换器
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器 底层使用Jackson将java对象转为json
mappingJackson2HttpMessageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到MVC框架的转换器集合中
converters.add(0,mappingJackson2HttpMessageConverter);//添加索引,使优先级最高,不然还是会默认使用自带的转换器
}
7.编辑员工信息
分析:
步骤: 1.点击编辑按钮时,页面跳转到add.html,并在url中携带参数员工ID 2.在add.html页面获取url中的参数员工ID 3.发送ajax请求,请求服务端,同时提交员工id参数 4.服务端接收请求,根据员工id查询员工信息,将员工信息以ison形式响应给页面 5.页面接收服务端响应的ison数据,通过VUE的数据绑定进行员工信息回显 6.点击保存按钮,发送aiax请求,将页面中的员工信息以ison方式提交给服务端 7.服务端接收员工信息,并进行处理,完成后给页面响应 8.页面接收到服务端响应信息后进行相应处理
created() {
this.id = requestUrlParam('id')
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {
this.init()
}
},
//获取url地址上面的参数
function requestUrlParam(argname){
var url = location.href
var arrStr = url.substring(url.indexOf("?")+1).split("&")
for(var i =0;i
// 修改页面反查详情接口
function queryEmployeeById (id) {
return $axios({
url: `/employee/${id}`,
method: 'get'
})
}
员工信息回显
Controller
@GetMapping("/{id}")
public R getById(@PathVariable Long id){
log.info("根据id查询员工信息...");
Employee employee = employeeService.getById(id);
if (employee!=null){
return R.success(employee);
}
return R.error("没有查询到相关信息");
}
由于修改和添加都是共用了add.html页面,所以修改操作只需要根据id查找到员工信息然后回显到前端即可,保存功能复用了上面的update方法
8.公共字段自动填充新增员工时需要设置时间,创建人,修改时间,修改人等字段,在编辑员工的时候也要设置这些字段,这些属于公共的字段可以使用MybatisPlus提供的公共字段填充功能,避免代码重复
步骤:
1.实体类的属性上加入@TableField注解,指定自动填充的策略
2.按照框架编写元数据对象处理器,同一字段赋值,实现MetaObjectHandler接口
3.使用ThreadLocal解决无法获取session中的员工ID的问题
3.1编写BaseContext工具类,基于ThreadLocal封装的工具类
3.2在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
3.3在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
ThreadLocal
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
我们可以在LogainCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值用户ID,然后使用MyMetaObjectHandler的updateFill方法来获取用户ID
实体类
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
MyMetaObjectHandler
package com.reggie.takeout.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段insert自动填充");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段update自动填充");
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
BaseContext
package com.reggie.takeout.common;
public class BaseContext{
private static ThreadLocal threadLocal=new ThreadLocal();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
CheckLoginFilter
//判断登陆状态,若已经登陆直接放行 获取session
if (request.getSession().getAttribute("employee")!=null){
log.info("用户已登录,id为..."+request.getSession().getAttribute("employee"));
Long empId=(Long)request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}



