章节

使用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. 49从零开始用Rust编写nginx,我竟然在同一个端口上绑定了多少IP

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实 ...

  2. NC22600 Rinne Loves Dynamic Graph

    题目链接 题目 题目描述 Rinne 学到了一个新的奇妙的东西叫做动态图,这里的动态图的定义是边权可以随着操作而变动的图. 当我们在这个图上经过一条边的时候,这个图上所有边的边权都会发生变动. 定义变 ...

  3. Dubbo本地调试方法

    方法一:用版本号来区分 比如,开发环境上跑的服务版本是1.0.0,那么为了在本地打断点调试某个服务,可以在本地启动,将version设置为2.0.0 服务提供者 @DubboService(versi ...

  4. [技术选型与调研] 流程引擎/工作流引擎:Activiti、Flowable、Camunda

    1 概述:流程与流程引擎 低代码平台.办公自动化(OA).BPM平台.工作流系统均需要流程引擎功能 [工作流引擎的三大功能] 1)验证当前过程状态:在给定当前状态的情况下,检查是否有效执行任务. 2) ...

  5. SVG与foreignObject元素

    SVG与foreignObject元素 可缩放矢量图形Scalable Vector Graphics - SVG基于XML标记语言,用于描述二维的矢量图形.作为一个基于文本的开放网络标准,SVG能够 ...

  6. Swift —— 一、架构解析

    一.简介 OpenStack 对象存储 (swift) 用于冗余.可扩展的数据 使用标准化服务器集群存储PB的存储 可访问的数据.它是一种长期存储系统,可存储大量 可以检索和更新的静态数据.对象存储使 ...

  7. layui切换select选项事件

    说明 我们经常遇到表单上面选择不同的下拉选项需要触发函数去完成一些业务逻辑,比如我这个地方根据所选商品查询它底下明细的数量,并展示. 效果演示 代码 <!--选择商品--> <div ...

  8. kafka学习笔记03-消息生产者producer

    kafka学习笔记03-消息生产者producer 发送消息整体流程示意图 消息发送的流程示意图: (From:High-level overview of Kafka producer compon ...

  9. 类型别名TypeAlias

    from collections.abc import Iterable from typing import TypeAlias FromTo = tuple[str, str] # 3.10之前 ...

  10. contextmanager装饰器

    虽然上下文管理器很好用,但定义一个符合协议的管理器对象其实挺麻烦的 得首先创建一个类,然后实现好几个魔法方法.为了简化这部分工作,python 提供了一个非常好用的工具:@contextmanager ...