<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      使用Go語言開發一個短鏈接服務:五、添加和獲取短鏈接

      章節

      ?使用Go語言開發一個短鏈接服務:一、基本原理  

      ?使用Go語言開發一個短鏈接服務:二、架構設計

      ?使用Go語言開發一個短鏈接服務:三、項目目錄結構設計

      ?使用Go語言開發一個短鏈接服務:四、生成code算法

      ?使用Go語言開發一個短鏈接服務:五、添加和獲取短鏈接

      ?使用Go語言開發一個短鏈接服務:六、鏈接跳轉

      ??Gitee https://gitee.com/alxps/short_link

        Github https://github.com/1911860538/short_link

       

        上一篇說明了短鏈接code的生成算法,這一篇講述怎么添加和獲取短鏈接。

         本篇涉及的代碼看這里https://gitee.com/alxps/short_link/tree/master/app/server/service

       

      添加短鏈接

      ??簡單來說就是登錄用戶,發來長鏈接,我們生成短鏈接code,并保存這條數據。分四個步驟:

      ????1、校驗long_url合法性

      ????2、檢查該用戶是不是已經為long_url生成短鏈接

      ????3、生成短鏈接code

      ????4、保存到數據庫

      ??步驟1,檢驗url合法性包括兩項,是否為一個合法的http或https url,以及http請求url是否能正常響應。至于http請求url,我們優先使用head請求,如果head請求返回405,則再使用get請求。因為head請求相比get請求更清涼,只傳回響應頭,也就是資源的“元信息”,但有可能部分服務器不支持head請求。

      ??步驟2,很簡單,到數據庫查詢user_id和long_url的數據是否存在。

      ??步驟3,生成code,看上一篇, 使用Go語言開發一個短鏈接服務:四、生成code算法

      ??步驟4,將數據保存到數據庫,如果code在數據庫已存在,則重新生成code,遞歸,直到code不重復。

      ??上代碼,由于添加短鏈接的handler只是負責http入參和出參的處理,代碼不貼,直接看service

      app/server/service/add_link.go

      package service
      
      import (
      	"context"
      	"crypto/md5"
      	"encoding/hex"
      	"fmt"
      	"hash/fnv"
      	"io"
      	"net/http"
      	"net/url"
      	"strconv"
      	"time"
      	"unsafe"
      
      	"github.com/1911860538/short_link/app/component"
      	"github.com/1911860538/short_link/config"
      )
      
      type AddLinkSvc struct {
      	Database component.DatabaseItf
      }
      
      type AddLinkParams struct {
      	UserId   string
      	LongUrl  string
      	Deadline time.Time
      }
      
      type AddLinkRes struct {
      	StatusCode int
      	Msg        string
      
      	Code string
      }
      
      const (
      	msgUrlInvalid = "不是一個合法的http或https鏈接"
      	msgUrlRespErr = "鏈接請求未能正常響應"
      )
      
      var (
      	confLongUrlConnTimeout = time.Duration(config.Conf.Core.LongUrlConnTimeout) * time.Second
      	confExpiredKeepHours   = time.Duration(config.Conf.Core.ExpiredKeepDays*24) * time.Hour
      )
      
      func (s *AddLinkSvc) Do(ctx context.Context, params AddLinkParams) (AddLinkRes, error) {
      	// 檢查url合法性
      	u, err := url.ParseRequestURI(params.LongUrl)
      	if err != nil {
      		return s.badRequest(msgUrlInvalid)
      	}
      	if u.Scheme != "http" && u.Scheme != "https" {
      		return s.badRequest(msgUrlInvalid)
      	}
      	client := http.Client{
      		Timeout: confLongUrlConnTimeout,
      	}
      	headResp, err := client.Head(u.String())
      	if err != nil {
      		return s.badRequest(msgUrlRespErr)
      	}
      	if headResp.Body != nil {
      		defer headResp.Body.Close()
      	}
      	respOk := headResp.StatusCode == http.StatusOK
      	// 使用GET,部分服務器不支持HEAD請求
      	if !respOk && headResp.StatusCode == http.StatusMethodNotAllowed {
      		getResp, err := client.Get(u.String())
      		if err != nil {
      			return s.badRequest(msgUrlRespErr)
      		}
      		if getResp.Body != nil {
      			defer getResp.Body.Close()
      		}
      		respOk = getResp.StatusCode == http.StatusOK
      	}
      	if !respOk {
      		return s.badRequest(msgUrlRespErr)
      	}
      
      	// 檢查這個userId是不是已經生成了此longUrl的code
      	filter := map[string]any{
      		"user_id":  params.UserId,
      		"long_url": params.LongUrl,
      	}
      	oldLink, err := s.Database.Get(ctx, filter)
      	if err != nil {
      		return s.internalErr(err)
      	}
      	if oldLink != nil && !oldLink.Expired() {
      		return s.codeConflicted(oldLink.Code)
      	}
      
      	// 生成longUrl對應的code
      	code, err := GenCode(params.UserId, params.LongUrl, "")
      	if err != nil {
      		return s.internalErr(err)
      	}
      
      	// 保存到數據庫,這里要注意可能和數據庫code沖突
      	var ttlTime time.Time
      	if params.Deadline.IsZero() {
      		ttlTime = time.Time{}
      	} else {
      		ttlTime = params.Deadline.Add(confExpiredKeepHours)
      	}
      	link := &component.Link{
      		UserId:    params.UserId,
      		Code:      code,
      		Salt:      "",
      		LongUrl:   params.LongUrl,
      		Deadline:  params.Deadline,
      		TtlTime:   ttlTime,
      		CreatedAt: time.Now().UTC(),
      		UpdatedAt: time.Now().UTC(),
      	}
      
      	if err := s.trySaveLink(ctx, link); err != nil {
      		return s.internalErr(err)
      	}
      
      	return s.ok(link.Code)
      }
      
      func (s *AddLinkSvc) ok(code string) (AddLinkRes, error) {
      	return AddLinkRes{
      		StatusCode: http.StatusCreated,
      		Code:       code,
      	}, nil
      }
      
      func (s *AddLinkSvc) badRequest(errMsg string) (AddLinkRes, error) {
      	return AddLinkRes{
      		StatusCode: http.StatusBadRequest,
      		Msg:        errMsg,
      	}, nil
      }
      
      func (s *AddLinkSvc) codeConflicted(code string) (AddLinkRes, error) {
      	return AddLinkRes{
      		StatusCode: http.StatusConflict,
      		Msg:        fmt.Sprintf("你已對該鏈接已生成了對應的短鏈接,短鏈接code為:%s", code),
      	}, nil
      }
      
      func (s *AddLinkSvc) internalErr(err error) (AddLinkRes, error) {
      	return AddLinkRes{
      		StatusCode: http.StatusInternalServerError,
      	}, err
      }
      
      func (s *AddLinkSvc) trySaveLink(ctx context.Context, link *component.Link) error {
      	_, existed, err := s.Database.Create(ctx, link)
      	if err != nil {
      		return err
      	}
      	if !existed {
      		return nil
      	}
      
      	nowTimestampStr := strconv.FormatInt(time.Now().UnixMilli(), 10)
      	link.Salt = nowTimestampStr
      	link.Code, err = GenCode(link.UserId, link.Code, nowTimestampStr)
      	if err != nil {
      		return err
      	}
      
      	return s.trySaveLink(ctx, link)
      }
      
      const letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      
      // GenCode
      /*
      下面一通計算,
      和隨機生成字母數字code的區別是,
      盡量保證同樣的userId+longUrl每次生成的code一樣,
      如果userId+longUrl生成了數據庫已有的code,
      則加上當前時間戳字符串作為鹽salt,
      遞歸,直到生成的code數據庫中沒有
      */
      func GenCode(userId string, longUlr string, salt string) (string, error) {
      	// 首先對userId+longUrl+salt md5 主要為了防止longUrl包含漢字等字符串
      	hasher := md5.New()
      	if _, err := io.WriteString(hasher, userId+longUlr+salt); err != nil {
      		return "", err
      	}
      	hashStr := hex.EncodeToString(hasher.Sum(nil))
      
      	stepLen := len(hashStr) / confCodeLen
      	remain := len(hashStr) % confCodeLen
      	if remain > 0 {
      		stepLen += 1
      	}
      	lettersLen := uint32(len(letters))
      	b := make([]byte, confCodeLen)
      
      	for i := 0; i < confCodeLen; i++ {
      		// 根據要生成的code長度,切分md5字符串
      		var piece string
      		if remain > 0 && i == confCodeLen-1 {
      			piece = hashStr[i*stepLen : i*stepLen+remain]
      		} else {
      			piece = hashStr[i*stepLen : i*stepLen+stepLen]
      		}
      
      		// 為切片元素生成對應的整形數值
      		h := fnv.New32a()
      		pieceBytes := unsafe.Slice(unsafe.StringData(piece), len(piece))
      		if _, err := h.Write(pieceBytes); err != nil {
      			return "", err
      		}
      		pieceHash32 := h.Sum32()
      
      		// 切片字符的整形,取len(letters)余數,并取letters索引為該余數的letter
      		letterIdx := pieceHash32 % lettersLen
      		b[i] = letters[letterIdx]
      	}
      
      	return unsafe.String(unsafe.SliceData(b), len(b)), nil
      }

       

      獲取短鏈接

      ??用戶登錄情況下,輸入code或者長鏈接url,獲取鏈接信息(code/long_url/deadline)。直接上代碼

      app/server/service/get_link.go

      package service
      
      import (
      	"context"
      	"net/http"
      	"time"
      
      	"github.com/1911860538/short_link/app/component"
      )
      
      type GetLinkSvc struct {
      	Database component.DatabaseItf
      }
      
      type GetLinkParams struct {
      	UserId  string
      	Code    string
      	LongUrl string
      }
      
      type GetLinkRes struct {
      	StatusCode int
      	Msg        string
      
      	Code     string
      	LongUrl  string
      	Deadline time.Time
      }
      
      func (s *GetLinkSvc) Do(ctx context.Context, params GetLinkParams) (GetLinkRes, error) {
      	filter := make(map[string]any)
      	if params.UserId != "" {
      		filter["user_id"] = params.UserId
      	}
      	if params.Code != "" {
      		filter["code"] = params.Code
      	}
      	if params.LongUrl != "" {
      		filter["long_url"] = params.LongUrl
      	}
      
      	link, err := s.Database.Get(ctx, filter)
      	if err != nil {
      		return GetLinkRes{
      			StatusCode: http.StatusInternalServerError,
      		}, err
      	}
      
      	if link == nil {
      		return GetLinkRes{
      			StatusCode: http.StatusNotFound,
      			Msg:        "數據不存在",
      		}, nil
      	}
      
      	return GetLinkRes{
      		StatusCode: http.StatusOK,
      		Code:       link.Code,
      		LongUrl:    link.LongUrl,
      		Deadline:   link.Deadline,
      	}, nil
      }

       

      總結

      ??下一篇,服務核心邏輯,短鏈接跳轉到長鏈接,敬請期待~

      ??

      posted @ 2024-03-26 12:05  ALXPS  閱讀(216)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产高清在线男人的天堂| 无码专区人妻系列日韩精品| 成人免费视频一区二区三区| 亚洲精品尤物av在线网站| 国内永久福利在线视频图片| 男女爽爽无遮挡午夜视频| 熟女精品视频一区二区三区| 9丨精品国产高清自在线看| 欧美人与动zozo在线播放| 日韩精品一区二区亚洲专区| 国产在线永久视频| 北流市| 久久精品成人无码观看免费| 婷婷色综合成人成人网小说| 成年人尤物视频在线观看| 免费无遮挡毛片中文字幕| 国产亚洲精品久久久久久无亚洲| 精品日本乱一区二区三区| 日韩欧美卡一卡二卡新区| 国产亚洲综合另类色专区| 国产精品欧美福利久久| 中文字幕无码免费久久| 国产亚洲精品岁国产精品| 少妇又爽又刺激视频| 波多野结衣av高清一区二区三区 | 40岁大乳的熟妇在线观看| 天堂mv在线mv免费mv香蕉| 太仓市| 欧洲免费一区二区三区视频| 亚洲中文久久久精品无码| 欧美性猛交xxxx乱大交丰满| 国产精品亚洲中文字幕| 77777亚洲午夜久久多人| 国产精品午夜福利片国产| 国产精品高潮无码毛片| 精品人妻午夜一区二区三区四区| 日日摸夜夜添夜夜添国产三级| 国产超碰无码最新上传| 日韩中文字幕v亚洲中文字幕| 精品人妻伦一二三区久久| 国产精品一码二码三码|