开发前准备
准备数据表
结合页面原型创建数据库reggie
,可以使用图形化界面或者MySQL命令运行SQL文件导入表结构(使用命令时sql文件不要放在中文目录中)
创建工程
创建一个SpringBoot的工程(勾选Spring Web,MySQL和MyBatis)
,配置pom.xml文件导入druid,lombok和MyBatisPlus依赖
的坐标
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
配置applicaton.yml文件
server:port: 8080
spring:
application:# web应用的名称(默认就是工程名)name: reggie_take_outdatasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: 123456redis:host: localhostport: 6379database: 0cache:redis:time-to-live: 1800000 #ms ->30min
mybatis-plus:configuration:# 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法开启映射(默认就是true)map-underscore-to-camel-case: true# 开启日志功能log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: ASSIGN_ID
reggie:path: D:\SpringBoot_Reggie\reggie_take_out\src\main\resources\static\front\hello\
创建主程序类并使用@SpringBootApplication注解
, 告诉SpringBoot这是一个SpringBoot应用并且是所有程序的启动入口
@Slf4j
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);log.info("项目启动成功...");}
}
在请求控制器中创建处理请求的方法
@Slf4j
@RestController
public class HelloController {@RequestMapping("/hello")public String handle01(@RequestParam("name") String name){log.info("请求进来了....");return "Hello, Spring Boot 2!"+"你好:"+name;}
}
静态资源映射
Spring Boot工程中引入的静态资源需要放到static或者templates
目录下才能访问到
- 访问resources目录下的静态资源需要编写配置类
config/WebMvcConfig
配置一下资源映射
放行这些资源
打开浏览器访问登录页面(暂无法登录)http://localhost:8080/backend/page/login/login.html
@Configuration
@Slf4j
public class WebMvcConfig extends WebMvcConfigurationSupport {@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("开始进行静态资源映射...");// 根据用户的请求路径映射到对应的请求资源目录registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}
}
统一结果封装
编写一个通用结果类common/Result
封装所有Controller的返回结果, 服务器响应给前端的所有数据最终都会包装成此种类型中
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {private Integer code; // 编码:1成功,0和其他数字失败private String errMsg; // 错误信息private T data; // 数据(如响应的实体类数据)private Map map = new HashMap(); // 动态数据public static <T> Result<T> success(T data) {Result<T> r = new Result<>();r.code = 1; //成功状态码r.data = data;return r;}public static <T> Result<T> error(String errMsg) {Result<T> r = new Result<>();r.errMsg = errMsg; //设置错误信息r.code = 0; //默认失败状态码,后期我们可以根据自己的需求来设置其他状态码return r;}public Result<T> add(String msg, String value) {this.map.put(msg, value);return this;}
}
登录和登出功能
前端登录登出效果
new Vue({methods: {async handleLogin() {// 校验用户名和密码是否为空,validate是element框架提供的校验方法this.$refs.loginForm.validate(async (valid) => {if (valid) {// 渲染登录的状态,true表示登录中this.loading = true// res是服务端响应的结果(一般是一个通用结果封装类) let res = await loginApi(this.loginForm)if (String(res.code) === '1') {// 1表示登录成功localStorage.setItem('userInfo',JSON.stringify(res.data))// data表示实体类数据window.location.href= '/backend/index.html'} else {this.$message.error(res.msg)this.loading = false}}})logout() {logoutApi().then((res)=>{if(res.code === 1){localStorage.removeItem('userInfo')window.location.href = '/backend/page/login/login.html'}})},}}
}) <div class="right-menu"><!--这里动态的显示登录的用户名--><div class="avatar-wrapper">{{ userInfo.name }}</div><!--这里就是登出的按钮--><img src="images/icons/btn_close@2x.png" class="outLogin" alt="退出" @click="logout" />
</div><div class="right-menu">// userInfo就是登录成功后后端响应的用户信息<div class="avatar-wrapper">{{ userInfo.name }}</div><img src="images/icons/btn_close@2x.png" class="outLogin" alt="退出" @click="logout" />
</div>function loginApi(data) {return $axios({'url': '/employee/login','method': 'post',data})
}function logoutApi(){return $axios({'url': '/employee/logout','method': 'post',})
}
登录和登出的业务流程
第一步: 创建对应的实体类Employee
@Data
public class Employee implements Serializable {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;private LocalDateTime createTime;private LocalDateTime updateTime;// 这两个先不用管,后面再说@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;
}
第二步: 创建对应的Mapper和Service
// 直接将Mapper接口动态生成的代理类交给Spring容器管理(不需要再扫描mapper接口)
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
public interface EmployeeService extends IService<Employee> {
}//继承ServiceImpl实现EmployeeService接口,别忘了@Service注解
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}
第三步: 在Controller中编写登录方法
@RestController
@RequestMapping("/employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 登入功能* @param request * @param employee* @return*/// 登录页面发送post请求提交用户名和密码@PostMapping("/login")// 将页面提交的用户名和密码封装到Employee对象中public Result<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {String password = employee.getPassword();// spring内部提供的工具类可以将密码进行MD5加密处理password = DigestUtils.md5DigestAsHex(password.getBytes());// 根据页面提交的用户名查询数据库LambdaQueryWrapper<Employee> lqw = new LambdaQueryWrapper<>();lqw.eq(Employee::getUsername, employee.getUsername());Employee emp = employeeService.getOne(lqw);// 没有查询到员工则返回登录失败的结果if (emp == null) {return Result.error("登陆失败");}// 与查询到的员工密码进行比对,如果不一致则返回登录失败的结果if (!emp.getPassword().equals(password)) {return Result.error("登录失败");}// 查看查询到员工的状态,如果是已禁用状态,则返回员工已禁用结果if (emp.getStatus() == 0) {return Result.error("该用户已被禁用");}// 登录成功,将员工id存入Session并返回登录结果request.getSession().setAttribute("employee",emp.getId());return Result.success(emp);}
}
第四步: 在Controller中编写退出方法
/**
* 登出功能
* @param request
* @return
*/
@PostMapping("/logout")
public Result<String> logout(HttpServletRequest request) {// 清理Session中保存的当前登录员工的idrequest.getSession().removeAttribute("employee");// 返回结果return Result.success("退出成功");
}
登录状态校验
我们不登录直接访问 http://localhost/backend/index.html也可以正常访问首页显然是不合理的,所以需要用到过滤器或拦截器
在过滤器或拦截器
中判断用户是否登录,只有登录成功才能看到首页面,未登录状态则跳转到登录页面
第一步: 设置过滤器,/*
表示处理所有请求
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {//Spring提供的工具类,专门用于路径匹配public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 强转HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//1.获取本次请求的URIString requestURI = request.getRequestURI();// 将拦截到的URI输出到日志,{}是占位符将自动填充request.getRequestURI()的内容log.info("拦截到请求:{}",requestURI);// 定义不需要处理的所有请求String[] urls = new String[]{"/employee/login","/employee/logout",// 访问backend和front目录下的静态页面的请求不需要拦截,但是页面中通过Ajax请求渲染的数据需要拦截"/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;}//5.如果未登录则返回一个Result对象且msg为NOTLOGINlog.info("用户未登录");// ,// 导入fastjson的坐标将Result对象转化为Json格式的字符串,然后通过输出流方式响应给客户端response.getWriter().write(JSON.toJSONString(Result.error("NOTLOGIN")));}public boolean check(String[] urls, String requestURI){for (String url : urls) {boolean match = PATH_MATCHER.match(url, requestURI);if (match) {//匹配return true;}}//不匹配return false;}
}
第二步: 前端设置响应拦截器
,获取后端响应的用户信息,如果用户未登录则自动重定向到登录页面
// 响应拦截器
service.interceptors.response.use(res => {if (res.data.code === 0 && res.data.msg === 'NOTLOGIN') {// 返回登录页面console.log('---/backend/page/login/login.html---')localStorage.removeItem('userInfo')// 跳转到登录页面window.top.location.href = '/backend/page/login/login.html'} else {return res.data}
}