{"id":13635493,"url":"https://github.com/houbb/resubmit","last_synced_at":"2025-04-12T06:30:48.121Z","repository":{"id":37113912,"uuid":"283995336","full_name":"houbb/resubmit","owner":"houbb","description":"The repeat submit tool for java.(java 防止重复提交框架，支持注解。支持 spring, springboot 整合。)","archived":false,"fork":false,"pushed_at":"2022-12-10T10:57:48.000Z","size":119,"stargazers_count":80,"open_issues_count":2,"forks_count":26,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-26T02:01:40.648Z","etag":null,"topics":["resubmission","spring-boot","springboot","springboot2","submit"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/houbb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-31T09:26:20.000Z","updated_at":"2024-12-24T06:17:18.000Z","dependencies_parsed_at":"2023-01-26T02:00:37.014Z","dependency_job_id":null,"html_url":"https://github.com/houbb/resubmit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fresubmit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fresubmit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fresubmit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fresubmit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/houbb","download_url":"https://codeload.github.com/houbb/resubmit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248529231,"owners_count":21119466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["resubmission","spring-boot","springboot","springboot2","submit"],"created_at":"2024-08-02T00:00:46.316Z","updated_at":"2025-04-12T06:30:47.488Z","avatar_url":"https://github.com/houbb.png","language":"Java","readme":"# resubmit\n\n[resubmit](https://github.com/houbb/resubmit) 是一款为 java 设计的渐进式防止重复提交框架。\n\n[![Build Status](https://travis-ci.com/houbb/resubmit.svg?branch=master)](https://travis-ci.com/houbb/resubmit)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.houbb/resubmit/badge.svg)](http://mvnrepository.com/artifact/com.github.houbb/resubmit)\n[![](https://img.shields.io/badge/license-Apache2-FF0080.svg)](https://github.com/houbb/resubmit/blob/master/LICENSE.txt)\n[![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/houbb/resubmit)\n\n推荐阅读：\n\n[面试官：你们的项目中是怎么做防止重复提交的？](https://mp.weixin.qq.com/s/ZQx7cDUJXMDm4QXAPsfnFQ)\n\n[resubmit 渐进式防重复提交框架简介](https://mp.weixin.qq.com/s/tVkeyrDNc_scRusbClrY1w)\n\n## 创作目的\n\n有时候手动加防止重复提交很麻烦，每次手动编写不利于复用。\n\n所以希望从从简到繁实现一个工具，便于平时使用。\n\n## 特性\n\n- 渐进式实现，可独立 spring 使用\n\n- 基于注解+字节码，配置灵活\n\n- 支持编程式的调用\n\n- 支持注解式，完美整合 spring\n\n- 支持整合 spring-boot\n\n\u003e [变更日志](https://github.com/houbb/resubmit/blob/master/CHANGELOG.md)\n\n# 快速开始\n\n## maven 引入\n\n```xml\n\u003cdependency\u003e\n    \u003cgroup\u003ecom.github.houbb\u003c/group\u003e\n    \u003cartifact\u003eresubmit-core\u003c/artifact\u003e\n    \u003cversion\u003e1.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 编码\n\n- UserService.java\n\n`@Resubmit` 对应的属性如下：\n\n| 属性 | 说明 | 默认值 |\n|:---|:---|:---|\n| value() | 多久内禁止重复提交，单位为毫秒。| 60000 |\n\n```java\n@Resubmit(5000)\npublic void queryInfo(final String id) {\n    System.out.println(\"query info: \" + id);\n}\n```\n\n- 测试代码\n\n如果在指定时间差内，重复请求，则会抛出异常 ResubmitException\n\n```java\n@Test(expected = ResubmitException.class)\npublic void errorTest() {\n    UserService service = ResubmitProxy.getProxy(new UserService());\n    service.queryInfo(\"1\");\n    service.queryInfo(\"1\");\n}\n```\n\n相同的参数直接提交2次，就会报错。\n\n- 测试场景2\n\n如果等待超过指定的 5s，就不会报错。\n\n```java\n@Test\npublic void untilTtlTest() {\n    UserService service = ResubmitProxy.getProxy(new UserService());\n    service.queryInfo(\"1\");\n    DateUtil.sleep(TimeUnit.SECONDS, 6);\n    service.queryInfo(\"1\");\n}\n```\n\n## 自定义\n\n`ResubmitProxy.getProxy(new UserService());` 可以获取 UserService 对应的代理。\n\n等价于：\n\n```java\nResubmitBs resubmitBs = ResubmitBs.newInstance()\n                .cache(new CommonCacheServiceMap())\n                .keyGenerator(new KeyGenerator())\n                .tokenGenerator(new HttpServletRequestTokenGenerator());\n\nUserService service = ResubmitProxy.getProxy(new UserService(), resubmitBs);\n```\n\n其中 ResubmitBs 作为引导类，对应的策略都支持自定义。\n\n| 属性 | 说明  | 默认值 |\n|:---|:---|:---|\n| cache() | 缓存实现策略 | 默认为基于 ConcurrentHashMap 实现的基于内存的缓存实现 |\n| keyGenerator() | key 实现策略，用于唯一标识一个方法+参数，判断是否为相同的提交 | md5 策略 |\n| tokenGenerator() | token 实现策略，用于唯一标识一个用户。 | 从 HttpServletRequest 中的 header 属性 `resubmit_token` 中获取 |\n\n\n# spring 整合使用\n\n## maven 引入\n\n```xml\n\u003cdependency\u003e\n    \u003cgroup\u003ecom.github.houbb\u003c/group\u003e\n    \u003cartifact\u003eresubmit-spring\u003c/artifact\u003e\n    \u003cversion\u003e1.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 代码编写\n\n- UserService.java\n\n```java\n@Service\npublic class UserService {\n\n    @Resubmit(5000)\n    public void queryInfo(final String id) {\n        System.out.println(\"query info: \" + id);\n    }\n\n}\n```\n\n- SpringConfig.java\n\n```java\n@ComponentScan(\"com.github.houbb.resubmit.test.service\")\n@EnableResubmit\n@Configuration\npublic class SpringConfig {\n}\n```\n\n### @EnableResubmit 注解说明\n\n`@EnableResubmit` 中用户可以指定对应的实现策略，便于更加灵活的适应业务场景。\n\n和 `ResubmitBs` 中支持自定义的属性一一对应。\n\n| 属性 | 说明  | 默认值 |\n|:---|:---|:---|\n| cache() | 缓存实现策略 | 默认为基于 ConcurrentHashMap 实现的基于内存的缓存实现 |\n| keyGenerator() | key 实现策略，用于唯一标识一个方法+参数，判断是否为相同的提交 | md5 策略 |\n| tokenGenerator() | token 实现策略，用于唯一标识一个用户。 | 从 HttpServletRequest 中的 header 属性 `resubmit_token` 中获取 |\n\n## 测试代码\n\n```java\n@ContextConfiguration(classes = SpringConfig.class)\n@RunWith(SpringJUnit4ClassRunner.class)\npublic class ResubmitSpringTest {\n\n    @Autowired\n    private UserService service;\n\n    @Test(expected = ResubmitException.class)\n    public void queryTest() {\n        service.queryInfo(\"1\");\n        service.queryInfo(\"1\");\n    }\n\n}\n```\n\n# 整合 spring-boot\n\n## maven 引入\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.houbb\u003c/groupId\u003e\n    \u003cartifactId\u003eresubmit-springboot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e1.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 代码实现\n\n- UserService.java\n\n这个方法实现和前面的一样。\n\n```java\n@Service\npublic class UserService {\n\n    @Resubmit(5000)\n    public void queryInfo(final String id) {\n        System.out.println(\"query info: \" + id);\n    }\n\n}\n```\n\n- Application.java\n\n启动入口\n\n```java\n@SpringBootApplication\npublic class ResubmitApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(ResubmitApplication.class, args);\n    }\n\n}\n```\n\n## 测试代码\n\n```java\n@ContextConfiguration(classes = ResubmitApplication.class)\n@RunWith(SpringJUnit4ClassRunner.class)\npublic class ResubmitSpringBootStarterTest {\n\n    @Autowired\n    private UserService service;\n\n    @Test(expected = ResubmitException.class)\n    public void queryTest() {\n        service.queryInfo(\"1\");\n        service.queryInfo(\"1\");\n    }\n\n}\n```\n\n# 自定义策略\n\n上面提到 `@EnableResubmit` 中的策略支持自定义。\n\n此处仅以 cache 为例，为了简单，默认是基于本地内存的缓存实现。\n\n**如果你不是单点应用，那么基于 redis 的缓存更加合适**\n\n## 自定义缓存 cache\n\n### 实现缓存\n\n只需要实现 `ICommonCacheService` 接口即可。\n\n```java\npublic class MyDefineCache extends CommonCacheServiceMap {\n\n    // 这里只是作为演示，实际生产建议使用 redis 作为统一缓存\n    @Override\n    public synchronized void set(String key, String value, long expireMills) {\n        System.out.println(\"------------- 自定义的设置实现\");\n\n        super.set(key, value, expireMills);\n    }\n\n}\n```\n\n### core 中指定使用\n\n在非 spring 项目中，可以在引导类中指定我们定义的缓存。\n\n```java\nResubmitBs resubmitBs = ResubmitBs.newInstance()\n                .cache(new MyDefineCache());\n\nUserService service = ResubmitProxy.getProxy(new UserService(), resubmitBs);\n```\n\n其他使用方式保持不变。\n\n### spring 中指定使用\n\n在 spring 项目中，我们需要调整一下配置，其他不变。\n\n```java\n@ComponentScan(\"com.github.houbb.resubmit.test.service\")\n@Configuration\n@EnableResubmit(cache = \"myDefineCache\")\npublic class SpringDefineConfig {\n\n    @Bean(\"myDefineCache\")\n    public ICommonCacheService myDefineCache() {\n        return new MyDefineCache();\n    }\n\n}\n```\n\n`@EnableResubmit(cache = \"myDefineCache\")` 指定我们自定义的缓存策略名称。\n\n# Redis 的内置缓存策略\n\n为了便于复用，基于 redis 的缓存策略已实现，后续有时间进行讲解。\n\n\u003e [Redis-Config](https://github.com/houbb/redis-config)\n\n# 开源地址\n\n为了便于大家学习使用，目前防重复提交框架已开源。\n\n欢迎大家 fork+star，鼓励一下老马~\n\n\u003e [https://github.com/houbb/resubmit](https://github.com/houbb/resubmit)\n\n# Road-Map\n\n- [ ] 优化 spring 对应的版本依赖\n\n- [ ] 添加基于 redis 的 cache 实现\n\n# 中间件等工具开源矩阵\n\n[heaven: 收集开发中常用的工具类](https://github.com/houbb/heaven)\n\n[rpc: 基于 netty4 实现的远程调用工具](https://github.com/houbb/rpc)\n\n[mq: 简易版 mq 实现](https://github.com/houbb/mq)\n\n[ioc: 模拟简易版 spring ioc](https://github.com/houbb/ioc)\n\n[mybatis: 简易版 mybatis](https://github.com/houbb/mybatis)\n\n[cache: 渐进式 redis 缓存](https://github.com/houbb/cache)\n\n[jdbc-pool: 数据库连接池实现](https://github.com/houbb/jdbc-pool)\n\n[sandglass: 任务调度时间工具框架](https://github.com/houbb/sandglass)\n\n[sisyphus: 支持注解的重试框架](https://github.com/houbb/sisyphus)\n\n[resubmit: 防止重复提交框架，支持注解](https://github.com/houbb/resubmit)\n\n[auto-log: 日志自动输出](https://github.com/houbb/auto-log)\n\n[async: 多线程异步并行框架](https://github.com/houbb/async)\n\n# 缓存相关工具\n\n[cache: 手写渐进式 redis](https://github.com/houbb/cache)\n\n[common-cache: 通用缓存标准定义](https://github.com/houbb/common-cache)\n\n[redis-config: 兼容各种常见的 redis 配置模式](https://github.com/houbb/redis-config)\n\n[lock: 开箱即用的分布式锁](https://github.com/houbb/lock)\n\n[resubmit: 防重复提交](https://github.com/houbb/resubmit)\n\n[rate-limit: 限流](https://github.com/houbb/rate-limit/)\n\n","funding_links":[],"categories":["测试"],"sub_categories":["客户端"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoubb%2Fresubmit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoubb%2Fresubmit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoubb%2Fresubmit/lists"}