章节

使用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的算法。

  

使用Go语言开发一个短链接服务:三、项目目录结构设计的更多相关文章

  1. 百度 谷歌 Twitter,这么多短链接服务(Short Url)究竟哪家强?

    一.短链接是什么 url=HPqdQ5VR3vA39x7ZWoWyNzwWnsDhTbh66BTpdzsJLroBDzFRm4JV-G818Zc027uZrwe7zxtxnD4H2FUahftpUK& ...

  2. 用PHP和Python生成短链接服务的字符串ID

    假设你想做一个像微博短链接那样的短链接服务,短链接服务生成的URL都非常短例如: http://t.cn/E70Piib, 我们应该都能想到链接中的E70Piib对应的就是存储长链接地址的数据记录的I ...

  3. Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务

    短网址顾名思义就是使用比较短的网址代替很长的网址.维基百科上面的解释是这样的: 短网址又称网址缩短.缩短网址.URL 缩短等,指的是一种互联网上的技术与服务,此服务可以提供一个非常短小的 URL 以代 ...

  4. Java 网址短链接服务原理及解决方案

    一.背景 现在在各种圈的产品各种推广地址,由于URL地址过长,不美观.不方便收藏.发布.传播以及各种发文字数限制等问题,微信.微博都在使用短链接技术.最近由于使用的三方的生成.解析短链接服务开始限制使 ...

  5. 短链接服务Octopus的实现与源码开放

    前提 半年前(2020-06)左右,疫情触底反弹,公司的业务量不断提升,运营部门为了方便短信.模板消息推送等渠道的投放,提出了一个把长链接压缩为短链接的功能需求.当时为了快速推广,使用了一些比较知名的 ...

  6. 最近做了一个短网址服务 di81.com

    最近做了一个短网址服务:   di81.com 项目中有一处需求,需要把长网址缩为短网址,把结果通过短信.微信等渠道推送给客户.刚开始直接使用网上现成的开放服务,然后在某个周末突然手痒想自己动手实现一 ...

  7. 使用plv8+hashids生成短链接服务

    有写过一个集成npm plv8 以及shortid生成短链接id服务,实际上我们可以集成触发器自动生成url对应的短链接地址,hashids也是一个不错的选择. 以下是一个别人写的一个博客实现可以参考 ...

  8. 使用go语言开发一个后端gin框架的web项目

    用liteide来开发go的后端项目,需要注意的是环境变量要配置正确了 主要是GOROOT, GOPATH, GOBIN, PATH这几个, GOPATH主要用来存放要安的包,主要使用go get 来 ...

  9. ubuntu下使用C语言开发一个cgi程序

    主要步骤是: 1. 开发一个C程序(在标准输出中输出HTML字符串) 2. 复制到apache2的cgi-bin目录去 3. 在httpd.conf中开启cgi功能(我似乎没用到,也可以使用cgi) ...

  10. Android开发学习之路--Android Studio项目目录结构简介

    既然已经搭建好环境了,那就对Android Studio中项目目录结构做个简单的了解了,这里以最简单的Hello工程为例子,新建好工程后看如下三个工程视图: 1.Android工程 manifests ...

随机推荐

  1. NC23051 华华和月月种树

    题目链接 题目 题目描述 华华看书了解到,一起玩养成类的游戏有助于两人培养感情.所以他决定和月月一起种一棵树.因为华华现在也是信息学高手了,所以他们种的树是信息学意义下的. 华华和月月一起维护了一棵动 ...

  2. NC24158 [USACO 2015 Jan G]Moovie Mooving

    题目链接 题目 题目描述 Bessie is out at the movies. Being mischievous as always, she has decided to hide from ...

  3. NC24263 USACO 2018 Feb G]Directory Traversal

    题目链接 题目 题目描述 奶牛Bessie令人惊讶地精通计算机.她在牛棚的电脑里用一组文件夹储存了她所有珍贵的文件,比如: bessie/ folder1/ file1 folder2/ file2 ...

  4. Linux 中iostat 命令详解

    iostat命令详解 iostat 主要是统计 磁盘活动情况. iostat有以下缺陷: iostat的输出结果大多数是一段时间内的平均值,因此难以反映峰值情况iostat仅能对系统整体情况进行分析汇 ...

  5. 在OAuth 2.0模式下使用Spring Cloud Gateway

    Spring Cloud Gateway主要用于以下角色之一: OAuth Client OAuth Resource Server 1  Spring Cloud Gateway as an OAu ...

  6. Ubuntu下通过Wine安装LTSpice 17.1.8

    LTSpice LTSpice 是常用的电路模拟软件, 但是只有 Windows 版本和 Mac 版本, 在 Linux 下需要用 Wine 运行. 以下说明如何在 Ubuntu 下安装最新的 LTS ...

  7. RFID EPC Class1 Gen2电子标签笔记

    RFID EPC Class1 Gen2 符合EPC Class1 Gen2(简称G2)协议V109版的电子标签(Tag)和读写器(Reader)应该具有下述的特性 标签存储器分区 Tag memor ...

  8. 端口碰撞Port Knocking和单数据包授权SPA

    端口碰撞技术 Port knocking 从网络安全的角度,服务器开启的端口越多就越不安全,因此系统安全加固服务中最常用的方式,就是先关闭无用端口,再对提供服务的端口做访问控制.而作为远程管理与维护的 ...

  9. 【OpenGL ES】立方体贴图(6张图)

    1 前言 ​ 本文通过一个立方体贴图的例子,讲解三维纹理贴图的应用,案例中使用 6 张不同的图片给立方体贴图,图片如下: ​ 本文涉及到的知识点主要包含:三维绘图.MVP 矩阵变换.纹理贴图,读者如果 ...

  10. C++ 多线程的错误和如何避免(5)

    要记得对加锁的临界区解锁 前提:在多个线程共享一块资源或者数据时,我们需要加上互斥锁来保护临界区(否则出现数据未定义的行为) 问题:我们往往在写了很多代码之后忘记 unlock 互斥锁,那么等待该资源 ...