SpringBoot实战项目精华总结(四)

SpringBoot实战项目精华总结(四)
    一、登录拦截原理与实现 -- AOP
    
、微信推送模板消息
    
、websocket模板消息推送
    
、Freemarker的使用
    
、分布式系统下的session及其他
一、登录拦截原理与实现 -- AOP
1.切面类(一个切面方法 - @Pointcut,再写一个具体的实现方法 - @Before)

/**
 * @Description 请求验证切面类
 * @Date 2020/2/20 11:13
 */
@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {

 @Autowired
 private StringRedisTemplate redisTemplate;

 // 先写一个切面方法,切入点为需要验证权限的Controller
 @Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
 "&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
 public void verify() {}

 // 再写一个切面方法的具体实现
 @Before("verify()")
 public void doVerify() {
  // 重点:这里获取请求request对象
  ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  HttpServletRequest request = attributes.getRequest();

  //查询cookie
  Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
  if (cookie == null) {
   log.warn("【登录校验】Cookie中查不到token");
   throw new SellerAuthorizeException();
  }

  //去redis里查询
  String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
  if (StringUtils.isEmpty(tokenValue)) {
   log.warn("【登录校验】Redis中查不到token");
   throw new SellerAuthorizeException();
  }
 }
}

2.不满足验证条件的抛出一个特殊的异常
如:SellerAuthorizeException -- 可以不写内容
3.对特殊异常进行捕获@ControllerAdvice

@ControllerAdvice
public class SellExceptionHandler {

 @Autowired
 private ProjectUrlConfig projectUrlConfig;

 @ExceptionHandler(value = SellerAuthorizeException.class)
 public ModelAndView handlerAuthorizeException() {
  return new ModelAndView("seller/login");
 }
}

二、微信推送模板消息
1.官方文档
https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#5
2.在微信公众平台设置好模板

3.编写推送给用户订单消息接口(WxMpTemplateData对象可以给关键字设置颜色

4.可能涉及到IP白名单配置 =>平台=>基本配置=>查看IP白名单
5.可以在yaml文件中设置一个Map,用于存放一系列TemplateId
  private Map<String, String> templateId;
6.可以再设置url,让用户点击通知跳转到H5页面;设置miniprogram可跳转到小程序

三、websocket模板消息推送(客户端、服务端都要设置)
1.客户端
 基于原生的H5中开发js代码,监听服务端推送来的消息
实现:
- 弹窗代码

<#--弹窗-->
<div class="modal fade" id="myModal" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
 <div class="modal-dialog">
  <div class="modal-content">
   <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
    <h4 class="modal-title" id="myModalLabel">
     提醒
    </h4>
   </div>
   <div class="modal-body">
    你有新的订单
   </div>
   <div class="modal-footer">
    <button onclick="javascript:document.getElementById('notice').pause()" type="button"
      class="btn btn-default" data-dismiss="modal">关闭
    </button>
    <button onclick="location.reload()" type="button" class="btn btn-primary">查看新的订单</button>
   </div>
  </div>
 </div>
</div>

- js代码

<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script>
 var websocket = null;
 if ('WebSocket' in window) {
  websocket = new WebSocket('ws://www.xxx.com/project/webSocket');
 } else {
  alert('该浏览器不支持websocket!');
 }

 websocket.onopen = function (event) {
  console.log('建立连接');
 }

 websocket.onclose = function (event) {
  console.log('连接关闭');
 }

 websocket.onmessage = function (event) {
  console.log('收到消息:' + event.data)
  //弹窗提醒, 播放音乐
  $('#myModal').modal('show');

  document.getElementById('notice').play();
 }

 websocket.onerror = function () {
  alert('websocket通信发生错误!');
 }

 window.onbeforeunload = function () {
  //在关闭窗口前,关闭ws监听!
  websocket.close();
 }
</script>

2.后端代码
- 引入springboot依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

- 配置WebSocketConfig

/**
 * @Description Websocket配置
 */
@Component
public class WebSocketConfig {
     @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

- 编写webservice类

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @Description webSocket通信类
 * @Date 2020/2/25 13:29
 */
@Component
@ServerEndpoint("/webSocket")
@Slf4j
public class WebSocket {

 private Session session; //
 private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();

 @OnOpen
 public void onOpen(Session session) {
  this.session = session;
  webSocketSet.add(this);
  log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());
 }

 @OnClose
 public void onClose() {
  webSocketSet.remove(this);
  log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
 }

 @OnMessage
 public void onMessage(String message) {
  log.info("【websocket消息】收到客户端发来的消息:{}", message);
 }

 public void sendMessage(String message) {
  for (WebSocket webSocket : webSocketSet) {
   log.info("【websocket消息】广播消息, message={}", message);
   try {
    webSocket.session.getBasicRemote().sendText(message);
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
}

四、Freemarker的使用
1.分页的页码语法
 
2.处于某一页时,当前页码不能点
 
3.上一页、下一页
   
4.3秒后自动跳转的实现

<script>
    setTimeout('location.href="${url}"', 3000);
</script>

5.页面中订单状态的判断
  
6.公用边栏的抽取实现
6.1 准备一个nav.ftl,在list.ftl引入sidebar

<#--边栏sidebar-->
<#include "../common/nav.ftl">

6.2 同样可以提取head,叫header.ftl

<head>
  <meta charset="utf-8">
  <title>xxx管理系统</title>
  <link href="https://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="/sell/css/style.css">
</head>

引入:

<#include "../common/header.ftl">

7.模板页面取不到值报错的解决办法
  如果取不到msg字段,就留空值(注意写法)
  ${msg!""}
  如果取不到productInfo对象,就留空值(注意写法)

  ${(productInfo.productIcon)!''}
8.下拉菜单实现代码示例

<select name="categoryType" class="form-control">
  <#list categoryList as category>
    <option value="${category.categoryType}"
      <#if (productInfo.categoryType)?? && productInfo.categoryType == category.categoryType>
          selected
      </#if>
      >${category.categoryName}
    </option>
  </#list>
</select>

五、分布式系统下的session
1.分布式系统
  三个特点:多节点、消息通信、不共享内存
2.session
  ->会话控制:保存用户的信息,并在后续的请求中验证身份信息
  ->session可以理解为一种k-v的机制,session设计的关键点:①.如何设计和获取key,②.如何保存和正确获取对应的value
  ->sessionId与token:前端请求设置在header中,后端存储在redis中,从而保持会话状态

3.微信网页扫码登录设计
  使用微信开放平台:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
  注册时比较恶心
  
时序:
  ->访问某个页面时,被前面拦截验证cookie,如果没有openid参数,抛出异常跳转到异常处理类,异常处理类携带一个回调url发起调用微信开发平台,唤起扫码页
  ->扫码人扫码后,微信回调携带的url?openid=xxx,注意此时的openid与公众平台的不一致
  ->再验证次openid是否在库,符合条件即设置cookie+缓存登陆成功,否则失败

4.登出    
    删除redis中存储的k-v,cookie设置失效
5.关于cookie操作的工具类

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description cookie工具类
 * @Date 2020/2/18 22:20
 */
public class CookieUtil {

 /**
  * 设置
  * @param response
  * @param name
  * @param value
  * @param maxAge
  */
 public static void set(HttpServletResponse response,
         String name,
         String value,
         int maxAge) {
  Cookie cookie = new Cookie(name, value);
  cookie.setPath("/");
  cookie.setMaxAge(maxAge);
  response.addCookie(cookie);
 }

 /**
  * 获取cookie
  * @param request
  * @param name
  * @return
  */
 public static Cookie get(HttpServletRequest request,
         String name) {
  Map<String, Cookie> cookieMap = readCookieMap(request);
  if (cookieMap.containsKey(name)) {
   return cookieMap.get(name);
  }else {
   return null;
  }
 }

 /**
  * 将cookie封装成Map
  * @param request
  * @return
  */
 private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
  Map<String, Cookie> cookieMap = new HashMap<>();
  Cookie[] cookies = request.getCookies();
  if (cookies != null) {
   for (Cookie cookie: cookies) {
    cookieMap.put(cookie.getName(), cookie);
   }
  }
  return cookieMap;
 }
}

6.代替String拼接的format方法
// 此时%s为占位符,res = "token_xiaoqiang"
String res = String.format("token_%s", “xiaoqiang”);

7.从url中获取请求参数:是value属性!
  @RequestParam(value = "page", defaultValue = "1")
  表单提交是取的name属性
  如果非必传参数,可以设置@RequestParam(value = "page", required = false)
8.枚举通用接口的使用

public interface CodeEnum {
    Integer getCode(); // 枚举类共用的方法
}

这样就避免了在每个枚举类中都写一个getByCode的方法
/**
 * @Description 根据code+枚举类型,返回枚举工具类
 * @Date 2020/2/16 21:54
 */
public class EnumUtil {
  public static <T extends CodeEnum> T getByCode(Integer code, Class<T> enumClass) {
    for (T each: enumClass.getEnumConstants()) {
     if (code.equals(each.getCode())) {
      return each;
     }
    }
    return null;
  }
}

9.@JsonIgnore可以在返回的json数据中被忽略不返回

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章