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數據中被忽略不返回

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