main.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. package main
  2. import (
  3. "encoding/base64"
  4. "flag"
  5. "fmt"
  6. "log"
  7. "math/rand"
  8. "net/http"
  9. "os"
  10. "path"
  11. "time"
  12. "github.com/gin-gonic/gin"
  13. "github.com/gomodule/redigo/redis"
  14. "github.com/sirupsen/logrus"
  15. )
  16. type Response struct {
  17. Code int
  18. Message string
  19. LongUrl string
  20. ShortUrl string
  21. }
  22. type redisPoolConf struct {
  23. maxIdle int
  24. maxActive int
  25. maxIdleTimeout int
  26. host string
  27. password string
  28. db int
  29. handleTimeout int
  30. }
  31. const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  32. const defaultPort int = 8002
  33. const defaultExpire = 180
  34. const defaultRedisConfig = "127.0.0.1:6379"
  35. const defaultLockPrefix = "myurls:lock:"
  36. const defaultRenewal = 1
  37. const secondsPerDay = 24 * 3600
  38. var redisPool *redis.Pool
  39. var redisPoolConfig *redisPoolConf
  40. var redisClient redis.Conn
  41. func main() {
  42. gin.SetMode(gin.ReleaseMode)
  43. router := gin.Default()
  44. // Log 收集中间件
  45. router.Use(LoggerToFile())
  46. router.LoadHTMLGlob("public/*.html")
  47. port := flag.Int("port", defaultPort, "服务端口")
  48. domain := flag.String("domain", "", "短链接域名,必填项")
  49. ttl := flag.Int("ttl", defaultExpire, "短链接有效期,单位(天),默认180天。")
  50. conn := flag.String("conn", defaultRedisConfig, "Redis连接,格式: host:port")
  51. passwd := flag.String("passwd", "", "Redis连接密码")
  52. https := flag.Int("https", 1, "是否返回 https 短链接")
  53. flag.Parse()
  54. if *domain == "" {
  55. flag.Usage()
  56. log.Fatalln("缺少关键参数")
  57. }
  58. redisPoolConfig = &redisPoolConf{
  59. maxIdle: 1024,
  60. maxActive: 1024,
  61. maxIdleTimeout: 30,
  62. host: *conn,
  63. password: *passwd,
  64. db: 0,
  65. handleTimeout: 30,
  66. }
  67. initRedisPool()
  68. router.GET("/", func(context *gin.Context) {
  69. context.HTML(http.StatusOK, "index.html", gin.H{
  70. "title": "MyUrls",
  71. })
  72. })
  73. router.POST("/short", func(context *gin.Context) {
  74. res := &Response{
  75. Code: 1,
  76. Message: "",
  77. LongUrl: "",
  78. ShortUrl: "",
  79. }
  80. longUrl := context.PostForm("longUrl")
  81. shortKey := context.PostForm("shortKey")
  82. if longUrl == "" {
  83. res.Code = 0
  84. res.Message = "longUrl为空"
  85. context.JSON(200, *res)
  86. return
  87. }
  88. _longUrl, _ := base64.StdEncoding.DecodeString(longUrl)
  89. longUrl = string(_longUrl)
  90. res.LongUrl = longUrl
  91. // 根据有没有填写 short key,分别执行
  92. if shortKey != "" {
  93. redisClient := redisPool.Get()
  94. // 检测短链是否已存在
  95. _exists, _ := redis.String(redisClient.Do("get", shortKey))
  96. if _exists != "" && _exists != longUrl {
  97. res.Code = 0
  98. res.Message = "短链接已存在,请更换key"
  99. context.JSON(200, *res)
  100. return
  101. }
  102. // 存储
  103. _, _ = redisClient.Do("set", shortKey, longUrl)
  104. } else {
  105. shortKey = longToShort(longUrl, *ttl*secondsPerDay)
  106. }
  107. protocol := "http://"
  108. if *https != 0 {
  109. protocol = "https://"
  110. }
  111. res.ShortUrl = protocol + *domain + "/" + shortKey
  112. // context.Header("Access-Control-Allow-Origin", "*")
  113. context.JSON(200, *res)
  114. })
  115. router.GET("/:shortKey", func(context *gin.Context) {
  116. shortKey := context.Param("shortKey")
  117. longUrl := shortToLong(shortKey)
  118. if longUrl == "" {
  119. context.String(http.StatusNotFound, "短链接不存在或已过期")
  120. } else {
  121. context.Redirect(http.StatusMovedPermanently, longUrl)
  122. }
  123. })
  124. router.Run(fmt.Sprintf(":%d", *port))
  125. }
  126. // 短链接转长链接
  127. func shortToLong(shortKey string) string {
  128. redisClient = redisPool.Get()
  129. defer redisClient.Close()
  130. longUrl, _ := redis.String(redisClient.Do("get", shortKey))
  131. // 获取到长链接后,续命1天。每天仅允许续命1次。
  132. if longUrl != "" {
  133. renew(shortKey)
  134. }
  135. return longUrl
  136. }
  137. // 长链接转短链接
  138. func longToShort(longUrl string, ttl int) string {
  139. redisClient = redisPool.Get()
  140. defer redisClient.Close()
  141. // 是否生成过该长链接对应短链接
  142. _existsKey, _ := redis.String(redisClient.Do("get", longUrl))
  143. if _existsKey != "" {
  144. _, _ = redisClient.Do("expire", _existsKey, ttl)
  145. log.Println("Hit cache: " + _existsKey)
  146. return _existsKey
  147. }
  148. // 重试三次
  149. var shortKey string
  150. for i := 0; i < 3; i++ {
  151. shortKey = generate(7)
  152. _existsLongUrl, _ := redis.String(redisClient.Do("get", shortKey))
  153. if _existsLongUrl == "" {
  154. break
  155. }
  156. }
  157. if shortKey != "" {
  158. _, _ = redisClient.Do("mset", shortKey, longUrl, longUrl, shortKey)
  159. _, _ = redisClient.Do("expire", shortKey, ttl)
  160. _, _ = redisClient.Do("expire", longUrl, secondsPerDay)
  161. }
  162. return shortKey
  163. }
  164. // 续命
  165. func renew(shortKey string) {
  166. redisClient = redisPool.Get()
  167. defer redisClient.Close()
  168. // 加锁
  169. lockKey := defaultLockPrefix + shortKey
  170. lock, _ := redis.Int(redisClient.Do("setnx", lockKey, 1))
  171. if lock == 1 {
  172. // 设置锁过期时间
  173. _, _ = redisClient.Do("expire", lockKey, defaultRenewal*secondsPerDay)
  174. // 续命
  175. ttl, _ := redis.Int(redisClient.Do("ttl", shortKey))
  176. if ttl != -1 {
  177. _, _ = redisClient.Do("expire", shortKey, ttl+defaultRenewal*secondsPerDay)
  178. }
  179. }
  180. }
  181. // 产生一个63位随机整数
  182. func generate(bits int) string {
  183. b := make([]byte, bits)
  184. currentTime := time.Now().UnixNano()
  185. rand.Seed(currentTime)
  186. for i := range b {
  187. b[i] = letterBytes[rand.Intn(len(letterBytes))]
  188. }
  189. return string(b)
  190. }
  191. // 定义 logger
  192. func Logger() *logrus.Logger {
  193. logFilePath := ""
  194. if dir, err := os.Getwd(); err == nil {
  195. logFilePath = dir + "/logs/"
  196. }
  197. if err := os.MkdirAll(logFilePath, 0777); err != nil {
  198. fmt.Println(err.Error())
  199. }
  200. logFileName := "access.log"
  201. //日志文件
  202. fileName := path.Join(logFilePath, logFileName)
  203. if _, err := os.Stat(fileName); err != nil {
  204. if _, err := os.Create(fileName); err != nil {
  205. fmt.Println(err.Error())
  206. }
  207. }
  208. //写入文件
  209. src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
  210. if err != nil {
  211. fmt.Println("err", err)
  212. }
  213. //实例化
  214. logger := logrus.New()
  215. //设置输出
  216. logger.SetOutput(src)
  217. // logger.Out = src
  218. //设置日志级别
  219. logger.SetLevel(logrus.DebugLevel)
  220. //设置日志格式
  221. logger.Formatter = &logrus.JSONFormatter{}
  222. return logger
  223. }
  224. // 文件日志
  225. func LoggerToFile() gin.HandlerFunc {
  226. logger := Logger()
  227. return func(c *gin.Context) {
  228. logMap := make(map[string]interface{})
  229. // 开始时间
  230. startTime := time.Now()
  231. logMap["startTime"] = startTime.Format("2006-01-02 15:04:05")
  232. // 处理请求
  233. c.Next()
  234. // 结束时间
  235. endTime := time.Now()
  236. logMap["endTime"] = endTime.Format("2006-01-02 15:04:05")
  237. // 执行时间
  238. logMap["latencyTime"] = endTime.Sub(startTime).Microseconds()
  239. // 请求方式
  240. logMap["reqMethod"] = c.Request.Method
  241. // 请求路由
  242. logMap["reqUri"] = c.Request.RequestURI
  243. // 状态码
  244. logMap["statusCode"] = c.Writer.Status()
  245. // 请求IP
  246. logMap["clientIP"] = c.ClientIP()
  247. // 请求 UA
  248. logMap["clientUA"] = c.Request.UserAgent()
  249. //日志格式
  250. // logJson, _ := json.Marshal(logMap)
  251. // logger.Info(string(logJson))
  252. logger.WithFields(logrus.Fields{
  253. "startTime": logMap["startTime"],
  254. "endTime": logMap["endTime"],
  255. "latencyTime": logMap["latencyTime"],
  256. "reqMethod": logMap["reqMethod"],
  257. "reqUri": logMap["reqUri"],
  258. "statusCode": logMap["statusCode"],
  259. "clientIP": logMap["clientIP"],
  260. "clientUA": logMap["clientUA"],
  261. }).Info()
  262. }
  263. }
  264. // redis 连接池
  265. func initRedisPool() {
  266. // 建立连接池
  267. redisPool = &redis.Pool{
  268. MaxIdle: redisPoolConfig.maxIdle,
  269. MaxActive: redisPoolConfig.maxActive,
  270. IdleTimeout: time.Duration(redisPoolConfig.maxIdleTimeout) * time.Second,
  271. Wait: true,
  272. Dial: func() (redis.Conn, error) {
  273. con, err := redis.Dial("tcp", redisPoolConfig.host,
  274. redis.DialPassword(redisPoolConfig.password),
  275. redis.DialDatabase(redisPoolConfig.db),
  276. redis.DialConnectTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second),
  277. redis.DialReadTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second),
  278. redis.DialWriteTimeout(time.Duration(redisPoolConfig.handleTimeout)*time.Second))
  279. if err != nil {
  280. return nil, err
  281. }
  282. return con, nil
  283. },
  284. }
  285. }