# sr-url **Repository Path**: welly_zhang/sr-url ## Basic Information - **Project Name**: sr-url - **Description**: SrUrl 是用于短网址生成、链接跳转、访问统计的一个项目,可以生成7位短链参数(使用OID转为62进制的话),对于常用配置可以通过提供的接口进行替换,包括存储方式、域名选择、短链生成算法、拒绝生成策略等 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2023-05-23 - **Last Updated**: 2023-05-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SrUrl > SrUrl 是用于短网址生成、短链跳转、访问统计的一个项目,可以通过后台设置或者是API接口调用的方式生成对应的短链 > 生成的短链长度为7位(使用OID转为62进制的话) > 对于常用配置可以通过提供的接口进行替换,包括存储方式、域名选择、短链生成算法、拒绝生成策略等,可根据实际需要替换为具体的实现方式 ## 系统说明 **后端基于Jdk 17、Spring Boot 3、Mysql、Mybatis Plus等** **前端基于Vue 3、Vite 4、Arco Design、Pinia、Axios等** ### 模块说明 ``` srurl ├── doc -- 文档(包含sql脚本) ├── short-url-core -- 核心模块 ├── short-url-sender -- ID生成相关模块 ├── short-url-system -- web相关功能模块 └── web -- 前端代码 └── src ├── api -- 接口 ├── assets -- 静态资源 ├── components -- 组件 ├── router -- 路由相关 ├── store -- 状态管理 ├── util -- 工具类 └── view -- 页面 ``` ## 接口说明 ### IDisabledStrategy 生成的过程中可以通过实现`IDisabledStrategy`实现某些域名的拒绝生成策略 可有多个实现,会依次调用,默认为后台可以配置的黑名单策略(即使不配置也会调用) ### IDataStore 短链接生成数据存储以及跳转查询的存储默认实现为`mysql`实现,可以通过实现`IDataStore`,实现存储方式替换为Mongodb、Postgresql、OceanBase等 ### IDomainSelector 如果存在多域名,可以通过实现`IDomainSelector`,对每个url实现不同的短链域名 未实现的话会使用默认域名,配置文件`short-url.domain.default-domain`上配置的参数 ### IConfigCache 通过实现这个类,可以实现缓存数据的获取(可以多实现,本地文件类型为必选,不配置也会使用),比如可以解决雪花算法最大ID、时钟回拨过程中重启导致ID重复的问题(具体解决方式取决于回拨处理方式) ### IDuplicateDetection 如果对一个同个URL需要返回同一个地址,可以通过这设置一个缓存,去缓存搜索,如果存在就去查询之前的记录,如果不存在直接生成新ID(已有根据url的md5或murmur_hash转换url,并存入布隆过滤器,如果存在则去store上判断是否真正存在,如果布隆过滤器返回不存在,则直接生成新的ID) ### IUniqueIdGenerate ID的生成方式,默认提供雪花算法、SID、OID三种,可以自行实现该接口实现短链地址生成算法的替换 ## 启动 ### 启动依赖 启动项目之前需要先启动redis、mysql,需要修改application-dev.yml上相关的mysql和redis配置 启动mysql和redis即可启动项目,后台登录账号密码默认:**admin/123** api账号可以登录后台后,在用户管理界面创建 ### 配置文件参数说明 > 此处说明的配置文件参数均为`short-url`下的配置 - `short-url.unique-id` 项目全局唯一ID配置,比如访问记录的主键ID,具体参数说明可以参考配置文件 - `short-url.jwt` jwt相关配置参数,**务必修改jwt下的key参数**,过期时间根据需要调整 - `short-url.id-gen` 短链ID生成算法,默认使用oid,如果需要使用雪花算法和sid生成短链,相关配置如下(上面所说的接口说明相关配置也在这边配置:`duplicate-detection`、`disabled-strategy`、`config-cache`、`domain-selector`...) ```yaml short-url: id-gen: use-type: # 雪花算法参数 snowflake: # 初始化时间点 epoch: 1288834974657 # 机器ID worker-id: 0 # 数据中心ID data-center-id: 0 # 允许回拨的毫秒数 如果使用借之后的时间 在获取频率不高的情况下 可以回拨很久 总会赶上的 # 如果使用的是新起一个work 那么在回拨没赶上回拨前的最新时间戳之前 不允许再次回拨 同意在获取频率不高的情况下 可以回拨很久 看业务量 # 允许一小时内的回拨 time-offset: 3600000 # 新毫秒生成ID的随机数 不能大于4095 random-sequence-limit: 100 # 时钟回拨处理 0:拒绝时间回拨(time-offset参数会失效),1:借以后的时间,2:创建新的工作ID(在当前工作ID的基础上+16,使用该模式工作ID不允许超过15) backwards-handle: 1 # 最新ID时间戳 默认-1 last-timestamp: -1 # sid参数 sid: # id起始年份 start-year: 2023 # 是否允许时钟回拨 allow-clock-backwards: true # 机器ID worker-id: 0 ``` - `short-url.bloom` 布隆过滤器相关配置参数,文档后面会提到布隆过滤器 - `short-url.domain` 短链域名相关,`short-url.domain.default-domain`为默认域名,既提供给用户的域名,如果需要实现不同url不同域名,需要实现`IDomainSelector`接口,参考上面`接口说明` - `short-url.login` 登录相关,默认登录密码会进行加密,**和jwt密钥一样,这个公/私钥需要替换,每个人的不应该一致**,[公私钥生成可以参考hutool官方文档相关代码](https://hutool.cn/docs/#/crypto/%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86-AsymmetricCrypto) ## ID生成方式说明 ### 雪花算法(Snowflake) 本项目上使用的雪花算法在原本的基础上增加了对时钟回拨的处理,提供两种时钟回拨的处理方式(seata方式未提供,原始雪花算法生成代码基于`hutool`) > 项目主键ID生成使用雪花算法,并且项目的主键配置缓存只是用本地文件缓存,而如果是短链接使用,则可以通过配置文件参数`short-url.id-gen.config-cache`进行配置 > 因为主键使用这个算法,感觉是没有必要上其他的缓存,如果需要其他缓存可以修改`com.gitee.srurl.core.util.UniqueIdUtil`中`@PostConstruct`标注的方法 - 1、借用以后的时间,既在出现时钟回拨后,锁定上一条数据生成的时间,后续ID将使用该时间作为生成时间,如果生成数量大于当前时间的最大ID,则将时间戳`+1`毫秒,继续使用,直到回拨时钟正常,恢复正常逻辑 - 2、使用新的工作ID,在当前处理时钟回拨方式下,工作ID只允许使用0~15,出现时钟回拨后,会将当前工作ID加16生成一个新的工作ID,在时钟恢复正常之前会使用这个工作ID去生成新的ID,时钟正常后,恢复使用原先的工作ID(注:如果使用该方式,在使用新的工作ID过程中,不允许时钟再次回拨,如果发生,会抛异常) - 3、和seata类似,只在项目启动的时候获取ID,后续根据这个ID递增,超过最大ID则时间戳加1,和第一点中的回拨处理类似,但是这个在项目中`未实现`该算法,需要的话可以自行实现 ### SID SID(ShortId),最开始这个ID的目的是为了减少雪花算法生成的ID,用于生成短网址的话,即使转为62进制,雪花算法还是太长了,具体说明可以看`SidConstant`上的注释。 但是这个方式存在一定的问题,即使3秒会上报一次数据,但是由于使用的是小时作为时间戳,如果没来得及发送数据,发生(停电、kill -9)等情况,就会导致重启后ID重复, 和雪花算法不同,项目停止后在启动一般是过了三秒(如果期间又发生了时钟回拨,那就当我没说),即使没上报也影响不大,SID使用小时作为时间戳,这影响就大了, 因此需要使用mysql、mssql、redis、本地文件等控制,避免漏报,但是引入这些,又存在时钟回拨问题,这个ID就很尴尬了。 后续又改了一下,但是不使用了,改了下也没怎么测试,可能会有问题,不建议使用这个,以后说不定有地方需要,先不删这部分代码。 ### OID OID(OrderId)是为了解决SID存在的问题而改变的,由于使用的是顺序ID,并且接入了如本地文件、mysql等方式去预取(或单个)参数,因此即使强制停止也不会出现ID重复的问题,同时使用这种方式,不需要使用定时上报功能。 具体说明在`OidConstant`,默认提供基于文件的ID控制,也提供了基于mysql的控制,可以通过实现`IOidStore`实现其他方式控制,加载方式使用`SPI`加载 ## 创建ID > 创建ID支持两种方式,一种是后台创建一种是通过API调用 - 后台创建 ![创建短链接](./doc/image/user_create_short_url.png) - api接口 api接口需要带上token,token获取流程 **请求地址:** http://ip:port/auth/login/api **请求方式:** POST **请求参数** ```json { "password": "RSA加密后的密码,默认公钥可以看配置文件`application-dev.yml`下`short-url.login.encrypt`的注释(不同用户使用需要替换为自己的公/私钥)", "apiKey": "api用户key" } ``` > api用户不使用userId登录,使用api用户key登录 **返回参数** ```json { "code": 20000, "msg": "操作成功", "data": { "expireTime": "2023-05-19 15:51:41", "userId": 10003, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcGkiOnRydWUsInVzZXJJZCI6MTAwMDMsInVzZXJuYW1lIjoidGVzdGxhwrciLCJpc3MiOiJzaG9ydC11cmwiLCJleHAiOjE2ODQ0ODI3MDF9.OEJyvFMjyoJGS_jfQ6Oi_Bd5gvV41gYYHxVvo5LKFes", "username": "test" } } ``` **参数说明** | 参数名称 | 是否必传 | 说明 | | ---------- |------|-----------------| | code | 是 | 返回状态(20000代表成功) | | msg | 是 | 描述 | | data | 是 | 返回参数 | **data参数** | 参数名称 | 是否必传 | 说明 | | ---------- |------|--------------------| | expireTime | 是 | token过期时间(默认7天后过期) | | userId | 是 | 用户ID | | token | 是 | 用户token | | username | 是 | 用户名 | > 请求接口时,header需要带上token,header的key是`Authorization`value是`Bearer ` + `token`(Bearer后有个空格) **请求参数** ```json { "url": "https://www.baidu.com", "times": -1, "expireTime": "2023-11-11 00:00:00" } ``` **返回参数** * 参数说明 | 参数名称 | 是否必传 | 说明 | | ---------- | ---- | ------------------ | | url | 是 | 原始地址 | | times | 否 | 可访问次数 默认-1,-1代表无限制 | | expireTime | 否 | 过期时间 | ```json { "code": 20000, "msg": "操作成功", "data": { "originUrl": "https://www.baidu.com", "shortUrl": "http://localhost:8898/JMAIkKo", "times": -1, "remainTimes": -1, "disabled": false, "expireTime": "2023-11-11 15:52:29", "createId": null } } ``` * 参数说明 | 参数名称 | 是否必传 | 说明 | | ----------- |------|------------------| | originUrl | 是 | 原始地址 | | shortUrl | 是 | 短链地址 | | times | 是 | 可访问次数,-1代表无限制 | | remainTimes | 是 | 剩余可访问次数,-1代表无限制 | | disabled | 是 | 是否禁用 | | expireTime | 是 | 过期时间 | | createId | 否 | 用户ID(生成ID这边不会返回) | ## 访问说明 ### 缓存 ### ID生成/跳转 目前如果请求生成一个短链,会调用`GenerateShortUrlContext`的`handle`方法,依次调用生成链路方法,最终将url返回。 在调用到`DataStoreHandle`的时候,一旦ID生成,会将ID添加到对应的布隆过滤器中,以便后续的访问,同时删除缓存(如果设置的是只允许一个url对应一个短链,新的调用会刷新URL参数,需要移除之前的缓存数据)。 在处理完返回数据后会延迟再次删除缓存 > 注意:默认使用的是延迟队列延迟1.5s去删除(调用`RedisCacheUtil#removeCache(String shortId, boolean delayRemove)`),这种方式删除会存在很多种意外情况,实际删除需要接入自己公司内部保证双写的方式去调用,因为每个公司不太一致,所以这边只给了最简单的实现。 目前查询URL跳转的缓存有两层,JVM层面和Redis层面,如果查询失败才会去调用`IDataStore`的实现类去查询,然后查询结果入缓存。 ### URL可访问次数 URL可访问次数会缓存至Redis中, 由于这个URL的可访问次数是不准确的,因为`随时可以更新次数`,并且扣减次数的时候是`没有加锁`的,从缓存中取出如果有可访问次数就`-1`然后跳转,可能存在设置100次,实际访问101次的情况,所以如果一个URL需要准确实现访问次数,应该是业务侧进行控制。 **访问次数刷新回store** > 通过自带的定时任务调用`RefreshRemainingVisitTimesTask#refresh()`(5分钟执行一次),从访问记录中读取一天时间内存在访问数据的短链, > 把数据从缓存中查询出来刷新回store。 > 如果有接入如:xxl-job、elastic-job等定时任务,可以修改成走那边,不使用自带的定时任务框架。 ### 布隆过滤器 目前项目的布隆过滤器使用Redis去实现,存在三种(由于是否相同URL初次判断为`md5`和`murmur_hash`二选一,所以实际是两个)。 - 短链ID是否存在的布隆过滤器 > 用于判断短链是否存在,不存在直接返回不存在的信息 - URL是否重复生成 > 如果设置的是相同URL返回同一个短链,则会使用md5或murmur_hash(看配置选用那种方式)进行初次的判断是否存在。 ## 项目截图 ![首页](./doc/image/visit-record.png) ![短链URL](./doc/image/url-record.png) ![访问记录](./doc/image/visit-record.png) ![黑名单列表](./doc/image/blacklist.png) ![用户](./doc/image/user.png)