自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基于數(shù)據(jù)庫鎖實(shí)現(xiàn)防重復(fù)提交

數(shù)據(jù)庫
今天介紹如何結(jié)合數(shù)據(jù)庫的悲觀鎖或樂觀鎖來實(shí)現(xiàn)對(duì)請(qǐng)求的有效驗(yàn)證,確保同一操作不會(huì)被重復(fù)執(zhí)行。

在 Web 應(yīng)用開發(fā)中,重復(fù)提交問題是一個(gè)常見的挑戰(zhàn)。當(dāng)用戶由于網(wǎng)絡(luò)延遲、誤操作等原因,多次點(diǎn)擊提交按鈕時(shí),可能會(huì)導(dǎo)致相同的數(shù)據(jù)被多次插入到數(shù)據(jù)庫中,從而引發(fā)數(shù)據(jù)一致性問題。

為了解決這個(gè)問題,我們可以采用 token 機(jī)制,大多數(shù)實(shí)現(xiàn)是基于Redis實(shí)現(xiàn),今天介紹如何結(jié)合數(shù)據(jù)庫的悲觀鎖或樂觀鎖來實(shí)現(xiàn)對(duì)請(qǐng)求的有效驗(yàn)證,確保同一操作不會(huì)被重復(fù)執(zhí)行。

一、Token 機(jī)制原理

Token 機(jī)制的核心思想是在用戶訪問頁面時(shí),后端服務(wù)生成一個(gè)唯一的 token,并返回給前端。前端在用戶提交請(qǐng)求時(shí),將這個(gè) token 一并發(fā)送到后端。后端接收到請(qǐng)求后,驗(yàn)證該 token 是否有效,即是否已經(jīng)被使用過。如果 token 未被使用過,則處理此次請(qǐng)求,并將該 token 標(biāo)記為已使用;如果 token 已被使用過,則判定為重復(fù)提交,拒絕處理此次請(qǐng)求。

二、實(shí)現(xiàn)

1. 使用數(shù)據(jù)庫悲觀鎖驗(yàn)證 Token

悲觀鎖認(rèn)為數(shù)據(jù)在被訪問時(shí)很可能被其他事務(wù)修改,因此在獲取數(shù)據(jù)時(shí)就對(duì)其加鎖,防止其他事務(wù)對(duì)其進(jìn)行修改。在驗(yàn)證 token 時(shí),我們可以利用悲觀鎖來確保在同一時(shí)刻只有一個(gè)事務(wù)能夠處理帶有特定 token 的請(qǐng)求。

假設(shè)我們有一個(gè)token_info表,用于存儲(chǔ) token 信息,表結(jié)構(gòu)如下:

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)
);

驗(yàn)證 token 的 SQL 語句及相關(guān)代碼如下:

public interface TokenMapper {
    // 插入Token到數(shù)據(jù)庫
    @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層,處理業(yè)務(wù)邏輯:

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

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

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

在控制器中使用服務(wù)層方法進(jìn)行驗(yàn)證:

@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 "重復(fù)提交,請(qǐng)求被拒絕";
        }
    }
}

2. 使用數(shù)據(jù)庫樂觀鎖驗(yàn)證 Token

樂觀鎖認(rèn)為數(shù)據(jù)在被訪問時(shí)很少會(huì)被其他事務(wù)修改,因此在獲取數(shù)據(jù)時(shí)不會(huì)對(duì)其加鎖,而是在更新數(shù)據(jù)時(shí)檢查數(shù)據(jù)是否被其他事務(wù)修改過。在驗(yàn)證 token 時(shí),我們可以通過版本號(hào)來實(shí)現(xiàn)樂觀鎖機(jī)制。

首先,修改token_info表結(jié)構(gòu),添加一個(gè)版本號(hào)字段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)
);

驗(yàn)證 token 的 SQL 語句及相關(guān)代碼如下:

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

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

    // 使用樂觀鎖更新 Token 的版本號(hào)
    @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層,處理業(yè)務(wù)邏輯:

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

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

    // 驗(yàn)證 Token 的有效性
    //token用完即刪除,新的token版本號(hào)永遠(yuǎn)為0,也可以不查詢庫默認(rèn)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) {
                // 驗(yàn)證成功,刪除 Token
                tokenMapper.deleteToken(token);
                return true;
            }
        }
        return false;
    }
}

在控制器中使用服務(wù)層方法進(jìn)行驗(yàn)證:

@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 "重復(fù)提交,請(qǐng)求被拒絕";
        }
    }
}

3. Redis

@Slf4j
@Service
public class TokenUtilService {

    @Autowired
    private StringRedisTemplate redisTemplate;

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

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

    /**
     * 驗(yàn)證 Token 正確性
     *
     * @param token token 字符串
     * @param value value 存儲(chǔ)在Redis中的輔助驗(yàn)證信息
     * @return 驗(yàn)證結(jié)果
     */
    public boolean validToken(String token, String value) {
        // 設(shè)置 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);
        // 根據(jù) Key 前綴拼接 Key
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        // 執(zhí)行 Lua 腳本
        Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value));
        // 根據(jù)返回結(jié)果判斷是否成功成功匹配并刪除 Redis 鍵值對(duì),若果結(jié)果不為空和0,則驗(yàn)證通過
        if (result != null && result != 0L) {
            log.info("驗(yàn)證 token={},key={},value={} 成功", token, key, value);
            return true;
        }
        log.info("驗(yàn)證 token={},key={},value={} 失敗", token, key, value);
        return false;
    }
}

三、最后

通過使用 token 機(jī)制結(jié)合數(shù)據(jù)庫的悲觀鎖或樂觀鎖,我們可以有效地避免用戶重復(fù)提交請(qǐng)求,保證數(shù)據(jù)的一致性和系統(tǒng)的穩(wěn)定性。悲觀鎖適用于數(shù)據(jù)競(jìng)爭(zhēng)較為激烈的場(chǎng)景,能夠確保數(shù)據(jù)的完整性,但可能會(huì)影響系統(tǒng)的并發(fā)性能;樂觀鎖則適用于數(shù)據(jù)沖突較少的場(chǎng)景,能夠提高系統(tǒng)的并發(fā)處理能力,但在數(shù)據(jù)沖突較多時(shí)可能會(huì)導(dǎo)致多次重試。在實(shí)際應(yīng)用中,我們需要根據(jù)具體的業(yè)務(wù)場(chǎng)景和數(shù)據(jù)特點(diǎn)選擇合適的鎖機(jī)制來實(shí)現(xiàn) token 驗(yàn)證邏輯。

責(zé)任編輯:趙寧寧 來源: 一安未來
相關(guān)推薦

2024-05-28 09:26:46

2024-07-26 07:59:25

2025-02-21 12:00:00

SpringBoot防重復(fù)提交緩存機(jī)制

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數(shù)據(jù)庫分布式鎖

2024-05-06 00:00:00

.NET分布式鎖技術(shù)

2018-09-11 17:13:23

MySQ數(shù)據(jù)庫重復(fù)記錄

2022-03-29 10:52:08

MySQL數(shù)據(jù)庫

2019-12-09 10:03:40

MySQLSQL數(shù)據(jù)庫

2011-08-18 13:44:42

Oracle悲觀鎖樂觀鎖

2024-09-06 11:52:47

2011-05-24 14:13:20

Oracle數(shù)據(jù)庫

2021-01-26 13:40:44

mysql數(shù)據(jù)庫

2018-10-16 16:00:39

數(shù)據(jù)庫鎖舞MySQL

2018-04-27 13:00:00

數(shù)據(jù)庫MySQL刪除重復(fù)行

2018-02-27 15:48:31

數(shù)據(jù)庫SQL鎖死

2009-06-30 09:16:45

數(shù)據(jù)庫存儲(chǔ)JSP文件

2010-08-18 09:00:38

數(shù)據(jù)庫
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)