天天看點

P2P項目過程中的細點與問題一、多線程二、資料庫相關三、JS相關四、一些問題五、一些操作六、一些功能七、一些約定俗稱其它

文章目錄

  • 一、多線程
    • 1. 緩存擊穿
    • 2. 線程池
      • (1)體系結構
      • (2)Executors
      • (3)ExecutorService
    • 3. 掉單問題
  • 二、資料庫相關
    • 1. cast與decimal
    • 2. 業務邏輯
      • (1)DQL
      • (2)DML
    • 3. resultMap
    • 4. Dao層方法起名字
    • 5*. Datetime時區問題
    • 6. 多表聯查查出的值的存放位置
    • 7. \$()與$(document).ready()
    • *8. 屬性和JS中均定義了某個事件
    • 9. MyBatis逆向工程
    • 10*. 資料庫樂觀鎖機制
    • 11. 資料庫中的日期
  • 三、JS相關
    • 1. 使用正則
    • 2*. $.each()中使用return
    • 3*. $().text()
    • 4. jQuery選取
    • 5. 對象失去焦點
    • 6. JQ中的md5加密
  • 四、一些問題
    • 1. dubbo逾時重試問題
    • 2. 産品超賣問題
    • 3. 訂單不過夜
  • 五、一些操作
    • 1. 随機
    • 3. TODO
    • 4. 脫敏操作
    • 5. 取小數點後兩位
    • 6. 日期格式與整數相加
      • (1)使用Calendar
      • (2)使用Commons-lang3工具包
    • 7. 引入非(本地和中央)倉庫的jar包
    • 8. 生成全局唯一單号
    • 9. 時序圖
    • 10. 背景跳轉頁面
  • 六、一些功能
    • 1. 短信驗證碼進行登入
    • 2. 認證功能
    • 3. 登入後傳回上次停留的頁面
    • 4. 投資排行榜功能
    • 5. 支付寶網頁支付功能
    • 6. 生成簡單的圖檔驗證碼
    • 7. 生成二維碼
  • 七、一些約定俗稱
    • 8. 登出
  • 其它
    • 1. 注解參數
      • (1)@RequestParam
    • 2. maven的繼承
    • 5. jsp中對字元串處理
    • 6. templates檔案夾
    • 可變長參數

一、多線程

1. 緩存擊穿

緩存擊穿,是指查詢一個資料庫一定不存在的資料。正常的使用緩存流程大緻是,資料查詢先進行緩存查詢,如果key不存在或者key已經過期,再對資料庫進行查詢,并把查詢到的對象,放進緩存。如果資料庫查詢對象為空,則不放進緩存。在高并發下,當第一個線程将資料放入緩存中之前,會出現多個線程從資料庫中進行查詢的現象,即緩存穿透

解決方法:雙重判斷加同步代碼塊

固定文法:

if(第一次對值進行判空,為空時繼續){
	synchronized (this){
		對值進行更新
		if(第二次對值進行判空,為空時繼續){
			從資料庫中擷取值
		}
	}
}
           

一個小例子:

public Integer queryAllUserCount() {
    BoundValueOperations<Object, Object> ops = redisTemplate.boundValueOps(Const.ALL_USER_COUNT);
    Integer allUserCount = (Integer) ops.get();

    if (!ObjectUtils.allNotNull(allUserCount)) {
        synchronized (this) {
        	allUserCount = (Integer) ops.get();
        	
            if (!ObjectUtils.allNotNull(allUserCount)) {
                allUserCount = userMapper.queryAllUserCount();
                ops.set(allUserCount);
            }
        }
    }
    return allUserCount;
}
           

2. 線程池

線程池是提供一個線程隊列,隊列中儲存着所有等待狀态的線程。避免了建立與銷毀的額外開銷,提高了響應的速度。

(1)體系結構

線程池的體系結構:

java.util.concurrent.Executor 負責線程的使用和排程的根接口
		|--ExecutorService 子接口: 線程池的主要接口
				|--ThreadPoolExecutor 線程池的實作類
				|--ScheduledExceutorService 子接口: 負責線程的排程
					|--ScheduledThreadPoolExecutor : 繼承ThreadPoolExecutor,實作了ScheduledExecutorService
           

(2)Executors

Executors是一個工具類,用于建立線程池 :

  • newCachedThreadPool

    :用來建立一個可以無限擴大的線程池,适用于伺服器負載較輕,執行很多短期異步任務。
  • newFixedThreadPool

    :建立一個固定大小的線程池,因為采用無界的阻塞隊列,是以實際線程數量永遠不會變化,适用于可以預測線程數量的業務中,或者伺服器負載較重,對目前線程數量進行限制。
  • newSingleThreadExecutor

    :建立一個單線程的線程池,适用于需要保證順序執行各個任務,并且在任意時間點,不會有多個線程是活動的場景。
  • newScheduledThreadPool

    :可以延時啟動,定時啟動的線程池,适用于需要多個背景線程執行周期任務的場景。
  • newWorkStealingPool

    :建立一個擁有多個任務隊列的線程池,可以減少連接配接數,建立目前可用cpu數量的線程來并行執行,适用于大耗時的操作,可以并行來執行

(3)ExecutorService

ExecutorService 對象的一些方法:

  • submit()

    • 送出Callable對象,需要重寫

      call()

      方法,傳回Future
    • 送出Runnable對象,需要重寫

      run()

      方法,傳回Future,
  • shutdown()

    • 關閉線程池

3. 掉單問題

簡單的一個處理:使用定時器去掃描掉單,然後再處理,使用鎖解決定時器和支付同時運作的情況(在支付時(建立了訂單但未支付完成),定時器同時掃描到了該條資料,使用鎖解決沖突)

二、資料庫相關

1. cast與decimal

  • CAST:進行資料類型轉換,AS關鍵字分隔的源值和目标資料類型,前面是源值,後面是目标資料類型
  • DECIMAL:格式化數字(小數點前的資料表示最多顯示的總位數,小數點後的資料表示保留幾位小數)

    例如:求出學生的平均年齡,

select cast(avg(age) as decimal(10,2)) as avgAge from student
           

2. 業務邏輯

(1)DQL

一個查詢語句就是一個完整的業務邏輯,目前台的響應要擷取多個不相關的資料值(不同表或者沒有關系的資料)的時候,在Controller層要多次調用相應的Service層,分别查出這些不相關的資料。

(2)DML

在Controller層,對于DML,要調用完整的業務,有時候是多個DML語言是一個業務。這時候給方法起的名字一般在控制層是業務名稱,在業務層再細化成各個操作的名字

3. resultMap

MyBatis逆向工程生成的 mapper.xml 中使用了

<mapper>

标簽,如果查詢對應的實體類,則在 标簽中使用 resultMap屬性指定與實體類的映射關系,因為可能存在表中的字段與實體類的字段不同的情況

4. Dao層方法起名字

Dao層方法的名字要細化,以便方法可以複用

5*. Datetime時區問題

java中使用Date對象往MySQL資料庫中存儲 Datetime 類型的日期,總是少加個時區。

原因:指定資料庫的url時,有個參數

serverTimezone

是用來指定時區的,而我寫的是UTC(世界标準時間),将UTC改為

CTT

(Asia&Shanghai)即可。

spring.datasource.url=jdbc:mysql://localhost:3306/p2p?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=CTT
           

6. 多表聯查查出的值的存放位置

可以在含有外鍵的表對應的實體類中,添加外鍵對應字段,該字段是外鍵對應的表的實體類

例如:

學生表和班級表:學生表中存放有班級表的外鍵,當多表聯查時,可以在學生類中添加一個班級類的字段,在mapper.xml檔案中,使用

<resultMap>

标簽和

<association>

标簽來查出的值與實體類字段進行映射

<resultMap id="BaseResultMap" type="com.wkcto.springboot.model.Student">
    <id column="id" jdbcType="INTEGER" property="id"/>
    <result column="name" jdbcType="VARCHAR" property="name"/>
    <result column="age" jdbcType="INTEGER" property="age"/>
    <association property="classRoom" javaType="com.wkcto.springboot.model.ClassRoom">
    	<id column="id" jdbcType="INTEGER" property="id"/>
		<result column="name" jdbcType="VARCHAR" property="name"/>
    </association>
</resultMap>
           

7. $()與$(document).ready()

這兩個函數的功能是等價的,都是在頁面加載完畢後再執行

$(function(){})
//這兩個方法是等價的,都是在頁面加載完畢後,然後執行
$(document).ready(function(){})	//在文檔加載後激活函數

           

*8. 屬性和JS中均定義了某個事件

當html标簽的屬性和JS中均定義了某個相同的事件,則隻會觸發屬性中定義的事件

9. MyBatis逆向工程

使用MyBatis逆向工程,推薦字段中分隔單詞使用下劃線"

_

",則逆向工程生成的實體類,會自動把下劃線去掉,并轉換為駝峰命名法,是以,要使用

<resultMap>

标簽來将表中的字段與實體類中的字段一一映射起來

10*. 資料庫樂觀鎖機制

實作:基于資料版本(Version)記錄機制實作

具體可通過給表加一個版本号或時間戳字段實作,當讀取資料時,将version字段的值一同讀出,資料每更新一次,對此version值加一。當我們送出更新的時候,判斷目前版本資訊與第一次取出來的版本值大小,如果資料庫表目前版本号與第一次取出來的version值相等,則予以更新,否則認為是過期資料,拒絕更新,讓使用者重新操作。

11. 資料庫中的日期

current_date:隻顯示日期,2020-01-01

current_time:隻顯示時間,23:11:49

current_timestamp:顯示日期加時間,2020-01-01 23:11:49

三、JS相關

1. 使用正則

常用的正規表達式:https://editor.csdn.net/md?articleId=103735776

在JS中使用正規表達式:

  • 文法:

    /正規表達式/.test(字元串)

    ,如果比對則傳回true,否則傳回false

2*. $.each()中使用return

jquery 的 each 方法中如果使用了

return true

或者

return

相當于是

continue

,而

return false

相當于是

break

。并不會使方法結束

3*. $().text()

$().text()

會将$() 中所有取出的DOM元素中的文本内容進行拼接,而其它的方法如

$().html()

隻會對第一個DOM元素進行操作

4. jQuery選取

  • $("div[class$=Err]")

    :選取所有class屬性以Err結尾的div标簽
  • $("div[class^=pro]")

    :選取所有class屬性以pro開頭的div标簽

5. 對象失去焦點

  • 為 id 為 input 的标簽綁定失去焦點事件:
  • 觸發 id 為 input 的标簽的失去焦點事件:

6. JQ中的md5加密

四、一些問題

1. dubbo逾時重試問題

在IDEA中進行debug時,因為發生了逾時,dubbo自動進行重試,會多次發送請求,如果剛好是往資料庫中插入資料,而且還不是在Service層的事務中,那麼可能會發生往資料庫中重複插入多條相同的資料問題(主鍵自增)

2. 産品超賣問題

使用資料庫樂觀鎖解決

3. 訂單不過夜

生成訂單,如果不付款,會在一定時間内過期 ,比如說在15分鐘内過期,但是如果是在23點55分生成的訂單,則會在 0 點的時候過期,即時間并不夠15分鐘

五、一些操作

1. 随機

将要随機生成的元素存放在數組中,可以是任何元素,然後使用

Math.Random()*array.length

來随機生成數組的下标

注意:java中預設小數轉為整數是去掉小數點後的部分,使用

Math.round(num)

可以将小數四舍五入進行取整

3. TODO

在IDEA中使用TODO來記錄自己還沒有完成的功能

4. 脫敏操作

脫敏操作是指:在頁面中,将敏感的資料變得不敏感,比如金額,手機号等,使用

***

來對原資料進行一定修改,進行脫敏操作

5. 取小數點後兩位

  1. 各種語言通用的一種思路:将小數乘以100,再取整,再将整數除以100
  2. java:使用DecimalFormat對象

    0:代表一個數字,如果不存在顯示0

    #:代表一個或多個數字,如果不存在則顯示為空

    DecimalFormat decimalFormat = new DecimalFormat("#.00");
    Double d = Double.valueOf(decimalFormat.format(13.13521));//
               
  3. js:toFixed(四舍五入保留的位數):
    var num = new Number(12.3863);
    document.write(num.toFixed(2));//輸入出:12.39
               

6. 日期格式與整數相加

(1)使用Calendar

主要方法:

  • add(field, amount)

    • field:int類型,指定要加的整數的機關,是常量,Calendar.DATE表示天,
    • amount:int類型,指定數量
public static Date getDateByAddDays(Date date, Integer count) {
    //日期處理類對象
    Calendar instance = Calendar.getInstance();

    //設定日期處理類對象的日期值
    instance.setTime(date);

    //在指定日期上添加天數
    instance.add(Calendar.DATE, count);
    
    return instance.getTime();
}
           

(2)使用Commons-lang3工具包

直接使用 DateUtils 的相關函數即可,例如:

  • DateUtils.addDays(Date date, int amount)

    :在給定的日期上加上指定的天數
  • DateUtils.addMonths(Date date, int amount)

    :在給定的上期上加上指定的月數

7. 引入非(本地和中央)倉庫的jar包

  • groupId/artifactId/version都可以随便填,但是不能有重複
  • <scope>

    标簽指定檔案的位置,一般為system
  • 使用

    <systemPath>

    标簽指定本地jar包的位置
  • ${basedir}

    是項目的根目錄
<dependency>
	<groupId>com.alipay.sdk</groupId>
	<artifactId>alipay-sdk-java</artifactId>
	<version>1.0</version>
	<scope>system</scope>
	<systemPath>${basedir}/src/main/webapp/WEB-INF/lib/alipay-sdk-java20170324180803.jar</systemPath>
</dependency>
           

8. 生成全局唯一單号

  1. 可以使用時間戳加上 redis 全局唯一數字
String s1 = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
Long l2 = redisTemplate.opsForValue().increment("onlyNum", 1L);
String result = s1 + l2;
           
  1. 使用電話号碼加上 redis 全局唯一數字(将上面的時間戳換成電話号即可)

9. 時序圖

要會看時序圖,會畫時序圖,對于複雜的業務邏輯,可以畫個時序圖,使用Rational工具

實線是請求,虛線是響應

ctrl + d 是删除元件

10. 背景跳轉頁面

從背景通路其它網站的頁面,比如說支付寶的頁面,可以使用請求轉發,但是請求轉發隻能是get請求方式,請求參數都在位址欄中,這時候可以采用如下的方式:

  • 先跳轉到一個自己網站中的一個頁面
  • 在該頁面中使用from表單内加隐藏域,并使用js在頁面加載完畢後自動送出的方式實作get轉post請求

例如:

<form method="post" action="http://localhost:9094/pay/api/alipay">
    <input type="hidden" name="out_trade_no" th:value="${out_trade_no}">
    <input type="hidden" name="total_amount" th:value="${total_amount}">
    <input type="hidden" name="subject" th:value="${subject}">
</form>

<script>document.forms[0].submit()</script>
           

六、一些功能

1. 短信驗證碼進行登入

随機生成的驗證碼存放在redis中,鍵是手機号,值是驗證碼

主要的實作過程:https://blog.csdn.net/zyx1260168395/article/details/103747807

2. 認證功能

當隻更新某個字段時,實體類中隻需傳入需要更新的字段即可,即便是實體類中的字段的值和資料庫中的值相同,沒必要将其傳入,因為雖然值一樣,但還是更新了一遍資料庫,速度慢

比如認證時要更新使用者的姓名和身份證号

思路一:從可以用從Session中取出的user(含有許多資訊),加入身份證号和姓名,再傳入dao層進行更新,但是雖然隻用到了實體類中的id,姓名和身份證号,但是user表中其它的字段也更新了一遍,會造成速度緩慢,是以使用思路二

思路二:建立一個User,隻加入需要改的字段即可

3. 登入後傳回上次停留的頁面

思路:跳往登入頁時,在前端擷取目前頁的網址,當作請求參數傳遞到背景,然後跳到登入頁後,存在登入頁的一個隐藏域中,當登入成功時,擷取到這個隐藏域中的位址,再跳到這個位址

擷取目前頁的網址(Thymeleaf):

主要屬性:

  • #httpServletRequest.requestURL

    :擷取目前頁面的URL
  • #httpServletRequest.queryString

    :擷取目前頁面的請求參數,為null時省略
var redirectUrl = [[${#httpServletRequest.requestURL + (#httpServletRequest.queryString == null? "": "?" + #httpServletRequest.queryString)}]];
//這兩種方法均可
var redirectUrl = [[${#strings.replace(#httpServletRequest.requestURL + '?' + #httpServletRequest.queryString,"?null","")}]];
           

4. 投資排行榜功能

使用redis中的zset集合(zset可以用來解決各種排行榜問題),key為電話号碼,score為累計投資金額,使用者每投資一筆,就往score中加一筆。

使用到的方法:

incrementScore(K, V, delta)

:元素分數增加,delta是增量

rangeWithScores(K,start,end)

:鍵為K的集合,索引start<=index<=end的元素子集,傳回泛型接口(包括score和value),正序

reverseRangeWithScores(K,start,end)

:鍵為K的集合,索引start<=index<=end的元素子集,傳回泛型接口(包括score和value),倒序

ZSetOperations 操作解釋:https://www.cnblogs.com/pqy521/p/7009620.html

5. 支付寶網頁支付功能

要學會看API文檔,支付寶文檔:https://docs.open.alipay.com/catalog

6. 生成簡單的圖檔驗證碼

@RequestMapping(value = "/jcaptcha/captcha")
public void handleCaptchaRequest(HttpServletRequest request, HttpServletResponse response) {
    //生成6位随機驗證碼,這裡就簡單的使用一個固定的字元串代替了
    String captcha = "1Ag5Kw";
    try {
        //建立位元組數組輸出流
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
        //建立圖檔緩存對象,BufferedImage.TYPE_INT_RGB : 表示一個圖像,該圖像具有整數像素的 8 位 RGB 顔色
        BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        //擷取圖檔的畫布
        Graphics graphics = bufferedImage.getGraphics();
        //設定畫布背景色
        graphics.setColor(Color.GREEN);
        //設定畫布填充區域
        graphics.fillRect(0, 0, WIDTH, HEIGHT);
        //邊框區域
        graphics.drawRect(1, 1, WIDTH - 2, HEIGHT - 2);
        //設定字型顔色
        graphics.setColor(Color.red);
        //設定字型樣式
        graphics.setFont(new Font("微軟雅黑", Font.ITALIC, 32));
        //填充資料
        graphics.drawString(captcha, 10, 38);
        //将生成的驗證碼存放到session中
        request.getSession().setAttribute(Constants.CAPTCHA, captcha);

        ImageIO.write(bufferedImage, "jpeg", jpegOutputStream);
        byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
        //将驗證碼輸出到頁面
        response.setHeader("Cache-Control", "no-store");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0L);
        response.setContentType("image/jpeg");
        ServletOutputStream respOs = response.getOutputStream();
        respOs.write(captchaChallengeAsJpeg);
        respOs.flush();
        respOs.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
           

7. 生成二維碼

使用的是Google生成二維碼的依賴:

<!-- google生成二維碼依賴 -->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.0.0</version>
</dependency>
           

生成二維碼:

Map<EncodeHintType,Object> map = new HashMap<EncodeHintType, Object>();

//設定字元編碼
map.put(EncodeHintType.CHARACTER_SET, "UTF-8");

/*建立一個二維碼,
第一個參數是二維碼内容,
第二個參數是常量,二維碼的編碼規則,
第三、四個參數是二維碼的長和寬,
最後一個參數是設定的一些參數
*/
BitMatrix encode = new MultiFormatWriter().encode("https://blog.csdn.net/zyx1260168395", BarcodeFormat.QR_CODE, 200, 200, map);

//将二維碼轉換成圖檔,寫到指定的路徑上
Path path = FileSystems.getDefault().getPath("D://", "grcde.jpg");
MatrixToImageWriter.writeToPath(encode, "jpg", path);

//在浏覽器使用輸出流來将二維碼圖檔響應到前台
OutputStream out = response.getOutputStream();
MatrixToImageWriter.writeToStream(encode, "jpg", out);

           

七、一些約定俗稱

業務層:session;biz

jar包名字中有 source 的是源碼包,不帶 source 的是編譯好的jar包

用例:測試中用的功能點(用例圖,測試用例)

商戶系統:開發者的系統

管道、終端:

code:通信辨別,10000表示通信成功

請求/響應參數又叫請求/響應封包

timestamp:時間戳

sign:簽名

out_trade_no:商戶訂單号

8. 登出

@RequestMapping("/logout")
public Object logout(HttpServletRequest request) {
    HttpSession session = request.getSession();

    session.invalidate();

    return "redirect:/";
}
           

其它

1. 注解參數

當注解中的參數隻有一個 value 屬性時,可以省略value不寫,當注解中有多個參數時,每個參數都必須指定屬性名。

(1)@RequestParam

将請求參數綁定到你控制器的方法參數上(是springmvc中接收普通參數的注解)

屬性:

  • value

    :參數名
  • required

    :是否包含該參數,預設為true,表示該請求路徑中必須包含該參數,如果不包含就報錯。
  • defaultValue

    :預設參數值,如果設定了該值,required=true将失效,自動為false,如果沒有傳該參數,就使用預設

2. maven的繼承

父模版中的build标簽中的内容是不能被子子產品繼承的

5. jsp中對字元串處理

使用

fn

标簽:https://www.cnblogs.com/evolcq/p/3688443.html?utm_source=tuicool&utm_medium=referral

6. templates檔案夾

templates檔案夾中的檔案是受保護的,必須要通過背景才能進行通路

可變長參數

可變長參數可以傳遞多個值,也可以傳遞數組(類型要對)