环境搭建
- 准备数据库表(dept,emp)
- 创建SpringBoot工程
- 引入web开发起步依赖,mybatis依赖(mybatis依赖和mysql驱动),lombok依赖
- 在application.properties中引入mybatis的配置信息
- 准备对应的实体类
- 准备对应的Mapper、Service(接口、实现类)、Controller基础结构
部门管理开发:根据接口文档阅读开发Restful风格
- 明确三层架构的功能
- Controller
- 1.接收请求
- 2.调用service层功能
- 3.响应
- Service
- Mapper
简化开发:二者效果相同
@RequestNapping(value = "/depts", method = RequestNethod.GET) @GetMapping("/ depts")
|
注意在接口中注入Bean对象,例如Controller中注入Service对象,Service中注入Mapper对象,达到解耦的效果
==接口测试,postman / 前后端联调==
==日志:@Slf4j==
==注意某些路径的 @PathVariable==
==注意解析json格式的前端请求参数可以使用@RequestBody来将json封装到实体类中==
==@RequestMapping(“/depts”) 公共路径抽取:完整的请求路径,应该是类上的@RequestMapping 的value属性+方法上的@RequestMapping的value属性。==
员工管理开发:
分页查询
需要两条SQL语句
select * from emp limit ?, ?;
|
select count(*) from emp;
|
前端传来的数据
- 当前页码:page
- 每页展示记录数:pageSize
后端响应的数据
- 数据列表:List select * from emp limit ?,?;
- 总记录数:total select count (*) from emp;
==目前需要返回两项数据,方法只能返回一个数据,使用实体类封装返回==
==@ResultParam(defaultvalue = ”默认值“) 参数 可以设置参数的默认值==
分页查询插件PageHelper
文件上传
方法1:将文件保存到本地
方法2:将文件保存到AliOSS
@PostMapping("/upload") public Result upload(MultipartFile image) throws Exception { log.info("文件上传,文件名:{}", image.getOriginalFilename());
String url = aliOSSUtils.upload(image); log.info("文件上传完成,文件访问的url为:{}", url);
return Result.success(url); }
|
==修改员工需要查询回显和update语句==
==配置注入使用@Value(“#{配置中的key}”)注解来防止硬编码==
登陆校验(难点)
==若按照普通的开发区编写映射页面,通过数据库判别是否用户名与密码正确式不可取的==
如此的话,直接访问类似于 /emps /depts 等页面可以直接进入控制台面板而跳过了判断是否为管理员的步骤
因此,我们需要在 /login /depts /emps 之间建立一种联系
实现思路:
- 员工登陆成功存储一个标记
- 在每一个方法接口前判断员工是否登陆,登陆则访问,未登录则返回给前端错误信息
- 因此,需要两种技术==1.统一拦截== ==2.登陆标记缓存==
- 统一拦截
- Servlet提供的过滤器Filter
- Spring提供的拦截器Interceptor
- 用户登陆
会话技术
http协议是无状态协议,不会携带上一次会话的信息,因此无法数据共享
- 会话跟踪方案
- 客户端会话跟踪技术:Cookie(存储在浏览器)
- 服务端会话跟踪技术:Session(存储在服务器端)
- 令牌技术
- Cookie:浏览器第一次请求时在服务器端设置Cookie,响应时携带Cookie,而后Cookie缓存在浏览器本地,之后的每次请求都会携带Cookie与客户端进行对比,服务器自动响应Cookie,浏览器自动保存Cookie,发起请求自动携带Cookie,三个自动表名Http支持Cookie技术
优点:HTTP协议支持
缺点:1.移动端无法使用 2.不安全,用户可以自己禁用 3.Cookie不能跨域(跨域:协议、IP/域名、端口不同)
- Session:浏览器第一次请求时在服务器端设置Session(ID),响应时通过Cookie返回Session的ID,之后的每次请求都会携带Cookie中的Session的ID到服务器端对比
优点:存储在服务器端,安全
缺点:1.服务器集群在负载均衡后有可能无法使用,因此无法在服务器集群环境下使用 2.Cookie的缺点
- 令牌技术(==建议使用==):用户身份表示,登陆成功生成令牌,响应给浏览器,可以存储在Cookie或者其他存储空间中,之后的每次请求都会携带令牌,比对是否有效,而且想要共享数据只需要将数据保存在令牌中即可
优点:1.支持PC、移动端 2、解决集群环境下的认证问题 3、减轻了服务器端的存储压力
缺点:需要自己去实现
JWT(JSON Web Token)令牌
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
jwt令牌组成
- 第一部分:Header(头),记录令牌类型、签名算法等。例如: {“alg”:”HS256” ,”type”:”JWT”}
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如: {“id”:” 1” ,”username”:”Tom”}
- 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、 payload,并加入指定秘钥,通过指定签名算法计算而来。
pom.xml引入
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
|
jwt生成
public class JwtUtils {
private static String signKey = "wangnine"; private static Long expire = 43200000L;
public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts.builder() .addClaims(claims) .signWith(SignatureAlgorithm.HS256, signKey) .setExpiration(new Date(System.currentTimeMillis() + expire)) .compact(); return jwt; }
public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(jwt) .getBody(); return claims; } }
|
统一拦截技术
Filter过滤器
JavaWeb三大组件(Servlet、Filter、Listener)之一。
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
相当于抽取了各个页面的登陆校验,优点aop的思想
==如何实现过滤器==
- 1.定义Filter:定义一个类,实现 Filter接口,并重写其所有方法。
- 配置Filter: Filter类上加@WebFilter注解,配置拦截资源的路径。引导类上加@ServletComponentScan开启Servlet组件支持。
结合了jwt后的代码如下:
@Slf4j
public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; String url = req.getRequestURL().toString(); log.info("请求的url:{}", url);
if(url.contains("login")) { log.info("登录操作,放行..."); filterChain.doFilter(servletRequest, servletResponse); return; }
String jwt = req.getHeader("token");
if(!StringUtils.hasLength(jwt)) { log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return; }
try { JwtUtils.parseJWT(jwt); } catch (Exception e) { log.info("解析令牌失败,返回未登录的误信息"); Result error = Result.error("NOT_LOGIN"); String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return; }
log.info("令牌合法,放行"); filterChain.doFilter(servletRequest, servletResponse); } }
|
Interceptor拦截器
@Slf4j @Component public class LoginCheckInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
String url = req.getRequestURL().toString(); log.info("请求的url:{}", url);
if(url.contains("login")) { log.info("登录操作,放行..."); return true; }
String jwt = req.getHeader("token");
if(!StringUtils.hasLength(jwt)) { log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false; }
try { JwtUtils.parseJWT(jwt); } catch (Exception e) { log.info("解析令牌失败,返回未登录的误信息"); Result error = Result.error("NOT_LOGIN"); String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false; }
log.info("令牌合法,放行"); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..."); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); } }
|
异常处理
在Controller层定义一个全局异常处理器
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class) public Result ex(Exception ex) { ex.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员"); } }
|
@RestControllerAdvice
@ExceptionHandler(Exception.class) //捕获所有的异常