All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.taotao.cloud.idempotent.enhance.redis.RedisIdempotentRepositoryImpl Maven / Gradle / Ivy

There is a newer version: 2024.09
Show newest version
/*
 * Copyright (c) 2020-2030, Shuigedeng ([email protected] & https://blog.taotaocloud.top/).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.taotao.cloud.idempotent.enhance.redis;

import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Lists;
import com.taotao.cloud.common.utils.log.LogUtils;
import com.taotao.cloud.idempotent.enhance.core.em.IdempotentStatusEnum;
import com.taotao.cloud.idempotent.enhance.core.exception.IdempotentException;
import com.taotao.cloud.idempotent.enhance.core.pojo.IdempotentEntity;
import com.taotao.cloud.idempotent.enhance.core.repository.IdempotentRepository;
import com.taotao.cloud.idempotent.enhance.redis.config.properties.IdempotentRedisAdapterProperties;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

public class RedisIdempotentRepositoryImpl implements IdempotentRepository {

    /** 创建幂等记录lua脚本 */
    private static final String CREATE_LUA = "script/lua/create.lua";

    /** 修改幂等状态为成功lua脚本 */
    private static final String STATE_FLOW_LUA = "script/lua/state-flow.lua";

    /** 删除幂等记录lua脚本 */
    private static final String DELETE_LUA = "script/lua/delete.lua";

    private static final DefaultRedisScript CREATE_SCRIPT;

    private static final DefaultRedisScript STATE_FLOW_SCRIPT;

    private static final DefaultRedisScript DELETE_SCRIPT;

    static {
        CREATE_SCRIPT = new DefaultRedisScript<>();
        CREATE_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource(CREATE_LUA)));
        CREATE_SCRIPT.setResultType(Boolean.class);

        STATE_FLOW_SCRIPT = new DefaultRedisScript<>();
        STATE_FLOW_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource(STATE_FLOW_LUA)));
        STATE_FLOW_SCRIPT.setResultType(Boolean.class);

        DELETE_SCRIPT = new DefaultRedisScript<>();
        DELETE_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource(DELETE_LUA)));
        DELETE_SCRIPT.setResultType(Boolean.class);
    }

    private final StringRedisTemplate redisTemplate;

    private final IdempotentRedisAdapterProperties properties;

    public RedisIdempotentRepositoryImpl(
            StringRedisTemplate redisTemplate, IdempotentRedisAdapterProperties properties) {
        if (redisTemplate == null) {
            throw new IdempotentException("redisTemplate can not be null.");
        }
        if (properties == null) {
            throw new IdempotentException("properties can not be null.");
        }
        this.properties = properties;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean create(IdempotentEntity idempotentEntity) {
        Boolean execute = doExecuteScript(idempotentEntity, CREATE_SCRIPT);
        if (!Boolean.TRUE.equals(execute)) {
            LogUtils.error(
                    "create idempotent record failed, because return value is false, uniqueKey is :" + " {}",
                    idempotentEntity.getUniqueKey());
        }
        return Boolean.TRUE.equals(execute);
    }

    @Override
    public Boolean changeIdempotentStatusProcessing(IdempotentEntity idempotentEntity) {
        // 再次显示设置状态为处理中
        idempotentEntity.setIdempotentStatus(IdempotentStatusEnum.PROCESSING.getStatus());
        // 版本号 +1
        idempotentEntity.setObjectVersionNumber(idempotentEntity.getObjectVersionNumber() + 1);
        Boolean result = doExecuteScript(idempotentEntity, STATE_FLOW_SCRIPT);
        if (!Boolean.TRUE.equals(result)) {
            // 更新不成功,则版本号-1
            idempotentEntity.setObjectVersionNumber(idempotentEntity.getObjectVersionNumber() - 1);
        }
        return Boolean.TRUE.equals(result);
    }

    @Override
    public boolean changeIdempotentStatusSuccess(IdempotentEntity idempotentEntity) {
        // 再次显示设置状态为处理成功
        idempotentEntity.setIdempotentStatus(IdempotentStatusEnum.SUCCESS.getStatus());
        // 版本号 +1
        idempotentEntity.setObjectVersionNumber(idempotentEntity.getObjectVersionNumber() + 1);
        Boolean result = doExecuteScript(idempotentEntity, STATE_FLOW_SCRIPT);
        if (!Boolean.TRUE.equals(result)) {
            // 更新不成功,则版本号-1
            idempotentEntity.setObjectVersionNumber(idempotentEntity.getObjectVersionNumber() - 1);
        }
        return Boolean.TRUE.equals(result);
    }

    @Override
    public Boolean delete(IdempotentEntity idempotentEntity) {
        String uniqueKey = format(properties.getKeyPrefix(), idempotentEntity.getUniqueKey());
        String uniqueDataKey = format(properties.getKeyPrefix(), idempotentEntity.getUniqueKey());
        // 严格检查要删除的key不能都为空
        if (StringUtils.isAllBlank(uniqueKey, uniqueDataKey)) {
            return true;
        }
        Boolean execute = redisTemplate.execute(DELETE_SCRIPT, Lists.newArrayList(uniqueKey, uniqueDataKey));
        return Boolean.TRUE.equals(execute);
    }

    @Override
    public Optional query(IdempotentEntity idempotentEntity) {
        String uniqueDataKey = format(properties.getDataKeyPrefix(), idempotentEntity.getUniqueKey());
        String result = redisTemplate.opsForValue().get(uniqueDataKey);
        // 从Redis里查询
        return Optional.ofNullable(JSONObject.parseObject(result, IdempotentEntity.class));
    }

    public String format(String prefix, String key) {
        return prefix + key;
    }

    /**
     * 执行lua脚本, 主要用于执行新增幂等记录和修改幂等记录lua脚本
     *
     * @param idempotentEntity 幂等实体
     * @param script 脚本
     * @return java.lang.Boolean
     */
    private Boolean doExecuteScript(IdempotentEntity idempotentEntity, DefaultRedisScript script) {
        // 存放 幂等key : 版本号
        String uniqueKey = format(properties.getKeyPrefix(), idempotentEntity.getUniqueKey());
        // 存放 幂等key : 幂等记录
        String uniqueDataKey = format(properties.getDataKeyPrefix(), idempotentEntity.getUniqueKey());
        // 期望的版本号(新增幂等记录idempotentEntity时版本号为1,不用增加)
        long expectVersion = idempotentEntity.getObjectVersionNumber() == 1L
                ? idempotentEntity.getObjectVersionNumber()
                : idempotentEntity.getObjectVersionNumber() - 1;
        // 过期时间
        String expireTime = String.valueOf(properties.getUnit().toSeconds(properties.getExpireTime()));
        // 幂等记录转为string存储
        String jsonStr = JSONObject.toJSONString(idempotentEntity);
        List keys = Lists.newArrayList(uniqueKey, uniqueDataKey);
        List argv = Lists.newArrayList(String.valueOf(expectVersion), expireTime, jsonStr);
        // 执行脚本
        return redisTemplate.execute(script, keys, argv.toArray());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy