專案介紹:
(1)功能介紹:
主要業務:為公司活動(如年會等)提供線上抽獎功能,滿足獎品、抽獎人員的管理,及抽獎活動的需要。
使用者註冊
使用者登入、會話管理
抽獎設定:獎品管理,抽獎人員管理
人員抽獎
(2)開發環境與技術棧:
windows
Maven
Lombok
Spring、SpringMVC、SpringBoot
MySQL、Mybatis、Druid
(3)專案演示:
使用者登入:
使用者註冊:
獎項設定:
抽獎人員設定:
抽獎:
2、專案準備:
(1)程式碼框架:(原始碼)
(2)資料庫設計:
資料庫表關係圖:
(業務上一對一)
為什麼要有設定表?
1對1關聯的表,其實可以只使用1張表儲存所有欄位;但是在一些可能出現的業務擴充套件,方便系統擴充套件使用,所以設計表時,考慮1對1設計。
拓展業務 ,一個使用者進來設定,當多個使用者進來設定的時候(多個使用者屬於同一公司),如果是隻有使用者表的話,即無法支撐業務,使用者表關聯公司,設定表在關聯公司
3、後端對前端介面的實現:
要實現功能,需要先明確前後端約定好的介面。需要說明的是,介面的定義一般是前後端約定好的,所以也和前端程式碼息息相關,前端需要什麼資料,需要什麼格式的資料,也會在介面中體現。
介面主要體現在:
請求需要的資訊:請求方法,請求路徑,請求資料
響應資料
(1)使用者的登入、註冊、登出
使用者登入:
前端請求:
POST api/user/login (請求路徑)
Content-Type: application/json {username: “qbs”, password: “123”}
響應:
{ “success” : true }
後端實現介面:
使用者註冊:
前端請求:
POST api/user/register (請求路徑)
Content-Type: multipart/form-data; boundary=—— WebKitFormBoundarypOUwkGIMUyL0aOZT
username: qbs
password: 123
nickname: 帥哥
email: 666@163。com
age: 18
headFile: (binary)
響應:
{ “success” : true }
後端實現:
使用者登出:
前端請求:
POST api/user/login (請求路徑)
後端實現:
(2)查詢獎項設定、修改抽獎人數:
查詢獎項設定:
前端請求:
GET api/setting/query(請求路徑)
後端實現:
修改抽獎人數:
前端請求:
GET api/setting/update?batchNumber=5(請求路徑)
(介面對應抽獎設定頁面中,點每次抽獎人數下拉選單切換時修改)
後端實現:
(3)新增、修改、刪除獎項:
新增獎項:
前端請求:
POST api/award/add (請求路徑)
Content-Type: application/json
{name: “特等獎”, count: 1, award: “全球旅行7日遊”}
後端實現:
修改獎項:
前端請求:
POST api/award/update (請求路徑)
Content-Type: application/json
後端響應:
刪除獎項:
前端請求:
GET api/award/delete/4(請求路徑)
最後的數字4,對應獎項的id
後端實現:
(4)新增、修改、刪除抽獎人員:
新增抽獎人員:
前端請求:
POST api/member/add (請求路徑)
Content-Type: application/json
後端實現:
修改抽獎人員
前端請求:
POST api/member/update
Content-Type: application/json
後端實現:
刪除抽獎人員
前端請求:
GET api/member/delete/97
(最後的數字為抽獎人員的id)
後端實現:
(5)抽獎、刪除獲獎人員:
抽獎:
前端請求:
POST api/record/add/3
Content-Type: application/json
(以上路徑中最後的數字代表獎項id,請求資料為抽獎人員id組成的陣列)
後端實現:
抽獎後端只是插入記錄(人員id、獎項id),具體的抽獎是前端實現的,而且也是簡單的實現方式,沒有任何演算法。(只是在當前獎項剩餘名額中,每次抽獎人數,在所有未中獎的人員列表中,隨機抽取)
刪除獲獎人員:
前端請求:
GET api/record/delete/member?id=22
(根據 人員id 刪除對應的獲獎記錄)
GET api/record/delete/award?id=3
(根據 獎項id 刪除對應所有獲獎人員記錄)
後端實現:
4、程式碼設計:
(1)設計資料庫的實體類:
透過 mybatis 生成工具(tool包):生成 mapper、資料庫表實體類(model包)、xml 檔案
(2)設計統一響應類:
主要是為了返回資料的統一欄位設計
/**
* 統一響應的資料格式
*/
public class JSONResponse {
private boolean success;
private String code;
private String message;
private Object data;
}
/**
* 統一資料封裝
*/
public class RequestResponseBodyMethodProcessorWrapper implements HandlerMethodReturnValueHandler {
private final HandlerMethodReturnValueHandler delegate;
public RequestResponseBodyMethodProcessorWrapper(HandlerMethodReturnValueHandler delegate) {
this。delegate = delegate;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return delegate。supportsReturnType(returnType);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//returnValue是Controller請求方法執行完,返回值
if(!(returnValue instanceof JSONResponse)){//返回值本身就是需要的型別,不進行處理
JSONResponse json = new JSONResponse();
json。setSuccess(true);
json。setData(returnValue);
returnValue = json;
}
delegate。handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
(3)設計自定義異常:
主要針對不同的場景,需要拋異常來處理時,能定位業務含義。
主要分為:
1、 客戶端請求錯誤時的異常:需要給定錯誤碼,方便前端提示使用者,如使用者名稱存在不允許註冊
2、 業務發生錯誤時的異常:需要給定錯誤碼,方便後端定位問題,一般如程式上的業務錯誤都可以拋(BUG)
3、 系統發生錯誤時的異常:需要給定錯誤碼,方便後端定位問題,程式出錯,如資料庫連接獲取失敗都可以拋(一般是系統發生錯誤,如網路斷了,資料庫掛了等等)自定義異常前端需要顯示錯誤碼和錯誤訊息,使用者可以根據提示資訊判斷原因。
4、非自定義異常,異常資訊一般是框架或JDK丟擲的英文,是給開發人員描述錯誤的,無法給使用者提示,所以錯誤資訊提示為未知異常。
/**
* 自定義異常:儲存錯誤碼和錯誤訊息
*/
@Getter
@Setter
public class AppException extends RuntimeException {
private String code;
public AppException( String code, String message) {
super(message);
this。code = code;
}
public AppException( String code, String message, Throwable cause) {
super(message, cause);
this。code = code;
}
}
//統一異常處理
@ControllerAdvice
@Slf4j//使用lombok日誌日誌註解,之後使用log屬性來完成日誌列印
public class ExceptionAdvice {
//自定義異常報錯錯誤碼和錯誤訊息
@ExceptionHandler(AppException。class)
@ResponseBody
public Object handle1(AppException e){
JSONResponse json = new JSONResponse();
json。setCode(e。getCode());
json。setMessage(e。getMessage());
log。debug(“自定義異常”, e);
return json;
}
//非自定義異常(英文錯誤資訊,堆疊資訊,不能給使用者看):
// 指定一個錯誤碼,錯誤訊息(未知錯誤,請聯絡管理員)
@ExceptionHandler(Exception。class)
@ResponseBody
public Object handle2(Exception e){
JSONResponse json = new JSONResponse();
json。setCode(“ERR000”);
json。setMessage(“未知錯誤,請聯絡管理員”);
log。error(“未知錯誤”, e);
return json;
}
}
(4)設計統一會話管理的攔截器
public class LoginInterceptor implements HandlerInterceptor {
private ObjectMapper objectMapper;
public LoginInterceptor(ObjectMapper objectMapper) {
this。objectMapper = objectMapper;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request。getSession(false);
if(session != null){//獲取登入時設定的使用者資訊
User user = (User) session。getAttribute(“user”);
if(user != null){//登入了,允許訪問
return true;
}
}
//登入失敗,不允許訪問的業務:區分前後端
//TODO:前端跳轉登入頁面,後端返回json
// new ObjectMapper()。writeValueAsString(object);//序列化物件為json字串
//請求的服務路徑
String servletPath = request。getServletPath();// /apiXXX。html
if(servletPath。startsWith(“/api/”)){//後端邏輯:返回json
response。setCharacterEncoding(“UTF-8”);
response。setContentType(MediaType。APPLICATION_JSON_VALUE);
JSONResponse json = new JSONResponse();
json。setCode(“USR000”);
json。setMessage(“使用者沒有登入,不允許訪問”);
String s = objectMapper。writeValueAsString(json);
response。setStatus(HttpStatus。UNAUTHORIZED。value());
PrintWriter pw = response。getWriter();
pw。println(s);
pw。flush();
}else{//前端邏輯:跳轉到登入頁面 /views/index。html
//相對路徑的寫法,一定是請求路徑作為相對位置的參照點
//使用絕對路徑來重定向,不建議使用相對路徑和轉發
String schema = request。getScheme();//http
String host = request。getServerName();//ip
int port = request。getServerPort();//port
String contextPath = request。getContextPath();//application Context path應用上下文路徑
String basePath = schema+“://”+host+“:”+port+contextPath;
//重定向到登入頁面
response。sendRedirect(basePath+“/index。html”);
}
return false;
}
}
(5)設計Mybatis中Mapper的基類:
使用Mybatis的介面方法,所有介面方法都是類似,只是傳入引數和返回值不同,可以考慮設計統一的基類,以泛型的方式定義出不同的引數型別、返回型別
/**
* 所有 mapper 父介面
*/
public interface BaseMapper
int deleteByPrimaryKey(Integer id);
int insert(T record);
int insertSelective(T record);
T selectByPrimaryKey(Integer id);//透過主鍵查詢
int updateByPrimaryKeySelective(T record);//根據主鍵修改其他非主鍵欄位
int updateByPrimaryKey(T record);
}