**說在前面:該項目是跟著B站一位大佬寫的,不分享源碼,支持項目付費 **
獲取圖形驗證碼
可以看到這里有2兩種圖形驗證碼,分為:
type=0:如上圖下面那個,是完成操作后要進行注冊的驗證碼
type=1: 如上圖上面那個,是要發送郵箱之前的圖形驗證碼。
這里在交互時,設置了session值,存放了兩種圖形驗證碼,便于后面進行用戶輸入情況和session值比對。
那什么是session呢?
(1)Session用于記錄用戶的狀態。Session指的是一段時間內,單個客戶端與Web服務器的一連串相關的交互過程。
(2)在一個Session中,客戶可能會多次請求訪問同一個資源,也有可能請求訪問各種不同的服務器資源。
(3)Session是由服務器端創建的一個對象
可能涉及到的session操作
-
session.setAttribute(key,value)
類似hashmap,存放鍵值。
-
session.getAttribute(key)
根據key獲取值,獲得值的類型為object,要根據情況進行強轉
-
session.removeAttribute(key)
刪除該key和對應的值,但session這個對象還在
-
session.invalidate()
銷毀了session對象
那如果是多服務器上部署這個項目,涉及多個session:
當系統部署到多臺服務器時,由于每個服務器都有自己獨立的內存空間,默認情況下 HttpSession 是無法在不同服務器之間共享的。這會導致用戶在不同服務器上的請求無法正確識別其會話狀態,例如用戶在一臺服務器上登錄成功,但下一次請求被分配到另一臺服務器時,該服務器無法獲取到用戶的登錄狀態。為了解決這個問題:
使用分布式緩存(如 Redis)是一種比較常用的解決方案。它將 Session 數據存儲在 Redis 中,而不是存儲在服務器的內存中。這樣,所有服務器都可以通過 Redis 來獲取和更新 Session 數據,從而實現了 Session 的共享。這種方案具有較好的擴展性和性能,而且可以避免會話復制帶來的網絡開銷。
這里給出圖形驗證碼是如何生成的:
public class CreateImageCode {// 圖片的寬度。private int width = 160;// 圖片的高度。private int height = 40;// 驗證碼字符個數private int codeCount = 4;// 驗證碼干擾線數private int lineCount = 20;// 驗證碼private String code = null;// 驗證碼圖片Bufferprivate BufferedImage buffImg = null;Random random = new Random();public CreateImageCode() {creatImage();}public CreateImageCode(int width, int height) {this.width = width;this.height = height;creatImage();}public CreateImageCode(int width, int height, int codeCount) {this.width = width;this.height = height;this.codeCount = codeCount;creatImage();}public CreateImageCode(int width, int height, int codeCount, int lineCount) {this.width = width;this.height = height;this.codeCount = codeCount;this.lineCount = lineCount;creatImage();}// 生成圖片private void creatImage() {int fontWidth = width / codeCount;// 字體的寬度int fontHeight = height - 5;// 字體的高度int codeY = height - 8;// 圖像bufferbuffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics g = buffImg.getGraphics();//Graphics2D g = buffImg.createGraphics();// 設置背景色g.setColor(getRandColor(200, 250));g.fillRect(0, 0, width, height);// 設置字體//Font font1 = getFont(fontHeight);Font font = new Font("Fixedsys", Font.BOLD, fontHeight);g.setFont(font);// 設置干擾線for (int i = 0; i < lineCount; i++) {int xs = random.nextInt(width);int ys = random.nextInt(height);int xe = xs + random.nextInt(width);int ye = ys + random.nextInt(height);g.setColor(getRandColor(1, 255));g.drawLine(xs, ys, xe, ye);}// 添加噪點float yawpRate = 0.01f;// 噪聲率int area = (int) (yawpRate * width * height);for (int i = 0; i < area; i++) {int x = random.nextInt(width);int y = random.nextInt(height);buffImg.setRGB(x, y, random.nextInt(255));}String str1 = randomStr(codeCount);// 得到隨機字符this.code = str1;for (int i = 0; i < codeCount; i++) {String strRand = str1.substring(i, i + 1);g.setColor(getRandColor(1, 255));// g.drawString(a,x,y);// a為要畫出來的東西,x和y表示要畫的東西最左側字符的基線位于此圖形上下文坐標系的 (x, y) 位置處g.drawString(strRand, i * fontWidth + 3, codeY);}}// 得到隨機字符private String randomStr(int n) {String str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";String str2 = "";int len = str1.length() - 1;double r;for (int i = 0; i < n; i++) {r = (Math.random()) * len;str2 = str2 + str1.charAt((int) r);}return str2;}// 得到隨機顏色private Color getRandColor(int fc, int bc) {// 給定范圍獲得隨機顏色if (fc > 255) fc = 255;if (bc > 255) bc = 255;int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);}/*** 產生隨機字體*/private Font getFont(int size) {Random random = new Random();Font font[] = new Font[5];font[0] = new Font("Ravie", Font.PLAIN, size);font[1] = new Font("Antique Olive Compact", Font.PLAIN, size);font[2] = new Font("Fixedsys", Font.PLAIN, size);font[3] = new Font("Wide Latin", Font.PLAIN, size);font[4] = new Font("Gill Sans Ultra Bold", Font.PLAIN, size);return font[random.nextInt(5)];}// 扭曲方法private void shear(Graphics g, int w1, int h1, Color color) {shearX(g, w1, h1, color);shearY(g, w1, h1, color);}private void shearX(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(2);boolean borderGap = true;int frames = 1;int phase = random.nextInt(2);for (int i = 0; i < h1; i++) {double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);g.copyArea(0, i, w1, 1, (int) d, 0);if (borderGap) {g.setColor(color);g.drawLine((int) d, i, 0, i);g.drawLine((int) d + w1, i, w1, i);}}}private void shearY(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(40) + 10; // 50;boolean borderGap = true;int frames = 20;int phase = 7;for (int i = 0; i < w1; i++) {double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);g.copyArea(i, 0, 1, h1, 0, (int) d);if (borderGap) {g.setColor(color);g.drawLine(i, (int) d, i, 0);g.drawLine(i, (int) d + h1, i, h1);}}}public void write(OutputStream sos) throws IOException {ImageIO.write(buffImg, "png", sos);sos.close();}public BufferedImage getBuffImg() {return buffImg;}public String getCode() {return code.toLowerCase();}
}
發郵箱前驗證碼
這段的實現邏輯:
根據傳過來的參數,其中type 0:注冊郵箱時 1:重置密碼時(后續)
先進行發郵箱前的圖形驗證碼校對,對應后,執行發送郵箱操作。
最后,清空一下session存的當前驗證碼。如果驗證碼在使用后不被清除,那么惡意用戶可能會獲取到該驗證碼,并在后續的請求中重復使用,從而繞過驗證碼的驗證機制,對系統的安全性造成威脅。
這段邏輯就是發送驗證碼前檢驗該賬號是否已經注冊
發送郵箱后,如果重復發送發送郵箱的話,需要將之前的郵箱驗證碼設置為1:用過了
然后是發送郵箱的實現:
AOP切面實現參數校驗
在前端傳過來的各種參數,我們需要進行非空校驗,最笨的方法就是if else判斷,這樣耗時耗力,很不適用,為了減少冗雜性判斷為空代碼,通過注解作用在參數上,實現切面類判斷。
首先,創建攔截器:
第一個是對參數進行校驗,@GlobalInterceptor 注解的主要目的是標記需要進行特定攔截處理的方法,其中 checkParams = true 表示需要對方法的參數進行校驗。
而這個,是對某參數進行具體的校驗:
然后就是切面設計:AOP 可以在方法執行前攔截方法調用,并對方法的參數進行校驗。
注冊
這段邏輯
首先進行注冊前的圖形驗證碼校對
然后執行注冊操作
最后再清空session存的當前圖形驗證碼。
service層實現:
再次檢查是否郵箱已經注冊,比對昵稱唯一(項目要求唯一)
然后根據表中之前存的郵箱和郵箱驗證碼,跟現在填的進行比對 ,校驗通過后,插入用戶信息,其中用戶網盤空間分配:已用空間0,總空間初始分配5MB.
其中校驗對應:
分兩種情況
1:郵箱和郵箱驗證碼不能對應起來,視為錯誤
2:郵箱驗證碼之前設置過15分鐘內有效。一旦超時,視為無效,再次更改表,將這個郵箱驗證碼設置為1:已用過
System.currentTimeMillis():用來獲取當前的總毫秒數
getTime():返回毫秒數
登錄
這段的邏輯
同樣檢查圖形驗證碼
然后設置了一個dto(含id昵稱頭像)來接收登錄信息,設置session當前登錄信息, 并將這個dto返回給VO
serviceImpl:
這段邏輯
通過郵箱比對是否該賬號注冊了或者密碼對不上。不能分情況if,不讓用戶知道具體是哪個錯了。
然后就算是它登錄了,就要更新其登錄時間,給dto的屬性賦值
另外還查看是否是超級管理員,如果是,則dto的isAdmin設為true。
成功登陸后,給用戶分配網盤空間。注冊時是給user標注空間分配信息,現在設置的是方便在redis中存儲
忘記密碼
和注冊邏輯相似,重置密碼,主要就是更新一下password。而發送郵箱等業務是已經完成了的,才把emailCode傳過來了。
修改密碼
這個和忘記密碼重置有區別,忘記密碼是沒登錄進去時重置,這個是登進去后在個人設置里修改。
controller
這段邏輯
通過繼承AbaseController類里的方法getUserInfoFromSession,通過session找到其SESSION_KEY對應的dto返回,再通過dto獲取當前用戶id,進行密碼修改。
獲取默認頭像
mkdir()和mkdirs()
創建文件夾。
mkdir方法是用于創建最后一個/后面的文件夾,最后一個/前面的文件夾必須都存在。
mkdirs方法是無論父文件夾是否存在都會創建。
這段邏輯
先在配置文件中定義了項目根目錄文件夾,隨后定義文件file/和用于存放頭像的avatar/文件夾,如果沒有這個文件夾,就創建。總的合起來組成一個完整的頭像路徑字符串
如項目根目錄/file/avatar/user123.jpg
如果還是沒有這個文件,再看看有沒有設置默認頭像路徑 項目根目錄/file/avatar/default.jpg
然后讀取文件。
上傳頭像
這段邏輯
先獲取dto用戶信息,然后就是找到或創建存放頭像的文件夾,將用戶上傳的文件保存到這個文件夾里
因為自定義頭像需要覆蓋原來的qq頭像,所以qq頭像設置為空
然后webUserDto的avatar也被設為null,并更新到session中,這樣下次請求時用戶的最新頭像信息會被重新加載現在自定義好的或者使用默認頭像。
關于MultipartFile
MultipartFile:
public interface MultipartFile extends InputStreamSource { //getName() 返回參數的名稱 String getName(); //獲取源文件的昵稱 @Nullable String getOriginalFilename(); //getContentType() 返回文件的內容類型 @Nullable String getContentType(); //isEmpty() 判斷是否為空,或者上傳的文件是否有內容 boolean isEmpty(); //getSize() 返回文件大小 以字節為單位 long getSize(); //getBytes() 將文件內容轉化成一個byte[] 返回 byte[] getBytes() throws IOException; //getInputStream() 返回InputStream讀取文件的內容 InputStream getInputStream() throws IOException;default Resource getResource() {return new MultipartFileResource(this); } //transferTo是復制file文件到指定位置(比如D盤下的某個位置),不然程序執行完,文件就會消失,程序運行時,臨時存儲在temp這個文件夾中 void transferTo(File var1) throws IOException, IllegalStateException;default void transferTo(Path dest) throws IOException, IllegalStateException {FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest)); } }
這里前端傳過來文件,我們用MultipartFile avatar 接收
關于getPath()
將抽象路徑名轉換為一個路徑名字符串。所得到的字符串使用默認名稱分隔符來分隔名稱序列中的名稱。
public static void test1() {File file1 = new File(".\\test1.txt");File file2 = new File("D:\\workspace\\test\\test1.txt");System.out.println("-----默認相對路徑:取得路徑不同------");System.out.println(file1.getPath());System.out.println(file1.getAbsolutePath());System.out.println("-----默認絕對路徑:取得路徑相同------");System.out.println(file2.getPath());System.out.println(file2.getAbsolutePath()); }----- 默認相對路徑:取得路徑不同 ------ .\test1.txt D:\workspace\test\.\test1.txt ----- 默認絕對路徑:取得路徑相同 ------ D:\workspace\test\test1.txt D:\workspace\test\test1.txt