开发前准备
准备数据表
结合页面原型创建数据库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_out
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: 123456
redis:
host: localhost
port: 6379
database: 0
cache:
redis:
time-to-live: 1800000 #ms ->30min
mybatis-plus:
configuration:
# 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法开启映射(默认就是true)
map-underscore-to-camel-case: true
# 开启日志功能
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-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 {
@Override
protected 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 {
@Autowired
private 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中保存的当前登录员工的id
request.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();
@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();
// 将拦截到的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为NOTLOGIN
log.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
}
}