自定義多數(shù)據(jù)源的redis-stater

背景

在我們實(shí)際開(kāi)發(fā)過(guò)程中,可能我們一個(gè)項(xiàng)目里面在使用redis,不同模塊的數(shù)據(jù)存儲(chǔ)到不同的redis服務(wù)器中,如果公司多個(gè)項(xiàng)目都是這種情況,那我們就可以自定義一個(gè)stater來(lái)支持這種實(shí)現(xiàn)

話(huà)不多說(shuō),開(kāi)始?。?!

自定義redis-stater

  • 創(chuàng)建 maven 工程,工程名為dy-commons-redis-starter,導(dǎo)入依賴(lài):
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <!--redis 依賴(lài)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--lombok 依賴(lài)-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
            <optional>true</optional>
        </dependency>

        <!--jedis 依賴(lài)-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>

    </dependencies>
  • 新建Properties類(lèi)
/**
 * @Description:
 * @author: dy
 */
@Data
@ConditionalOnClass(RedisProperties.class)
@ConfigurationProperties(prefix = "dy.redis")
public class CustomRedisProperties {

    /**
     * 使用 Map <K,V> 結(jié)構(gòu)存儲(chǔ)多個(gè) Redis 驅(qū)動(dòng)的連接信息
     */
    private Map<String, RedisProperties> instances = new HashMap<>();

    /**
     * 通過(guò) ID 刷選出配置信息
     *
     * @param id
     * @return
     */
    public RedisProperties findById(String id) {
        try {
            return instances.get(id);
        } catch (Exception e) {
            throw new RuntimeException("Not found the redis instance configuration info with id[" + id + "].");
        }
    }
}
  • 新建配置類(lèi)CustomRedisAutoConfiguration
/**
 * @Description:
 * @author: dy
 */
@Configuration
@EnableConfigurationProperties({CustomRedisProperties.class})
public class CustomRedisAutoConfiguration {

    private final static Logger log = LoggerFactory.getLogger(CustomRedisAutoConfiguration.class);

    private CustomRedisProperties customRedisProperties;

    public CustomRedisAutoConfiguration(CustomRedisProperties customRedisProperties) {
        this.customRedisProperties = customRedisProperties;
    }

    @Bean
    public CustomRedisProperties customRedisProperties() {
        log.debug("===>> Start Configuration Custom RedisAutoConfiguration.");
        return new CustomRedisProperties();
    }
}
  • 新建RedisConfigBuilder進(jìn)行RedisTemplate的構(gòu)建
/**
 * @Description:
 * @author: dy
 */
public class RedisConfigBuilder {

    private RedisProperties properties;

    private RedisSentinelConfiguration sentinelConfiguration;

    private RedisClusterConfiguration clusterConfiguration;

    public RedisConfigBuilder(RedisProperties properties) {
        this.properties = properties;
    }

    /**
     * @param properties
     * @return RedisConfigBuilder
     */
    public static RedisConfigBuilder create(RedisProperties properties) {
        return new RedisConfigBuilder(properties);
    }

    /**
     * RedisTemplate build
     *
     * @return RedisTemplate
     */
    public RedisTemplate build() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 將 Key 設(shè)置為字符串類(lèi)型,方便維護(hù)
        // value序列化  使用 JSON 序列化方式(庫(kù)是 Jackson
        template.setValueSerializer(RedisSerializer.json());
        //key序列化 設(shè)置key為字符串類(lèi)型
        template.setKeySerializer(new StringRedisSerializer());
        // Hash value序列化  使用 JSON 序列化方式(庫(kù)是 Jackson
        template.setHashValueSerializer(RedisSerializer.json());
        //hashKey序列化設(shè)置hashKey為字符串類(lèi)型
        template.setHashKeySerializer(new StringRedisSerializer());

        //設(shè)置redis使用連接池,這里采用的是lettuce
        template.setConnectionFactory(createJedisConnectionFactory());

        //初始化 RedisTemplate (必須調(diào)用)
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 創(chuàng)建一個(gè)連接池 工廠(chǎng)對(duì)象
     *
     * @return 連接池
     */
    private LettuceConnectionFactory createJedisConnectionFactory() {
        //GenericObjectPoolConfig 連接池配置
        GenericObjectPoolConfig genericObjectPoolConfig = genericObjectPoolConfig();
        // 配置池
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(properties.getTimeout())
                .poolConfig(genericObjectPoolConfig)
                .build();

        LettuceConnectionFactory lettuceConnectionFactory = null;
        if (null != properties.getSentinel()) {
            //哨兵模式
            lettuceConnectionFactory = new LettuceConnectionFactory(redisSentinelConfiguration(), clientConfig);
        }
        if (null != properties.getCluster()) {
            //集群模式
            lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration(), clientConfig);
        } else {
            //單實(shí)例模式
            lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration(), clientConfig);
        }

        //初始化 lettuceConnectionFactory (必須調(diào)用)
        lettuceConnectionFactory.afterPropertiesSet();
        return lettuceConnectionFactory;
    }

    /**
     * GenericObjectPoolConfig 連接池配置
     *
     * @return  連接池配置
     */
    private GenericObjectPoolConfig genericObjectPoolConfig() {
        //GenericObjectPoolConfig 連接池配置
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        if (null != properties.getLettuce() && null != properties.getLettuce().getPool()) {
            genericObjectPoolConfig.setMaxIdle(properties.getLettuce().getPool().getMaxIdle());
            genericObjectPoolConfig.setMinIdle(properties.getLettuce().getPool().getMinIdle());
            genericObjectPoolConfig.setMaxTotal(properties.getLettuce().getPool().getMaxActive());
            genericObjectPoolConfig.setMaxWaitMillis(properties.getLettuce().getPool().getMaxWait().toMillis());
        }
        return genericObjectPoolConfig;
    }

    /**
     * 單實(shí)例 配置
     *
     * @return RedisStandaloneConfiguration
     */
    private RedisStandaloneConfiguration redisStandaloneConfiguration() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(properties.getHost(), properties.getPort());
        redisStandaloneConfiguration.setPassword(properties.getPassword());
        return redisStandaloneConfiguration;
    }

    /**
     * 集群配置
     *
     * @return RedisClusterConfiguration
     */
    private RedisClusterConfiguration redisClusterConfiguration() {
        // 集群
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(properties.getCluster().getNodes());
        redisClusterConfiguration.setMaxRedirects(properties.getCluster().getMaxRedirects());
        redisClusterConfiguration.setPassword(properties.getPassword());
        return redisClusterConfiguration;
    }

    /**
     * 哨兵配置
     *
     * @return RedisSentinelConfiguration
     */
    private RedisSentinelConfiguration redisSentinelConfiguration() {
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(properties.getSentinel().getMaster(),
                new HashSet<>(properties.getSentinel().getNodes()));
        redisSentinelConfiguration.setPassword(properties.getPassword());
        return redisSentinelConfiguration;
    }
}
  • 對(duì)redis的增刪改查進(jìn)行封裝
/**
 * @Description:
 * @author: dy
 */
public interface CacheService {

    /**
     * 刪除
     *
     * @param keys
     */
    void del(String... keys);

    /**
     * 為key設(shè)置超時(shí)時(shí)間
     *
     * @param key
     * @param seconds
     * @return
     */
    boolean expire(String key, long seconds);

    /**
     * 根據(jù)key獲取過(guò)期時(shí)間
     *
     * @param key 鍵 不能為null
     * @return 時(shí)間(秒) 返回0代表為永久有效
     */
    long getExpire(String key);

    /**
     * 普通緩存放入
     * @param key 鍵
     * @param value 值
     * @param expireTime 超時(shí)時(shí)間(秒)
     * @return true成功 false失敗
     */
    Boolean set(String key, Object value, int expireTime);

    /**
     * 普通緩存放入
     * @param key 鍵
     * @param value 值
     * @return true成功 false失敗
     */
    Boolean set(String key, Object value);

    /**
     * 存儲(chǔ)數(shù)據(jù)
     *
     * @param key
     * @param value
     * @return
     */
    Boolean set(String key, String value);

    /**
     * 獲取數(shù)據(jù)
     *
     * @param key
     * @return
     */
    Object getObject(String key);

    /**
     * 遞增
     *
     * @param key   鍵
     * @param delta 要增加幾(大于0)
     * @return
     */
    long incr(String key, long delta) ;

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小于0)
     * @return
     */
    long decr(String key, long delta);

    /**
     * 在原有的值基礎(chǔ)上新增字符串到末尾。
     * @param key
     * @param value
     */
    void append(String key, String value);

    /**
     * 設(shè)置map集合到redis。
     * @param map
     */
    void multiSet(Map map);

    /**
     * 根據(jù)集合取出對(duì)應(yīng)的value值。
     * @param list
     * @return
     */
    List multiGet(List list);

    /**
     *  如果鍵不存在則新增,存在則不改變已經(jīng)有的值
     * @param key
     * @param value
     * @return
     */
    boolean setIfAbsent(String key, String value);

    /**
     * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
     * @param key   鍵
     * @param item  項(xiàng)
     * @param value 值
     * @return true 成功 false失敗
     */
    boolean hashSet(String key, String item, Object value);

    /**
     * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
     * @param key   鍵
     * @param item  項(xiàng)
     * @param value 值
     * @param time  時(shí)間(秒) 注意:如果已存在的hash表有時(shí)間,這里將會(huì)替換原有的時(shí)間
     * @return true 成功 false失敗
     */
    boolean hashSet(String key, String item, Object value, long time);

    /**
     * 刪除hash表中的值
     * @param key  鍵 不能為null
     * @param item 項(xiàng) 可以使多個(gè) 不能為null
     */
    void hashDel(String key, Object... item);

    /**
     * 判斷hash表中是否有該項(xiàng)的值
     * @param key  鍵 不能為null
     * @param item 項(xiàng) 不能為null
     * @return true 存在 false不存在
     */
    boolean hHasKey(String key, String item);

    /**
     * 使用lua腳本的優(yōu)勢(shì):
     * 1.原子性。redis執(zhí)行l(wèi)ua腳本的時(shí)候,會(huì)將它作為一個(gè)整體執(zhí)行,要么全部執(zhí)行成功,如果出現(xiàn)異常則執(zhí)行結(jié)果不會(huì)更新到redis中,
     * 可以代替redis中的事務(wù)操作。
     *
     * 2.節(jié)省網(wǎng)絡(luò)開(kāi)銷(xiāo)。通過(guò)腳本的方式執(zhí)行多個(gè)命令,一次傳輸返回結(jié)果。
     * redis的pipeline也同樣有這樣的優(yōu)點(diǎn),相比lua腳本中執(zhí)行的多個(gè)命令,
     * pipe中某個(gè)命令執(zhí)行出現(xiàn)異常不會(huì)影響其他的命令的更新到redis中;但lua腳本使用更加靈活。
     *
     * 3.腳本的復(fù)用。如果每次請(qǐng)求都要傳輸腳本,存在一定的網(wǎng)絡(luò)開(kāi)銷(xiāo),
     * 通過(guò) SCRIPT LOAD 命令進(jìn)行Redis 將腳本緩存到服務(wù)器的操作,
     * 并且返回腳本內(nèi)容的SHA1校驗(yàn)和,Evalsha 命令根據(jù)給定的 sha1 校驗(yàn)碼,執(zhí)行緩存在服務(wù)器中的腳本。
     *
     * 執(zhí)行 lua 腳本
     *
     * @param luaScript  lua 腳本
     * @param returnType 返回的結(jié)構(gòu)類(lèi)型
     * @param keys       KEYS
     * @param argv       ARGV
     * @param <T>        泛型
     *
     * @return 執(zhí)行的結(jié)果
     */
    <T> T executeLuaScript(String luaScript, Class<T> returnType, String[] keys, String... argv);


    /**
     * 分布式鎖
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    boolean tryLock(String key, long timeout, TimeUnit unit);

    /**
     * 釋放鎖
     * @param key
     */
    void releaseLock(String key);

}
  • 實(shí)現(xiàn)類(lèi)如下
/**
 * @Description:
 * @author: dy
 */
public class CacheServiceImpl implements CacheService {

    private RedisTemplate<String, Object> redisTemplate;

    public CacheServiceImpl() {

    }

    public CacheServiceImpl(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 普通緩存放入
     *
     * @param key        鍵
     * @param value      值
     * @param expireTime 超時(shí)時(shí)間(秒)
     * @return true成功 false失敗
     */
    @Override
    public Boolean set(String key, Object value, int expireTime) {
        try {
            redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    @Override
    public Boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    @Override
    public Boolean set(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 在原有的值基礎(chǔ)上新增字符串到末尾。
     *
     * @param key   鍵
     * @param value 追加的值
     */
    @Override
    public void append(String key, String value) {
        redisTemplate.opsForValue().append(key, value);
    }

    /**
     * 普通緩存獲取
     *
     * @param key 鍵
     * @return 值
     */
    @Override
    public Object getObject(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 如果鍵不存在則新增,存在則不改變已經(jīng)有的值
     *
     * @param key
     * @param value
     * @return true成功 false失敗
     */
    @Override
    public boolean setIfAbsent(String key, String value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 刪除指定KEY的緩存
     *
     * @param keys
     */
    @Override
    public void del(String... keys) {
        if (keys != null && keys.length > 0) {
            if (keys.length == 1) {
                redisTemplate.delete(keys[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(keys));
            }
        }
    }

    /**
     * 根據(jù)key設(shè)置過(guò)期時(shí)間
     *
     * @param key     鍵
     * @param seconds 超時(shí)時(shí)間(秒)
     * @return true成功 false失敗
     */
    @Override
    public boolean expire(String key, long seconds) {
        return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
    }

    /**
     * 根據(jù)key獲取過(guò)期時(shí)間
     *
     * @param key 鍵 不能為null
     * @return 時(shí)間(秒) 返回0代表為永久有效
     */
    @Override
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 遞增
     *
     * @param key   鍵
     * @param delta 要增加幾(大于0)
     * @return 返回增加后的值
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞增因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小于0)
     * @return 返回減少后的值
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞減因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    /**
     * 設(shè)置map集合到redis。
     *
     * @param map
     */
    @Override
    public void multiSet(Map map) {
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 根據(jù)集合取出對(duì)應(yīng)的value值。
     *
     * @param list
     * @return
     */
    @Override
    public List multiGet(List list) {
        return redisTemplate.opsForValue().multiGet(list);
    }

    /**
     * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
     *
     * @param key   鍵
     * @param item  項(xiàng)
     * @param value 值
     * @return true 成功 false失敗
     */
    public boolean hashSet(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入數(shù)據(jù),如果不存在將創(chuàng)建
     *
     * @param key   鍵
     * @param item  項(xiàng)
     * @param value 值
     * @param time  時(shí)間(秒) 注意:如果已存在的hash表有時(shí)間,這里將會(huì)替換原有的時(shí)間
     * @return true 成功 false失敗
     */
    public boolean hashSet(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除hash表中的值
     *
     * @param key  鍵 不能為null
     * @param item 項(xiàng) 可以使多個(gè) 不能為null
     */
    public void hashDel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判斷hash表中是否有該項(xiàng)的值
     *
     * @param key  鍵 不能為null
     * @param item 項(xiàng) 不能為null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * 執(zhí)行 lua 腳本
     *
     * @param luaScript  lua 腳本
     * @param returnType 返回的結(jié)構(gòu)類(lèi)型
     * @param keys       KEYS
     * @param argv       ARGV
     * @param <T>        泛型
     * @return 執(zhí)行的結(jié)果
     */
    public <T> T executeLuaScript(String luaScript, Class<T> returnType, String[] keys, String... argv) {
        return (T) redisTemplate.execute(RedisScript.of(luaScript, returnType),
                new StringRedisSerializer(),
                new GenericToStringSerializer<>(returnType),
                Arrays.asList(keys),
                argv);
    }

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit unit) {
        String uuid = UUID.randomUUID().toString();
        return redisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
    }

    @Override
    public void releaseLock(String key) {
        redisTemplate.delete(key);
    }


}
  • 在resources文件下創(chuàng)建META-INF文件夾,新建spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.dy.commons.data.redis.starter.CustomRedisAutoConfiguration

至此stater我們就定義好了,發(fā)布到maven倉(cāng)庫(kù)

redis-stater的使用

  • 引入依賴(lài)
 <dependency>
      <groupId>io.gitee.dai-yong-1</groupId>
       <artifactId>dy-commons-redis-starter</artifactId>
       <version>1.0.2-SNAPSHOT</version>
</dependency>
  • 配置文件如下
dy.redis:
  instances:
    redis-stack:
      host: xxx.xxx.xxx.xxx
      password: xxxx
      port: 6379
      lettuce:
        pool:
          max-active: 200
          max-idle: 30
          min-idle: 10
          max-wait: 3000ms
      timeout: 3000
    redis-2:
      database: 1
      host: xxx.xxx.xxx.xxx
      password: xxxx
      port: 6379
      lettuce:
        pool:
          max-active: 200
          max-idle: 30
          min-idle: 10
          max-wait: 3000ms
      timeout: 3000
  • 新建RedisConfig把我們的CacheService裝入容器
/**
 * @Description:
 * @author: dy
 */
@Configuration
public class RedisConfig {

    @Autowired
    private CustomRedisProperties customRedisProperties;

    @Bean(name="cacheService")
    @Qualifier("cacheService")
    public CacheService cacheService() {
        CacheService cacheService = new CacheServiceImpl(RedisConfigBuilder.create(customRedisProperties.findById("redis-stack")).build());
        return cacheService;
    }

    @Bean(name="cacheService2")
    @Qualifier("cacheService2")
    public CacheService cacheService2() {
        CacheService cacheService2 = new CacheServiceImpl(RedisConfigBuilder.create(customRedisProperties.findById("redis-2")).build());
        return cacheService2;
    }

}

  • 具體使用redis存儲(chǔ)數(shù)據(jù)如下
@Slf4j
@RestController
public class UserController implements UserApi {

    @Autowired
    private CacheService cacheService;
    @Autowired
    private CacheService cacheService2;


    @Override
    public String testRedis() {
        UserDTO userDTO = new UserDTO();
        userDTO.setId("111");
        userDTO.setName("小紅");
        cacheService.set("hello", userDTO);
        return "" + cacheService.getObject("hello");
    }

    @Override
    public String testRedis2() {

        for (int i=0;i<500;i++){
            int finalI = i;
            MyThreadPoolExecutor.open(5).execute(() -> {
                getNumber("errorId10", finalI);
            });
        }
        return "success";
    }

    private Long getNumber(String key,int i) {
        long startTime = System.currentTimeMillis();
        long num = 0l;
        if (null == cacheService.getObject(key)) {
            num = getNum(key);
        } else {
            num = cacheService.incr(key, 1);
        }
        log.info("===>> pos:{} 用時(shí)==========>>:{}  ,===>>num:{}" , i ,(System.currentTimeMillis() - startTime),num);
        return num;
    }

    private Long getNum(String key) {
        String lockKey = "lockKey";
        while (true) {
            if (cacheService.tryLock(lockKey, 5, TimeUnit.SECONDS)) {
                log.info("num===ddddddddddddddddddddddddddddd==加鎖成功============");
                break;
            }
        }
        if (null != cacheService.getObject(key)) {
            cacheService.del(lockKey);
            return cacheService.incr(key, 1);
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cacheService.set(key, 100);
        cacheService.del(lockKey);
        log.info("num===ddddddddddddddddddddddddddddd==============="+cacheService.getObject(lockKey));
        return 100l;
    }


}

大功告成?。。。?!
源碼地址:https://gitee.com/dai-yong-1/dy-commons-redis-starter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容