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