1、設計數據庫
2、寫基本框架
entity、controller、service、exception、utils、mapper
mapper層:
生成了一系列的CRUD方法
工具類:線程安全的日期工具類、???參數校驗工具類?
線程安全的日期工具類??:主要用于 ??日期格式化(format)和解析(parse)??,并解決了?SimpleDateFormat
?的線程安全問題?
參數校驗工具類:主要用于檢查對象參數是否滿足“至少有一個非空字段”的條件,并提供了一些字符串輔助方法(如首字母大寫、判空)
3、登錄驗證碼校驗
這段代碼是一個 ??驗證碼生成與校驗?? 的接口,主要用于生成圖片驗證碼并存儲到?HttpSession
?中,以便后續驗證用戶輸入的驗證碼是否正確?
-
??生成圖片驗證碼??
- 使用?
CreateImageCode
?類創建一個 ??130x38 像素?? 的驗證碼圖片,包含 ??5 個隨機字符??,干擾線數量為 ??10??。 - 生成的驗證碼字符串存儲在?
code
?變量中。
- 使用?
-
??設置 HTTP 響應頭??
- 禁用緩存,確保每次請求都生成新的驗證碼:
response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0);
- 設置響應類型為?
image/jpeg
,表示返回的是 JPEG 圖片:response.setContentType("image/jpeg");
- 禁用緩存,確保每次請求都生成新的驗證碼:
-
??存儲驗證碼到 Session??
- 根據?
type
?參數決定驗證碼的用途:type=1
:普通驗證碼(如登錄驗證),存儲到?Constants.CHECK_CODE_KEY
。- 其他情況(如郵箱驗證碼),存儲到?
Constants.CHECK_CODE_KEY_EMAIL
。
- 根據?
-
??輸出驗證碼圖片??
- 調用?
vCode.write(response.getOutputStream())
?將生成的圖片寫入 HTTP 響應流,返回給前端顯示。
- 調用?
為了輔助上述方法,因此創建了一個圖形驗證碼生成工具類??(CreateImageCode
)
package com.cjl.entity.dto;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;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(){createCode();}public CreateImageCode(int width, int height, int codeCount, int lineCount){this.width=width;this.height=height;this.codeCount=codeCount;this.lineCount=lineCount;createCode();}public CreateImageCode(int width, int height, int lineCount){this.width=width;this.height=height;this.lineCount=lineCount;createCode();}public CreateImageCode(int width, int height){this.width=width;this.height=height;createCode();}//生成圖片private void createCode(){int fontWidth=width/codeCount; //字體寬度int fontHeight=height-5; //字體高度int codeY=height-8; //驗證碼y坐標//創建圖像bufferbuffImg=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);Graphics g=buffImg.getGraphics();//設置背景顏色g.setColor(getRandColor(200,250));g.fillRect(0,0,width,height);//設置字體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是坐標,基于要畫的東西最左側字符的基線g.drawString(strRand,i*fontWidth+3,codeY);}}//得到隨機字符private String RandomStr(int n){String str1="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";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 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/period+ (6.2831853071795862D* (double)phase/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();}
}
最終達到的效果:
4、創建郵箱數據庫
依舊通過java生成器生成,不過,第一次遇見這種:
<!-- 通用查詢結果列--><sql id="base_column_list">e.email,e.code,e.creat_time,e.status</sql><sql id="base_condition_filed"><if test="query.email != null and query.email!=''">and e.email = #{query.email}</if><if test="query.code != null and query.code!=''">and e.code = #{query.code}</if><if test="query.creatTime != null and query.creatTime!=''"><![CDATA[ and e.creat_time=str_to_date(#{query.creatTime}, '%Y-%m-%d') ]]></if><if test="query.status != null">and e.status = #{query.status}</if></sql><!-- 通用條件列--><sql id="base_condition"><where><include refid="base_condition_filed" /></where></sql><!-- 通用查詢條件列--><sql id="query_condition"><where><include refid="base_condition_filed" /><if test="query.emailFuzzy!= null and query.emailFuzzy!=''">and e.email like concat('%', #{query.emailFuzzy}, '%')</if><if test="query.codeFuzzy!= null and query.codeFuzzy!=''">and e.code like concat('%', #{query.codeFuzzy}, '%')</if><if test="query.creatTimeStart!= null and query.creatTimeStart!=''"><![CDATA[ and e.creat_time>=str_to_date(#{query.creatTimeStart}, '%Y-%m-%d') ]]></if><if test="query.creatTimeEnd!= null and query.creatTimeEnd!=''"><![CDATA[ and e.creat_time< date_sub(str_to_date(#{query.creatTimeEnd},'%Y-%m-%d'),interval -1 day) ]]></if></where></sql>
?<sql id="base_column_list"> ???我很好奇了這是什么
通過?<sql>
?標簽定義可重用的 SQL 片段,并通過?<include>
?標簽引用這些片段,從而提高代碼的復用性和可維護性。
當 MyBatis 啟動時,會解析這些?<sql>
?片段并存儲在內存中,形成可重用的 SQL 模板。
1. ??片段注冊??
base_column_list
?→ 字段列表模板base_condition_filed
?→ 基礎條件模板base_condition
?→ 完整 WHERE 條件(直接引用?base_condition_filed
)query_condition
?→ 擴展 WHERE 條件(引用?base_condition_filed
?并追加模糊/范圍查詢)
2.邏輯關系
這段代碼的核心思想就是 ??將常用的 SQL 片段拆解成可復用的模塊??,通過 MyBatis 的?<sql>
?和?<include>
?機制實現 ??邏輯復用?? 和 ??動態拼接??。但它不僅僅是簡單的“代碼片段集合”,而是一種 ??模塊化 SQL 設計模式??
可能跟我一樣第一次遇見的同學看見這里就有點蒙圈了,沒事,我們來舉例?一個簡單的代碼來理解:
1. 定義可復用的 SQL 片段(樂高積木塊)
<!-- 基礎車體(相當于字段列表) -->
<sql id="base_car_body">id, brand, model, color
</sql><!-- 基礎輪子(相當于基礎條件) -->
<sql id="base_wheels"><if test="wheelSize != null">AND wheel_size = #{wheelSize}</if>
</sql>
2.組裝小車(簡單查詢)
<select id="selectBasicCar" resultType="Car">SELECT <include refid="base_car_body"/> <!-- 插入車體 -->FROM cars<where><include refid="base_wheels"/> <!-- 插入輪子條件 --></where>
</select>
3.生成的SQL??(當 wheelSize=18 時):
SELECT id, brand, model, color
FROM cars
WHERE wheel_size = 18
這樣是不是就好理解一點了!!
5、實現發送郵箱驗證碼接口?
就在主播寫這個接口的發送郵箱驗證的時候,命名mappers互相跳轉都沒有問題,困擾我一個多小時,結果發現沒有在??屬性配置文件添加mybatis.mapper-locations=classpath:mappers/*.xml,因此雖然我可以互相跳轉,但是代碼自己找不到sql映射,屬所以說寫代碼還是要規范
言歸正傳,
當我們寫完這個接口,我們要考慮很多因素,首先就是用戶輸入的驗證碼是不是正確的,要根據之前定義的checkCode方法來查看,其次就要考慮status的問題,如果賬戶已經被注冊了就沒有必要了要進行判斷,不僅如此,當用戶多次點擊發送的時候,我們只需要禁用之前的驗證碼,這個如何實現?
答案是在service層
@Update("update email_code set status=1 where email=#{email} and status=0")
為什么這樣就可以?我們來分析,當檢測到用戶未注冊的時候,代碼的流程走到這里來的時候,此時他的status就會變成1,也就是說每次發送新驗證碼前,會先執行?disableEmailCode
?方法,將所有該郵箱未使用的驗證碼(status=0
)標記為被使用了已禁用(status=1
)。因此能保證最晚(最新)的那一個驗證碼才會生效
我們現在來發送郵件,???JavaMailSender??(Spring框架提供的郵件發送工具)來創建并發送一封簡單的電子郵件:
流程:
1.入口??:調用sendMailCode(email, code)
方法,傳入收件人郵箱和驗證碼
2.獲取系統郵件模板
SysSettingDto sysSettingDto = redisComponent.getSysSetting();
設置郵件發送的格式:
?
3.構建郵件內容
- ??標題處理??:直接使用系統配置的標題
helper.setSubject(sysSettingDto.getRegisterMailTitle());
- ??內容格式化??:將驗證碼插入模板
String.format("您好,您的郵箱驗證碼為:%s,15分鐘有效", "A1B2C3")
提問:有人就好奇了,redis一開始是空的哪來的模板??
答案是?
1. 嘗試從Redis讀取(此時返回null),發現為空時,創建默認配置
最后保存到Redis(無過期時間)?
整個發郵件的大致過程就是這樣,最后用戶會收到: