首页 > 其他分享 >使用Go语言开发一个短链接服务:三、项目目录结构设计

使用Go语言开发一个短链接服务:三、项目目录结构设计

时间:2024-03-26 12:11:06浏览次数:37  
标签:app component go handler link Go 链接 结构设计

章节

使用Go语言开发一个短链接服务:一、基本原理  

使用Go语言开发一个短链接服务:二、架构设计

使用Go语言开发一个短链接服务:三、项目目录结构设计

使用Go语言开发一个短链接服务:四、生成code算法

使用Go语言开发一个短链接服务:五、添加和获取短链接

使用Go语言开发一个短链接服务:六、链接跳转

  源码:https://gitee.com/alxps/short_link

  

  上一篇我们讨论了项目基本架构以及组件选择。这篇我们讲一下项目目录结构。

 

目录结构

  目录结构大致如下:

.
├── app
│   ├── component                         # 组件
│   │   ├── cache.go                      # 缓存interface
│   │   ├── cache_redis.go                # 缓存redis实现
│   │   ├── database.go                   # 数据库interface
│   │   ├── database_mongodb.go           # 数据库mongodb实现
│   │   └── lifespan.go                   # 组件生命周期管理
│   └── server                            # Web业务主要逻辑
│       ├── handler                       # handler层
│       │   ├── add_link.go               # 添加短链接handler
│       │   ├── get_link.go               # 获取链接详情handler
│       │   ├── jwt.go                    # handler处理认证jwt公共逻辑
│       │   └── redirect.go               # 短链接跳转handler
│       ├── middleware                    # 中间件
│       │   └── auth.go                   # 认证中间件
│       ├── router.go                     # url与handler路由
│       └── service                       # handler对于的业务逻辑实现
│           ├── add_link.go
│           ├── get_link.go
│           └── redirect.go
├── cmd
│   └── server
│       └── main.go                       # http server启动文件
├── config                                # 配置文件
│   ├── config_dev.json
│   ├── config.go
│   ├── config_prod.json
│   └── init.go
├── Dockerfile
├── docs
│   └── api_doc.json                      # api接口文档
├── go.mod
├── go.sum
├── LICENSE
├── Makefile
├── README.md
├── tests
│   └── service                           # app/server/service对应的单元测试
│       ├── add_link_test.go
│       ├── get_link_test.go
│       ├── mock_cache.go                 # app/component/cache interface单元测试mock实现
│       ├── mock_database.go              # app/component/database interface单元测试mock实现
│       ├── mock_lifespan.go              # app/component/lifespan interface单元测试mock实现
│       └── redirect_test.go

  具体代码点击这里

  上面代码目录中主要讲一下app内几个目录作用。

    1、app/component为项目依赖的组件,组件方法interface以及实现。

    2、app/server/handler为接口层,负责request参数处理,调用service逻辑并处理response数据

    3、app/server/service,业务逻辑实现

  app/component这里定义组件相关interface,组要为了方便单元测试。不由想起鲁迅的话:“Don't design with interfaces, discover them.”。比如我们添加短链接信息add_link,依赖数据库。

app/server/service/add_link.go

package service

import (
    "context"
    "net/http"

    "github.com/1911860538/short_link/app/component"
)

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
}

// ...省略代码

func (s *AddLinkSvc) Do(ctx context.Context, params AddLinkParams) (AddLinkRes, error) {
   // ...省略代码
   return AddLinkRes{
		StatusCode: http.StatusCreated,
		Code:       code,
	}, nil
}

 app/component/database.go

package component

import (
	"context"
	"log"

	"github.com/1911860538/short_link/config"
)

type DatabaseItf interface {
	Lifespan

	Create(ctx context.Context, link *Link) (id string, codeExisted bool, err error)
	Get(ctx context.Context, params map[string]any) (*Link, error)
}

var Database DatabaseItf

func init() {
	switch databaseType := config.Conf.Server.DbType; databaseType {
	case "mongodb":
		Database = DefaultMongoDB
	default:
		log.Fatalf("不支持的数据库组件:%s\n", databaseType)
	}
}

  AddLinkSvc无需依赖特定数据库,只需实现了对应interface。在实际handler逻辑中我们使用实现了interface的mongodb

app/server/handler/add_link.go

package handler

import (
	"log/slog"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/1911860538/short_link/app/component"
	"github.com/1911860538/short_link/app/server/service"
)

var addLinkSvc = service.AddLinkSvc{
	Database: component.Database,
}

// ...省略代码

func AddLinkHandler(c *gin.Context) {
	// ...省略代码
	res, err := addLinkSvc.Do(c.Request.Context(), addLinkParams)
	// ...省略代码
}

  而对AddLinkSvc单元测试时,使用实现了interface的mock Database。不由想起刚入门go时,函数逻辑有数据库相关操作,要对这个函数写单元测试时的心情:

停杯投箸不能食,拔剑四顾心茫然。
欲渡黄河冰塞川,将登太行雪满山。

 

实现API接口

  之前文章介绍过短链接的基本原理( 使用Go语言开发一个短链接服务:一、基本原理 ),这里简单回顾一下。用户有一个长链接接Looooong,需要一个短的链接S映射并跳转到Looooong。因此需要实现下面3个接口

    1、用户申请短链接,输入长链接,服务返回对应短链接,并保存两者映射关系;

    2、用户获取自己申请的短链接详情;

    3、跳转服务,用户申请获得的短链接经本服务调转到对应的长链接

app/server/router.go

package server

import (
	"github.com/gin-gonic/gin"

	"github.com/1911860538/short_link/app/server/handler"
	"github.com/1911860538/short_link/app/server/middleware"
)

// Route 路由注册
func Route(e *gin.Engine) {
	// 核心,跳转服务
	e.GET("/:code", handler.RedirectHandler)

	// 短链接管理
	linKGroupV1 := e.Group("/api/v1/links")
	linKGroupV1.Use(middleware.JwtMiddleware)
	{
		linKGroupV1.POST("", handler.AddLinkHandler)
		linKGroupV1.GET("", handler.GetLinkHandler)
	}
}

 

数据库设计

  orm结构体

app/component/database_mongodb.go

// Link
/*
对于添加索引操作,官方go驱动不能在结构体tag赋值完成
需要在该collection创建了,并包含至少一个document,才能添加索引

code唯一索引: db.links.createIndex({"code": 1}, {"unique": true})
user_id普通索引: db.links.createIndex({"user_id": 1})
long_url普通索引: db.links.createIndex({"long_url": 1})
ttl_time ttl索引: db.links.createIndex({"ttl_time": 1}, {"expireAfterSeconds": 7200})
// ttl索引会增加数据库负载。如果不使用ttl索引,可以用定时脚本任务删除无用数据
*/
type Link struct {
	Id        string    `bson:"_id,omitempty"`
	UserId    string    `bson:"user_id"`      // 用户id
	Code      string    `bson:"code"`         // 短链接code
	Salt      string    `bson:"salt"`         // 生成code算法可能需要的盐
	LongUrl   string    `bson:"long_url"`     // 跳转目标长链接
	Deadline  time.Time `bson:"deadline"`     // 短链接有效期
	TtlTime   time.Time `bson:"ttl_time"`     // 本条数据删除时间
	CreatedAt time.Time `bson:"created_at"`
	UpdatedAt time.Time `bson:"updated_at"`
}

  对应mongodb,database为short_link,collection为links。

>> use short_link
switched to db short_link

>> db.links.find().sort({"created_at": -1}).limit(1)

{
  _id: ObjectId('65f40d43b5f826547e721ef7'),
  user_id: '1f70a466-1449-4676-b2d7-2037341c718e',
  code: 'Y64CyP',
  salt: '',
  long_url: 'https://juejin.cn/post/7346009985770471451',
  deadline: 2024-03-15T19:12:51.000Z,
  ttl_time: 2024-04-14T19:12:51.000Z,
  created_at: 2024-03-15T08:56:35.778Z,
  updated_at: 2024-03-15T08:56:35.778Z
}

 

用户系统

  本项目接口API中,添加链接和过去链接两个接口需要用户登录后才能操作,这两个接口使用了中间件验证请求头必须有认证JsonWebToken。

  然而,项目并没有实现用户登录注册等逻辑。原因有三

    1、项目主要用来展示短链接的实现,登录注册非主要目的

    2、实际项目中,用户服务通常是独立于其它服务的基础服务,而用到用户信息的服务通常只实现认证逻辑

    3、我不想写

  认证jwt解析使用了,github.com/golang-jwt/jwt/v5

 

总结

  好了,上面大致介绍了项目主要目录以及各目录职责。下一篇将阐述生成短链接code的算法。

  

标签:app,component,go,handler,link,Go,链接,结构设计
From: https://www.cnblogs.com/ALXPS/p/18068252

相关文章

  • golang模板库之fasttemplate
    简介fasttemplate是一个比较简单、易用的小型模板库。fasttemplate的作者valyala另外还开源了不少优秀的库,如大名鼎鼎的fasthttp,前面介绍的bytebufferpool,还有一个重量级的模板库quicktemplate。quicktemplate比标准库中的text/template和html/template要灵活和易用很多,后面会专......
  • Go的可变参数函数
    可变函数是指可以接收可变数量的参数的函数。在Golang中,可以传递与函数签名中引用的类型相同的不同数量的参数。在声明可变函数时,最后一个参数的类型前会有一个省略号"...",这表明该函数可以用任意数量的该类型参数来调用,可以是0个、1个或者多个。这种类型的函数在不知道传递给......
  • Django之权限管理
    一,引入1.为什么要有权限?2.为什么要开发权限的组件?3.在web开发中,什么是权限?4.表结构的设计权限表IDURL1/user_list/2/customer_list/用户表IDUSER_NAME1root2root2角色/用户组表ID组1销售2开发用户与角色的......
  • CPPB 表中的TXN_CATEGORY
    cst_pac_period_balanceTXN_CATEGORY1    期初    2    成本更新:新成本或百分比变动    3    自有成本事务处理    4    非返工完成    5    成本更新:值变动    6    返工发放    7    返工完成    8    组......
  • google搜索如何修改搜索结果地区
    本文写于2024年03月25日,阅读时请注意时效。解决使用谷歌搜索引擎的时候经常会因为IP地址位置导致搜索结果的语言出现偏差,尤其是使用汉语进行搜索时常常会导致搜索结果中出现很多日语等语言结果造成干扰的问题。设置位置在:首页→右下角Settings→Searchsettings→左上角......
  • c语言中的goto语句,goto语句的使用
    在c语言中,goto语句与分支语句if,switch不同,也和循环语句while,for,do...while不同,goto语句被称为无条件转移语句,也被称为转向语句,其实和break,return语句是同一个类型。goto语句的使用一般都需要一个again进行配合,当使用goto语句时,程序会转跳回again处重新运行again后的程序。got......
  • 获取Book里所有sheet的名字,且带上超链接
     应用背景:        当一个excel有很多sheet的时候,来回切换sheet会比较复杂,所以我希望excel的第一页有目录,可以随着sheet的增加,减少,改名而随时可以去更新,还希望有超链接可以直接跳到该sheet。可以采用以下代码OptionExplicitSubList_Hyperlink()Dimk!,shk=0......
  • Typora结合PicGo + Github搭建个人图床
    目录一、GitHub仓库设置1、新建仓库2、创建Token并复制保存二、PicGo客户端配置1、下载&安装2、配置图床三、Typora配置一、GitHub仓库设置1、新建仓库点击主页右上角的+号创建 Newrepository填写仓库信息2、创建Token并复制保存点击右上角用户头像......
  • Django框架之中间件引入
    【一】中间件介绍之前我们通过给视图函数加上装饰器来判断用户是否登录,把没有登陆的用户请求跳转到登录页面。但是如果有很多的功能都需要在登录之后才能使用,那么每个功能对应的视图函数都需要加上装饰器,这样的话就略显繁琐通过django中间件就可以实现控制全局的效果【1】什......
  • Django框架之Cookie和Session
    【一】Cookie与Session介绍【1】早期的网站早期的很多网站可能都没有保存用户功能的需求,所有用户访问的返回结果都是一样的,如新闻,博客文章等等...但是互联网发展至今,已经有绝大多数网站,在你登录后反馈的页面和不登录反馈的页面已经是不一样的了,这些网站就用到了保存用户登录信......