当前位置:

首页 > 编程开发 > 如何使用Redis实现接口幂等性拦截器在Spring Boot中

如何使用Redis实现接口幂等性拦截器在Spring Boot中

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-22
正版软件 Windows macOS软件 Linux 数据库管理工具

正文自定义注解怎么玩的:①标记哪个接口需要进行幂等性拦截②每个接口可以要求幂等性范围时间不一样,举例:可以2秒内,可以3秒内,时间自己传③一旦触发了,提示语可以不同,举例:VIP的接口,普通用户的接口,提示语不一样(开玩笑)效果:实战开始核心三件套注解、拦截器、拦截器配置①RepeatDaMie.javaimportjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation

本文目录

    正文

    如何使用Redis实现接口幂等性拦截器在Spring Boot中

    自定义注解 怎么玩的 :      

    ①标记哪个接口需要进行幂等性拦截        

    ②每个接口可以要求幂等性范围时间不一样,举例:可以2秒内,可以3秒内,时间自己传        

    ③ 一旦触发了,提示语可以不同 ,举例:VIP的接口,普通用户的接口,提示语不一样(开玩笑)

    效果:

    如何使用Redis实现接口幂等性拦截器在Spring Boot中

    如何使用Redis实现接口幂等性拦截器在Spring Boot中

    实战开始

    核心三件套

    注解、拦截器、拦截器配置

    ① RepeatDaMie.java

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
     
    /**
     * @Author: JCccc
     * @Date: 2022-6-13 9:04
     * @Description:  自定义注解,防止重复提交
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RepeatDaMie {
     
        /**
         * 时间ms限制
         */
        public int second() default 1;
     
        /**
         * 提示消息
         */
        public String describe() default "重复提交了,兄弟";
        
    }

    ②ApiRepeatInterceptor.java

    import com.example.repeatdemo.annotation.RepeatDaMie;
    import com.example.repeatdemo.util.ContextUtil;
    import com.example.repeatdemo.util.Md5Encrypt;
    import com.example.repeatdemo.util.RedisUtils;
    import com.example.repeatdemo.wrapper.CustomHttpServletRequestWrapper;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
     
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Objects;
     
    /**
     * @Author: JCccc
     * @Date: 2022-6-15 9:11
     * @Description: 接口幂等性校验拦截器
     */
    @Component
    public class ApiRepeatInterceptor implements HandlerInterceptor {
     
        private final Logger log = LoggerFactory.getLogger(this.getClass());
     
        private static final String POST="POST";
        private static final String GET="GET";
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            try {
                if (handler instanceof HandlerMethod) {
                    HandlerMethod handlerMethod = (HandlerMethod) handler;
                    // 获取RepeatDaMie注解
                    RepeatDaMie repeatDaMie = handlerMethod.getMethodAnnotation(RepeatDaMie.class);
                    if (null==repeatDaMie) {
                        return true;
                    }
                    //限制的时间范围
                    int seconds = repeatDaMie.second();
                    //这个用户唯一标识,可以自己细微调整,是userId还是token还是sessionId还是不需要
                    String userUniqueKey = request.getHeader("userUniqueKey");
                    String method = request.getMethod();
                    String apiParams = "";
                    if (GET.equals(method)){
                        log.info("GET请求来了");
                        apiParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
                    }else if (POST.equals(method)){
                        log.info("POST请求来了");
                        CustomHttpServletRequestWrapper wrapper = (CustomHttpServletRequestWrapper) request;
                        apiParams = wrapper.getBody();
                    }
                   log.info("当前参数是:{}",apiParams);
                    // 存储key
                    String keyRepeatDaMie = Md5Encrypt.md5(userUniqueKey+request.getServletPath()+apiParams) ;
                    RedisUtils redisUtils = ContextUtil.getBean(RedisUtils.class);
                    if (Objects.nonNull(redisUtils.get(keyRepeatDaMie))){
                       log.info("重复请求了,重复请求了,拦截了");
                       returnData(response,repeatDaMie.describe());
                       return false;
                   }else {
                        redisUtils.setWithTime(keyRepeatDaMie, true,seconds);
                   }
     
                }
                return true;
            } catch (Exception e) {
                log.warn("请求过于频繁请稍后再试");
                e.printStackTrace();
            }
            return true;
        }
     
        public void returnData(HttpServletResponse response,String msg) throws IOException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            ObjectMapper objectMapper = new ObjectMapper();
            //这里传提示语可以改成自己项目的返回数据封装的类
            response.getWriter().println(objectMapper.writeValueAsString(msg));
            return;
        }
     
    }

    ③ WebConfig.java

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
     
    /**
     * @Author: JCccc
     * @Date: 2022-6-15 9:24
     * @Description:
     */
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
     
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new ApiRepeatInterceptor()).addPathPatterns("/**");
        }
    }

    工具类三件套

    ①ContextUtil.java

    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    /**
     * @Author: JCccc
     * @Date: 2022-6-15 9:24
     * @Description:
     */
    @Component
    public final class ContextUtil implements ApplicationContextAware {
        protected static ApplicationContext applicationContext ;
        @Override
        public void setApplicationContext(ApplicationContext arg0) throws BeansException {
            if (applicationContext == null) {
                applicationContext = arg0;
            }
        }
        public static Object getBean(String name) {
            //name表示其他要注入的注解name名
            return applicationContext.getBean(name);
        }
        /**
         * 拿到ApplicationContext对象实例后就可以手动获取Bean的注入实例对象
         */
        public static  T getBean(Class clazz) {
            return applicationContext.getBean(clazz);
        }
    }

    ②Md5Encrypt.java

    import java.io.UnsupportedEncodingException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
     
    /**
     * @Author: JCccc
     * @CreateTime: 2018-10-30
     * @Description:
     */
    public class Md5Encrypt {
     
        private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
                'b', 'c', 'd', 'e', 'f'};
     
        /**
         * 对字符串进行MD5加密
         *
         * @param text 明文
         * @return 密文
         */
        public static String md5(String text) {
            MessageDigest msgDigest = null;
     
            try {
                msgDigest = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("System doesn't support MD5 algorithm.");
            }
     
            try {
                // 注意该接口是按照指定编码形式签名
                msgDigest.update(text.getBytes("UTF-8"));
     
            } catch (UnsupportedEncodingException e) {
     
                throw new IllegalStateException("System doesn't support your  EncodingException.");
     
            }
     
            byte[] bytes = msgDigest.digest();
     
            String md5Str = new String(encodeHex(bytes));
     
            return md5Str;
        }
     
        private static char[] encodeHex(byte[] data) {
     
            int l = data.length;
     
            char[] out = new char[l << 1];
            // two characters form the hex value.
            for (int i = 0, j = 0; i < l; i++) {
                out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
                out[j++] = DIGITS[0x0F & data[i]];
            }
     
            return out;
        }
    }

    ③RedisUtils.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.*;
    import org.springframework.stereotype.Component;
    import java.io.Serializable;
    import java.util.List;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
     
     
    @Component
    public class RedisUtils {
     
        @Autowired
        private RedisTemplate redisTemplate;
     
        /**
         * 写入String型 [ 键,值]
         *
         * @param key
         * @param value
         * @return
         */
     
        public boolean set(final String key, Object value) {
            boolean result = false;
            try {
                ValueOperations operations = redisTemplate.opsForValue();
                operations.set(key, value);
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
     
        }
     
     
     
        /**
         * 写入String型,顺便带有过期时间 [ 键,值]
         *
         * @param key
         * @param value
         * @return
         */
     
        public boolean setWithTime(final String key, Object value,int seconds) {
            boolean result = false;
            try {
     
                ValueOperations operations = redisTemplate.opsForValue();
                operations.set(key, value,seconds, TimeUnit.SECONDS);
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
     
        }
     
     
     
        /**
         * 批量删除对应的value
         *
         * @param keys
         */
     
        public void remove(final String... keys) {
            for (String key : keys) {
                remove(key);
            }
        }
     
        /**
         * 批量删除key
         *
         * @param pattern
         */
     
        public void removePattern(final String pattern) {
            Set keys = redisTemplate.keys(pattern);
            if (keys.size() > 0)
                redisTemplate.delete(keys);
        }
     
        /**
         * 删除对应的value
         *
         * @param key
         */
     
        public void remove(final String key) {
            if (exists(key)) {
                redisTemplate.delete(key);
            }
        }
     
     
        /**
         * 判断缓存中是否有对应的value
         *
         * @param key
         * @return
         */
     
        public boolean exists(final String key) {
            return redisTemplate.hasKey(key);
        }
     
     
        /**
         * 读取缓存
         *
         * @param key
         * @return
         */
     
        public Object get(final String key) {
            Object result = null;
            ValueOperations operations = redisTemplate.opsForValue();
            result = operations.get(key);
            return result;
        }
     
     
        /**
         * 哈希 添加
         * hash 一个键值(key->value)对集合
         *
         * @param key
         * @param hashKey
         * @param value
         */
     
        public void hmSet(String key, Object hashKey, Object value) {
     
            HashOperations hash = redisTemplate.opsForHash();
     
            hash.put(key, hashKey, value);
     
        }
     
     
        /**
         * Hash获取数据
         *
         * @param key
         * @param hashKey
         * @return
         */
     
        public Object hmGet(String key, Object hashKey) {
            HashOperations hash = redisTemplate.opsForHash();
            return hash.get(key, hashKey);
     
        }
     
     
        /**
         * 列表添加
         * list:lpush key value1
         *
         * @param k
         * @param v
         */
     
        public void lPush(String k, Object v) {
            ListOperations list = redisTemplate.opsForList();
            list.rightPush(k, v);
        }
     
     
        /**
         * 列表List获取
         * lrange: key 0 10 (读取的个数 从0开始 读取到下标为10 的数据)
         *
         * @param k
         * @param l
         * @param l1
         * @return
         */
     
        public List lRange(String k, long l, long l1) {
            ListOperations list = redisTemplate.opsForList();
            return list.range(k, l, l1);
        }
     
     
        /**
         * Set集合添加
         *
         * @param key
         * @param value
         */
     
        public void add(String key, Object value) {
            SetOperations set = redisTemplate.opsForSet();
            set.add(key, value);
        }
     
     
        /**
         * Set 集合获取
         *
         * @param key
         * @return
         */
     
        public Set setMembers(String key) {
     
            SetOperations set = redisTemplate.opsForSet();
     
            return set.members(key);
     
        }
     
     
        /**
         * Sorted set :有序集合添加
         *
         * @param key
         * @param value
         * @param scoure
         */
     
        public void zAdd(String key, Object value, double scoure) {
            ZSetOperations zset = redisTemplate.opsForZSet();
            zset.add(key, value, scoure);
        }
     
     
        /**
         * Sorted set:有序集合获取
         *
         * @param key
         * @param scoure
         * @param scoure1
         * @return
         */
     
        public Set rangeByScore(String key, double scoure, double scoure1) {
     
            ZSetOperations zset = redisTemplate.opsForZSet();
     
            return zset.rangeByScore(key, scoure, scoure1);
     
        }
     
     
        /**
         * 根据key获取Set中的所有值
         *
         * @param key 键
         * @return
         */
     
        public Set sGet(String key) {
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
     
     
        /**
         * 根据value从一个set中查询,是否存在
         *
         * @param key   键
         * @param value 值
         * @return true 存在 false不存在
         */
     
        public boolean sHasKey(String key, Object value) {
            try {
                return redisTemplate.opsForSet().isMember(key, value);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
     
    }

    REDIS配置类

    RedisConfig.java

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import static org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig;
     
    /**
     * @Author: JCccc
     * @CreateTime: 2018-09-11
     * @Description:
     */
    @Configuration
    @EnableCaching
    public class RedisConfig {
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
            RedisCacheConfiguration cacheConfiguration =
                    defaultCacheConfig()
                            .disableCachingNullValues()
                            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Object.class)));
            return RedisCacheManager.builder(connectionFactory).cacheDefaults(cacheConfiguration).build();
        }
     
     
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(factory);
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            //序列化设置 ,这样为了存储操作对象时正常显示的数据,也能正常存储和获取
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
            return redisTemplate;
        }
        @Bean
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
            StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
            stringRedisTemplate.setConnectionFactory(factory);
            return stringRedisTemplate;
        }
     
    }

    最后写测试接口,看看效果(一个POST,一个GET):

    故意把时间放大,1000秒内重复调用,符合我们拦截规则的都会被拦截。

    TestController.java

    import com.example.repeatdemo.dto.PayOrderApply;
    import com.example.repeatdemo.annotation.RepeatDaMie;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.*;
     
    /**
     * @Author: JCccc
     * @Date: 2022-6-05 9:44
     * @Description:
     */
    @RestController
    public class TestController {
        private final Logger log = LoggerFactory.getLogger(this.getClass());
     
        @RepeatDaMie(second = 1000,describe = "尊敬的客户,您慢点")
        @PostMapping(value = "/doPost")
        @ResponseBody
        public void test(@RequestBody PayOrderApply payOrderApply) {
            log.info("Controller POST请求:"+payOrderApply.toString());
        }
     
        @RepeatDaMie(second = 1000,describe = "大哥,你冷静点")
        @GetMapping(value = "/doGet")
        @ResponseBody
        public void doGet( PayOrderApply payOrderApply) {
            log.info("Controller GET请求:"+payOrderApply.toString());
        }
     
    }

    PayOrderApply.java

    /**
     * @Author: JCccc
     * @Date: 2022-6-12 9:46
     * @Description:
     */
    public class PayOrderApply {
     
        private String sn;
        private Long amount;
        private String proCode;
     
        public String getSn() {
            return sn;
        }
     
        public void setSn(String sn) {
            this.sn = sn;
        }
     
        public Long getAmount() {
            return amount;
        }
     
        public void setAmount(Long amount) {
            this.amount = amount;
        }
     
        public String getProCode() {
            return proCode;
        }
     
        public void setProCode(String proCode) {
            this.proCode = proCode;
        }
     
        @Override
        public String toString() {
            return "PayOrderApply{" +
                    "sn='" + sn + '\'' +
                    ", amount=" + amount +
                    ", proCode='" + proCode + '\'' +
                    '}';
        }
    }

    如何使用Redis实现接口幂等性拦截器在Spring Boot中

    redis生成了值:

    如何使用Redis实现接口幂等性拦截器在Spring Boot中

    如何使用Redis实现接口幂等性拦截器在Spring Boot中

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    php72w redis,docker php7安装php-redis
    php72w redis,docker php7安装php-redis

    在Docker环境下为PHP安装Redis扩展:手动编译需下载源码、安装phpize、配置编译并安装,生成redis.so并在php.d目录新建redis.ini加载,重启PHP-FPM;也可直接yum安装php72w-pecl-redis,免手动编译。

    php 如何配置 redis,php 如何设置redis
    php 如何配置 redis,php 如何设置redis

    配置PHP的Redis扩展:先安装Redis服务,从GitHub下载phpredis源码,用phpize编译安装,修改php.ini添加extension=redis.so,重启php-fpm或Apache,通过phpinfo验证并连接Redis。

    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】
    c++如何将结构体序列化为Redis兼容的RESP协议格式【深度】

    C++结构体无法直接序列化为RESP协议,需手动将字段映射为*N数组嵌套$lenbulkstring形式。整数需转换为字符串并加:前缀,空字符串用$0\r\n,null用$-1\r\n。长度计算需精确到字节数,特别注意二进制安全,同时确保字段顺序与结构体定义一致。

    如何在Golang架构中部署Redict作为高能数据临时存储中转站
    如何在Golang架构中部署Redict作为高能数据临时存储中转站

    Redis作为跨服务临时状态枢纽,需按消息队列、缓存和原子状态三重角色设计。选型用List实现任务分发及延迟重试,Stream用于事件广播与位点回溯。go-redis/v8需配置MinIdleConns、MaxRetries及超时。数据写入应JSON序列化并带type标签、设合理TTL、将关键字段提至顶层。消费通过原子GET+DEL或processedkey

    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制
    TP8.0 高并发下库存超卖?Redis 原子递减结合数据库乐观锁控制

    在高并发秒杀场景下,利用Redis的减库存原子操作预扣减库存拦截超卖请求,再通过数据库乐观锁(版本号与库存校验)确保最终一致性,异常时回滚Redis库存并借助Lua脚本保证原子性,从而有效防止库存超卖。

    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑
    如何在 Go 中利用 go-redis 执行 Lua 脚本优化逻辑

    在Go中使用go-redis执行Lua脚本可借助原子性解决竞态问题,通过redis.Script封装脚本并自动处理SHA1缓存与重试。需注意KEYS必须为非空切片且所有键在Cluster模式下落在同一slot,ARGV以字符串传入并由tonumber转换。返回值需先检查错误再调用Val(),避免panic。

    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】
    TP6.0 基于 Redis SetNX 实现的简单互斥锁机制【并发】

    先看一个典型场景:电商秒杀、库存扣减、定时任务重复调度——这些并发冲突在 TP6 应用里屡见不鲜。用 Redis 的 SETNX 做互斥锁,确实是轻量又直接的方案,但“能用”和“稳妥”之间,隔着三层硬功夫:原子加锁、身份校验、合理超时。这三条,差一条就翻车。 很多人第一次上手,直接写 SETNX 加

    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】
    TP6.0 如何处理高并发抢购?Redis 原子锁与队列削峰详解【秒杀】

    高并发抢购中,Redis的DECR原子操作可安全扣库存;配合think-queue异步削峰,接口仅扣库存和推消息;分布式锁需用SETNXEX加唯一标识,释放时用Lua脚本校验;成功资格后须以数据库事务双写为准,失败时补回Redis库存。

    Golang 实现基于 Redis 的延时任务队列
    Golang 实现基于 Redis 的延时任务队列

    基于Redis的ZSET实现延时队列需解决并发重复消费、失败重试混乱、精度不足及内存爆炸等问题。通过Lua脚本保证取删原子性,使用毫秒级时间戳,对临时错误采用指数退避重试,永久错误记入独立哈希表,并限制最大延迟、定期清理过期任务、使用紧凑序列化格式。

    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】
    RedisInsight怎么修改Key的过期时间 Redis操作方法【步骤】

    操作 Redis 的时候,改个 Key 的过期时间其实是个高频场景。特别是做运维排查或者临时调试那会儿,谁也不想反复切命令行窗口、背那些命令参数。好在 RedisInsight 这个图形工具把这事儿做得很顺手——三种方式任选,总有一种合你胃口。 在动手前,先确认一下 Key 当前的 TTL 状态是个

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?
    为什么要购买正版软件?
    01 官方授权

    授权来源透明,订单与许可信息可随时查询。

    02 即时交付

    付款成功后自动发放激活信息,支持订单追踪。

    03 中文协助

    安装、激活、迁移与续费问题都有专人响应。

    04 长期可用

    提供升级提醒与授权管理,换机时也能轻松迁移。

    网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

    Copyright ©2018-2026