main.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package main
  2. import (
  3. "encoding/base64"
  4. "flag"
  5. "fmt"
  6. "github.com/gin-gonic/gin"
  7. "github.com/gomodule/redigo/redis"
  8. "log"
  9. "math/rand"
  10. "net/http"
  11. "time"
  12. )
  13. type Response struct {
  14. Code int
  15. Message string
  16. LongUrl string
  17. ShortUrl string
  18. }
  19. type redisPoolConf struct {
  20. maxIdle int
  21. maxActive int
  22. maxIdleTimeout int
  23. host string
  24. password string
  25. db int
  26. handleTimeout int
  27. }
  28. const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  29. const defaultPort int = 8002
  30. const defaultExpire = 90
  31. const defaultRedisConfig = "127.0.0.1:6379"
  32. const defaultLockPrefix = "myurls:lock:"
  33. const defaultRenewal = 1
  34. const secondsPerDay = 24 * 3600
  35. var redisPool *redis.Pool
  36. var redisPoolConfig *redisPoolConf
  37. var redisClient redis.Conn
  38. func main() {
  39. gin.SetMode(gin.ReleaseMode)
  40. router := gin.Default()
  41. router.LoadHTMLGlob("public/*.html")
  42. port := flag.Int("port", defaultPort, "服务端口")
  43. domain := flag.String("domain", "", "短链接域名,必填项")
  44. ttl := flag.Int("ttl", defaultExpire, "短链接有效期,单位(天),默认90天。")
  45. conn := flag.String("conn", defaultRedisConfig, "Redis连接,格式: host:port")
  46. passwd := flag.String("passwd", "", "Redis连接密码")
  47. https := flag.Int("https", 1, "是否返回 https 短链接")
  48. flag.Parse()
  49. if *domain == "" {
  50. flag.Usage()
  51. log.Fatalln("缺少关键参数")
  52. }
  53. redisPoolConfig = &redisPoolConf{
  54. maxIdle: 1024,
  55. maxActive: 1024,
  56. maxIdleTimeout: 30,
  57. host: *conn,
  58. password: *passwd,
  59. db: 0,
  60. handleTimeout: 30,
  61. }
  62. initRedisPool()
  63. router.GET("/", func(context *gin.Context) {
  64. context.HTML(http.StatusOK, "index.html", gin.H{
  65. "title": "MyUrls",
  66. })
  67. })
  68. router.POST("/short", func(context *gin.Context) {
  69. res := &Response{
  70. Code: 1,
  71. Message: "",
  72. LongUrl: "",
  73. ShortUrl: "",
  74. }
  75. longUrl := context.PostForm("longUrl")
  76. shortKey := context.PostForm("shortKey")
  77. if longUrl == "" {
  78. res.Message = "longUrl为空"
  79. context.JSON(400, *res)
  80. return
  81. }
  82. _longUrl, _ := base64.StdEncoding.DecodeString(longUrl)
  83. longUrl = string(_longUrl)
  84. res.LongUrl = longUrl
  85. // 根据有没有填写 short key,分别执行
  86. if shortKey != "" {
  87. redisClient := redisPool.Get()
  88. // 检测短链是否已存在
  89. _exists, _ := redis.String(redisClient.Do("get", shortKey))
  90. if _exists != "" && _exists != longUrl {
  91. res.Message = "短链接已存在,请更换key"
  92. context.JSON(400, *res)
  93. return
  94. }
  95. // 存储
  96. _, _ = redisClient.Do("set", shortKey, longUrl)
  97. } else {
  98. shortKey = longToShort(longUrl, *ttl*secondsPerDay)
  99. }
  100. protocol := "http://"
  101. if *https != 0 {
  102. protocol = "https://"
  103. }
  104. res.ShortUrl = protocol + *domain + "/" + shortKey
  105. // context.Header("Access-Control-Allow-Origin", "*")
  106. context.JSON(200, *res)
  107. })
  108. router.GET("/:shortKey", func(context *gin.Context) {
  109. shortKey := context.Param("shortKey")
  110. longUrl := shortToLong(shortKey)
  111. if longUrl == "" {
  112. context.String(http.StatusNotFound, "短链接不存在或已过期")
  113. } else {
  114. context.Redirect(http.StatusMovedPermanently, longUrl)
  115. }
  116. })
  117. router.Run(fmt.Sprintf(":%d", *port))
  118. }
  119. // 短链接转长链接
  120. func shortToLong(shortKey string) string {
  121. redisClient = redisPool.Get()
  122. defer redisClient.Close()
  123. longUrl, _ := redis.String(redisClient.Do("get", shortKey))
  124. // 获取到长链接后,续命1天。每天仅允许续命1次。
  125. if longUrl != "" {
  126. renew(shortKey)
  127. }
  128. return longUrl
  129. }
  130. // 长链接转短链接
  131. func longToShort(longUrl string, ttl int) string {
  132. redisClient = redisPool.Get()
  133. defer redisClient.Close()
  134. // 是否生成过该长链接对应短链接
  135. _existsKey, _ := redis.String(redisClient.Do("get", longUrl))
  136. if _existsKey != "" {
  137. _, _ = redisClient.Do("expire", _existsKey, ttl)
  138. log.Println("Hit cache: " + _existsKey)
  139. return _existsKey
  140. }
  141. // 重试三次
  142. var shortKey string
  143. for i := 0; i < 3; i++ {
  144. shortKey = generate(7)
  145. _existsLongUrl, _ := redis.String(redisClient.Do("get", shortKey))
  146. if _existsLongUrl == "" {
  147. break
  148. }
  149. }
  150. if shortKey != "" {
  151. _, _ = redisClient.Do("mset", shortKey, longUrl, longUrl, shortKey)
  152. _, _ = redisClient.Do("expire", shortKey, ttl)
  153. _, _ = redisClient.Do("expire", longUrl, secondsPerDay)
  154. }
  155. return shortKey
  156. }
  157. // 产生一个63位随机整数
  158. func generate(bits int) string {
  159. b := make([]byte, bits)
  160. currentTime := time.Now().UnixNano()
  161. rand.Seed(currentTime)
  162. for i := range b {
  163. b[i] = letterBytes[rand.Intn(len(letterBytes))]
  164. }
  165. return string(b)
  166. }
  167. func initRedisPool() {
  168. // 建立连接池
  169. redisPool = &redis.Pool{
  170. MaxIdle: redisPoolConfig.maxIdle,
  171. MaxActive: redisPoolConfig.maxActive,
  172. IdleTimeout: time.Duration(redisPoolConfig.maxIdleTimeout) * time.Second,
  173. Wait: true,
  174. Dial: func() (redis.Conn, error) {
  175. con, err := redis.Dial("tcp", redisPoolConfig.host,
  176. redis.DialPassword(redisPoolConfig.password),
  177. redis.DialDatabase(redisPoolConfig.db),
  178. redis.DialConnectTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second),
  179. redis.DialReadTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second),
  180. redis.DialWriteTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second))
  181. if err != nil {
  182. return nil, err
  183. }
  184. return con, nil
  185. },
  186. }
  187. }
  188. func renew(shortKey string) {
  189. redisClient = redisPool.Get()
  190. defer redisClient.Close()
  191. // 加锁
  192. lockKey := defaultLockPrefix + shortKey
  193. lock, _ := redis.Int(redisClient.Do("setnx", lockKey, 1))
  194. if lock == 1 {
  195. // 设置锁过期时间
  196. _, _ = redisClient.Do("expire", lockKey, defaultRenewal*secondsPerDay)
  197. // 续命
  198. ttl, _ := redis.Int(redisClient.Do("ttl", shortKey))
  199. _, _ = redisClient.Do("expire", shortKey, ttl+defaultRenewal*secondsPerDay)
  200. }
  201. }