国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

基于數據庫鎖實現防重復提交

數據庫
今天介紹如何結合數據庫的悲觀鎖或樂觀鎖來實現對請求的有效驗證,確保同一操作不會被重復執行。

在 Web 應用開發中,重復提交問題是一個常見的挑戰。當用戶由于網絡延遲、誤操作等原因,多次點擊提交按鈕時,可能會導致相同的數據被多次插入到數據庫中,從而引發數據一致性問題。

為了解決這個問題,我們可以采用 token 機制,大多數實現是基于Redis實現,今天介紹如何結合數據庫的悲觀鎖或樂觀鎖來實現對請求的有效驗證,確保同一操作不會被重復執行。

一、Token 機制原理

Token 機制的核心思想是在用戶訪問頁面時,后端服務生成一個唯一的 token,并返回給前端。前端在用戶提交請求時,將這個 token 一并發送到后端。后端接收到請求后,驗證該 token 是否有效,即是否已經被使用過。如果 token 未被使用過,則處理此次請求,并將該 token 標記為已使用;如果 token 已被使用過,則判定為重復提交,拒絕處理此次請求。

二、實現

1. 使用數據庫悲觀鎖驗證 Token

悲觀鎖認為數據在被訪問時很可能被其他事務修改,因此在獲取數據時就對其加鎖,防止其他事務對其進行修改。在驗證 token 時,我們可以利用悲觀鎖來確保在同一時刻只有一個事務能夠處理帶有特定 token 的請求。

假設我們有一個token_info表,用于存儲 token 信息,表結構如下:

CREATE TABLE token_info (
    id INT AUTO_INCREMENT PRIMARY KEY,
    token VARCHAR(255) NOT NULL UNIQUE,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_token (token)
);

驗證 token 的 SQL 語句及相關代碼如下:

public interface TokenMapper {
    // 插入Token到數據庫
    @Insert("INSERT INTO token_info (token) VALUES (#{token})")
    int insertToken(String token);

    // 使用悲觀鎖查詢Token
    @Select("SELECT * FROM token_info WHERE token = #{token} FOR UPDATE")
    Token selectTokenForUpdate(String token);

    // 刪除Token
    @Delete("DELETE FROM token_info WHERE token = #{token}")
    int deleteToken(String token);
}

Token的Service層,處理業務邏輯:

@Service
public class TokenService {
    @Autowired
    private TokenMapper tokenMapper;

    // 生成Token并存儲到數據庫
    public String generateToken() {
        String token = UUID.randomUUID().toString();
        tokenMapper.insertToken(token);
        return token;
    }

    // 驗證Token的有效性
    @Transactional
    public boolean validateToken(String token) {
        Token dbToken = tokenMapper.selectTokenForUpdate(token);
        if (dbToken != null) {
            tokenMapper.deleteToken(token);
            return true;
        }
        return false;
    }
}

在控制器中使用服務層方法進行驗證:

@RestController
public class SubmissionController {

    private final TokenService tokenService;

    public SubmissionController(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @PostMapping("/submit")
    public String submit(@RequestParam String token) {
        if (tokenService.validateToken(token)) {
            // 處理正常的提交邏輯
            return "提交成功";
        } else {
            return "重復提交,請求被拒絕";
        }
    }
}

2. 使用數據庫樂觀鎖驗證 Token

樂觀鎖認為數據在被訪問時很少會被其他事務修改,因此在獲取數據時不會對其加鎖,而是在更新數據時檢查數據是否被其他事務修改過。在驗證 token 時,我們可以通過版本號來實現樂觀鎖機制。

首先,修改token_info表結構,添加一個版本號字段version:

CREATE TABLE token_info (
    id INT AUTO_INCREMENT PRIMARY KEY,
    token VARCHAR(255) NOT NULL UNIQUE,
    version INT DEFAULT 0,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_token (token)
);

驗證 token 的 SQL 語句及相關代碼如下:

// Token 的 Mapper 接口,定義與數據庫交互的方法
public interface TokenMapper {
    // 插入 Token 到數據庫
    @Insert("INSERT INTO token_info (token, version) VALUES (#{token}, 0)")
    int insertToken(String token);

    // 查詢 Token 及其版本號
    @Select("SELECT * FROM token_info WHERE token = #{token}")
    Token selectToken(String token);

    // 使用樂觀鎖更新 Token 的版本號
    @Update("UPDATE token_info SET version = version + 1 WHERE token = #{token} AND version = #{version}")
    int updateTokenVersion(@Param("token") String token, @Param("version") Integer version);

    // 刪除 Token
    @Delete("DELETE FROM token_info WHERE token = #{token}")
    int deleteToken(String token);
}

Token的Service層,處理業務邏輯:

@Service
public class TokenService {
    @Autowired
    private TokenMapper tokenMapper;

    // 生成 Token 并存儲到數據庫
    public String generateToken() {
        String token = UUID.randomUUID().toString();
        tokenMapper.insertToken(token);
        return token;
    }

    // 驗證 Token 的有效性
    //token用完即刪除,新的token版本號永遠為0,也可以不查詢庫默認0
    @Transactional
    public boolean validateToken(String token) {
        Token dbToken = tokenMapper.selectToken(token);
        if (dbToken != null) {
            int rowsAffected = tokenMapper.updateTokenVersion(dbToken.getToken(), dbToken.getVersion());
            if (rowsAffected > 0) {
                // 驗證成功,刪除 Token
                tokenMapper.deleteToken(token);
                return true;
            }
        }
        return false;
    }
}

在控制器中使用服務層方法進行驗證:

@RestController
public class SubmissionController {

    private final TokenService tokenService;

    public SubmissionController(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @PostMapping("/submit")
    public String submit(@RequestParam String token) {
        if (tokenService.validateToken(token)) {
            // 處理正常的提交邏輯
            return "提交成功";
        } else {
            return "重復提交,請求被拒絕";
        }
    }
}

3. Redis

@Slf4j
@Service
public class TokenUtilService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 存入 Redis 的 Token 鍵的前綴
     */
    private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent_token:";

    /**
     * 創建 Token 存入 Redis,并返回該 Token
     *
     * @param value 用于輔助驗證的 value 值
     * @return 生成的 Token 串
     */
    public String generateToken(String value) {
        // 實例化生成 ID 工具對象
        String token = UUID.randomUUID().toString();
        // 設置存入 Redis 的 Key
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        // 存儲 Token 到 Redis,且設置過期時間為5分鐘
        redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
        // 返回 Token
        return token;
    }

    /**
     * 驗證 Token 正確性
     *
     * @param token token 字符串
     * @param value value 存儲在Redis中的輔助驗證信息
     * @return 驗證結果
     */
    public boolean validToken(String token, String value) {
        // 設置 Lua 腳本,其中 KEYS[1] 是 key,KEYS[2] 是 value
        String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        // 根據 Key 前綴拼接 Key
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        // 執行 Lua 腳本
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value));
        // 根據返回結果判斷是否成功成功匹配并刪除 Redis 鍵值對,若果結果不為空和0,則驗證通過
        if (result != null && result != 0L) {
            log.info("驗證 token={},key={},value={} 成功", token, key, value);
            return true;
        }
        log.info("驗證 token={},key={},value={} 失敗", token, key, value);
        return false;
    }
}

三、最后

通過使用 token 機制結合數據庫的悲觀鎖或樂觀鎖,我們可以有效地避免用戶重復提交請求,保證數據的一致性和系統的穩定性。悲觀鎖適用于數據競爭較為激烈的場景,能夠確保數據的完整性,但可能會影響系統的并發性能;樂觀鎖則適用于數據沖突較少的場景,能夠提高系統的并發處理能力,但在數據沖突較多時可能會導致多次重試。在實際應用中,我們需要根據具體的業務場景和數據特點選擇合適的鎖機制來實現 token 驗證邏輯。

責任編輯:趙寧寧 來源: 一安未來
相關推薦

2024-05-28 09:26:46

2024-07-26 07:59:25

2025-02-21 12:00:00

SpringBoot防重復提交緩存機制

2013-04-26 11:39:40

2010-09-30 09:11:01

2010-09-30 08:27:48

2021-03-11 09:53:07

SpringBoot數據庫分布式鎖

2024-05-06 00:00:00

.NET分布式鎖技術

2018-09-11 17:13:23

MySQ數據庫重復記錄

2011-08-18 13:44:42

Oracle悲觀鎖樂觀鎖

2019-12-09 10:03:40

MySQLSQL數據庫

2022-03-29 10:52:08

MySQL數據庫

2024-09-06 11:52:47

2021-01-26 13:40:44

mysql數據庫

2011-05-24 14:13:20

Oracle數據庫

2018-02-27 15:48:31

數據庫SQL鎖死

2010-08-18 09:00:38

數據庫

2018-10-16 16:00:39

數據庫鎖舞MySQL

2018-04-27 13:00:00

數據庫MySQL刪除重復行

2025-06-23 08:55:00

架構Vue重復提交
點贊
收藏

51CTO技術棧公眾號

天天操,天天操| 欧美24videosex性欧美| 国产精品v欧美精品v日本精品动漫| 日韩麻豆第一页| 黄色网在线看| 欧洲生活片亚洲生活在线观看| 免费看的毛片| 国产精品九色蝌蚪自拍| 国产精品99久久免费黑人人妻| 粉嫩久久99精品久久久久久夜| 在线免费观看一区二区三区| 一本色道久久综合| 国产精品制服诱惑| 亚洲欧洲视频| 欧美高清视频一区| 日韩高清在线观看| 亚洲一区三区视频在线观看| 日本成人在线不卡视频| 亚洲成色www久久网站| 男人的j进女人的j一区| 日韩激情视频| 狠狠网亚洲精品| 日本熟妇人妻xxxx| aaa国产一区| 国产v亚洲v天堂无码久久久| 久久网站热最新地址| 91久久精品网| 污网站免费在线观看| 天天综合色天天综合| 在线一级观看| 色94色欧美sute亚洲线路一ni| 国产中文字幕在线| 东京热加勒比无码少妇| 欧美jizz19性欧美| 国产一区二区三区不卡在线观看| 国产精品波多野结衣| 午夜亚洲福利| 美女精品国产| 国产欧美久久久精品影院| 亚洲一区三区电影在线观看| 中文精品在线| 午夜精品久久久久久久99黑人| 黄色网在线播放| 先锋欧美三级| 欧美日韩性视频| 色999韩欧美国产综合俺来也| 亚洲高清不卡av| 色豆豆成人网| 韩国精品久久久999| 欧洲杯半决赛直播| 亚洲在线观看视频网站| 免费看黄色91| www.com毛片| 亚洲高清不卡在线观看| 黄色免费在线网站| 久久九九热免费视频| 色综合天天综合网中文字幕| 日本精品一区二区三区视频 | 韩国三级一区| 国产97色在线|日韩| 亚洲一区成人| www日韩在线观看| 色欧美88888久久久久久影院| 中文字幕不卡三区视频| 欧美性一区二区三区| 中文亚洲字幕| xxx国产在线观看| 日韩欧美亚洲一区二区| 久久视频在线观看| 欧美日韩精品久久久免费观看| 国产日产亚洲精品系列| 黄网站在线播放| 久久久久久久国产精品| 欧美亚洲在线| av在线影视| 亚洲视频第一页| 91九色精品| 欧美 日韩 国产一区| 精品1区2区3区| 国内露脸中年夫妇交换精品| 亚洲电影网站| 欧美日韩在线视频一区二区| 国产日韩精品在线观看| 久久激情网站| 中文字幕理伦片免费看| 欧美激情2020午夜免费观看| 精品国产aⅴ| 久久久久久久久久久人体| 欧美亚洲一区| 99免费在线视频观看| 久久综合狠狠综合| 激情成人四房播| 97超碰色婷婷| 国产在线国偷精品产拍免费yy| 理论视频在线观看| 日韩在线视频免费观看| 亚洲少妇自拍| 欧美vide| 欧美激情国内偷拍| 国产一区二区精品久久91| 中文字幕一二三区在线观看| 中文字幕免费国产精品| 日韩亚洲国产精品| 国产a级网站| 久久精品在线视频| 免费观看在线综合| av大片在线看| 成人在线中文字幕| 亚洲精品视频一区二区| 亚洲a成人v| 91免费国产精品| 日韩欧美一区二区不卡| 欧美精品97| 丝袜+亚洲+另类+欧美+变态| 欧美另类交人妖| av午夜一区麻豆| japanese23hdxxxx日韩| 日韩欧美手机在线| 欧美日韩精品是欧美日韩精品| 黑人操亚洲人| 国产一级电影网| 欧美亚洲视频在线观看| 中文字幕精品一区二区精品绿巨人 | 日韩av中文在线观看| 国产精品剧情一区二区在线观看| 精品久久久久久久久久久久久久久 | 精品蜜桃一区二区三区| 密臀av在线播放| 国内精品二区| 91黄色在线观看| 亚洲精品一区二区在线看| 国内精品卡一卡二卡三新区| 日本电影亚洲天堂| 一区二区三区 在线观看视频| 亚洲色图美女| 91se在线观看| 91在线视频免费| 欧美日韩国产一二三| 亚洲一区二区三区四区五区午夜 | 亚洲人成人77777线观看| 555夜色666亚洲国产免| 日韩精品欧美成人高清一区二区| 2024最新电影在线免费观看| 日韩欧美视频一区二区三区四区 | 日韩av片永久免费网站| 亚洲警察之高压线| 成年美女黄网站色大片不卡| 国产精品又粗又长| 97在线观看视频国产| 国产校园另类小说区| 亚洲专区欧美专区| 在线电影福利片| 一区二区精品在线| 少妇激情av一区二区三区| 26uuu日韩精品一区二区| 亚洲欧美一区二区视频| 久草精品在线| 啊v视频在线| 亚洲砖区区免费| 中文字幕日韩免费视频| 欧美国产日本韩| 91精品秘密在线观看| 久久五月精品| 日韩小视频网站| 欧美一级高清免费| 欧美人妇做爰xxxⅹ性高电影| 极品美女销魂一区二区三区免费| 99精品国产九九国产精品| 激情丁香在线| 久久66热这里只有精品| 国产一区二区成人| 一区二区三区中文字幕电影| 先锋影音日韩| 欧美精品在线第一页| 精品电影在线观看| 美日韩一级片在线观看| 成人香蕉社区| 99精品老司机免费视频| 久久www视频| 国产精品黄色av| 日韩欧美国产午夜精品| 久久精品在线观看| 一区二区三区四区五区精品视频| 四虎精品一区二区免费| 青春有你2免费观看完整版在线播放高清 | 欧美特黄色片| 亚洲精品一区二区| 亚洲精品一区二区三区四区五区| 蜜月aⅴ免费一区二区三区| 亚洲国产视频网站| 国产尤物一区二区在线| 欧美午夜精品一区二区三区电影| 999福利在线视频| 特黄特色大片免费视频大全| 一区二区精品在线| 91色在线视频| 久久久久国产精品www| 亚洲成人av在线| 黄色成人在线播放| 日本一区二区三区四区|