golang 入门教程:迷你 Twitter 后端

news2024/11/30 10:46:25

请记住,这个项目主要是为了稍微熟悉下Golang,您可以复制架构,但该项目缺少适当的 ORM,没有适当的身份验证或授权,我完全无视中间件,也没有测试。
我将在其自己的部分中讨论所有这些问题,但重要的是你要知道这还没有准备好投入生产。

如果我必须从头开始或重新制作项目,我会添加诸如 sqlx 和 Gorm 之类的库。 以及改进 API 和我在下面进行的其他更改。

此外,我想谈谈我正在使用的路由库:Fiber。 请注意,它是版本 2,但是很多教程和博客文章都在展示和谈论 v1,此后发生了一些变化,因此在查找其他信息时确保导入是 github.com/gofiber/fiber/v2。

此外,您绝对应该在项目中有一个配置文件和一个 .env 来隐藏您的数据。

README 中还有一个免责声明:
免责声明
这是一个介绍性的项目,可以稍微了解一下 Golang。 这个项目还没有准备好生产,它有不好的做法,比如分页的工作方式或我们与数据库的交互方式。
通过代码库,您会发现用于调试项目的不同“打印件”,请随意使用它们。 生产就绪缺少什么?
您应该添加适当的记录器、配置、中间件、处理数据的不同方式(可能是 ORM)、处理分页的更好方式以及更好的 API。
请注意,如果您复制并粘贴代码,可能会使自己容易受到 SQL 注入的攻击。 这是出于学习目的而制作的。

Database

您可能应该让 Docker 运行 MySQL(或任何其他 SQL 数据库)。 如果没有,你总是可以在你的系统上安装 MySQL 并使用像 MySQL Workbench 这样的东西来处理它。

Database design

在这里插入图片描述

数据库本身相当简单,你有发布推文的用户和一个关注者表来保存谁关注谁的数据。关注者表主要是为我们的用户实现提要/时间线。

Create Database

在脚本文件夹中,您将找到运行以启动 MySQL 数据库的主要脚本:

use twitterdb;

DROP TABLE IF EXISTS tweets;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS followers;

CREATE TABLE users (
  user_id INT NOT NULL AUTO_INCREMENT,
  user VARCHAR(255) NOT NULL,
  passhash VARCHAR(40) NOT NULL,
  email VARCHAR(255) NOT NULL,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  dob DATE,
  PRIMARY KEY (user_id)
);

CREATE TABLE tweets (
  tweet_id INT NOT NULL AUTO_INCREMENT,
  user_id INT NOT NULL,
  tweet VARCHAR(140) NOT NULL,
  date_tweet DATETIME NOT NULL,
  PRIMARY KEY (tweet_id),
  FOREIGN KEY user_id(user_id) REFERENCES users(user_id) 
  ON UPDATE CASCADE ON DELETE CASCADE
);

CREATE TABLE followers (
  id_user INT NOT NULL REFERENCES users (user_id),
  id_follower INT NOT NULL REFERENCES users (user_id),
  PRIMARY KEY (id_user, id_follower)
);

INSERT INTO users (user, passhash, email, first_name, last_name, dob) VALUES
("foo", "asdsad1", "test@gmail.com", "bob", "bobbinson", "2006-01-01"),
("foo2", "asdsad2", "test2@gmail.com", "bob2", "bobbinson2", "1992-01-01"),
("foo3", "asdsad3", "test3@gmail.com", "bob3", "bobbinson3", "1993-01-01"),
("foo4", "asdsad4", "test4@gmail.com", "bob4", "bobbinson4", "1994-01-01"),
("foo5", "asdsad5", "test5@gmail.com", "bob5", "bobbinson5", "1995-01-01"),
("foo6", "asdsad6", "test6@gmail.com", "bob6", "bobbinson6", "1996-01-01"),
("foo7", "asdsad7", "test7@gmail.com", "bob7", "bobbinson7", "1925-01-01"),
("foo8", "asdsad8", "test8@gmail.com", "bob8", "bobbinson8", "1980-01-01"),
("foo9", "asdsad9", "test9@gmail.com", "bob9", "bobbinson9", "1980-01-01"),
("foo10", "asdsad10", "test10@gmail.com", "bob10", "bobbinson10", "1970-01-01");

INSERT INTO tweets(user_id, tweet, date_tweet) VALUES
(1, "test tweet", "2001-01-01 22:00:00"),
(2, "test tweet2", "2002-01-01 22:00:00"),
(3, "test tweet3", "2003-01-01 22:00:00"),
(4, "test tweet4", "2004-01-01 22:00:00"),
(5, "test tweet5", "2005-01-01 22:00:00");

INSERT INTO followers(id_user, id_follower) VALUES
(5,1),
(4,1),
(3,1),
(2,1),
(6,1),
(2,5),
(4,5);

其他文件是我们稍后将使用的查询示例。

Project Architecture

在这里插入图片描述

如果您熟悉构建软件,如果不熟悉这应该很熟悉,首先让我告诉您缺少什么,然后我们将遍历每个文件夹。

该项目缺少配置、中间件、记录器和测试等文件夹。 如果你能更好地组织控制器,你就会有一个路由文件夹,你可以更好地组织 API。

关于导入包的说明

在这个项目中,您可能会对以下导入感到困惑:

import (
    "goexample/database"
    "goexample/models"
    "goexample/services/utils"
)

这里的 goexample 是项目的名称,我只是在之后重命名了我的 repo 这样它才有意义,所以我们使用“goexample/database”而不是“mini-twitter-clone/database”来导入数据库包。
对于任何其他新项目,只需使用文件夹的名称。

API

路由存储在 controller.go 中:

package api

import (
    "goexample/services"

    "github.com/gofiber/fiber/v2"
)

func SetupRoutes(app *fiber.App) {

    api := app.Group("/api")

    //get all unordened users
    api.Get("/users", services.GetUsers)
    //get all users ordered by age ASC
    api.Get("/users/age", services.GetUsersByAgeAsc)

    //get all unordened tweets from db
    api.Get("/tweets", services.GetTweets)

    //http://localhost:3000/api/feed/1
    //get MOST RECENT feed/timeline for the user
    api.Get("/feed/:id", services.GetFeedTweets)
    //pagination
    api.Get("/feed/:id/:limit/:offset", services.GetFeedTweetsPaginated)
    //can try https://github.com/gofiber/fiber/issues/193#issuecomment-591976894
    //a whole presentation on why you shouldn't do what I did:
    //https://use-the-index-luke.com/no-offset

}

我正在使用类似于 Express 的 Fiber 库,这里我们有几个端点,我们通过 Get 请求调用这些端点,一旦服务器收到请求,它的响应就是调用我从服务包中公开的函数——那就是 我们的业务逻辑在哪里。

注意:分页未正确实现。

这只是一个简单的示例,此端点与其他端点一样存在安全问题,但您应该通过示例了解库和 Golang 的工作方式。

请注意,在 /feed/:id 中,id 参数与我们要为其获取提要的用户有关。

启动项目

您可以通过执行 go run 来启动项目。 文件夹内。
您的终端应如下所示:

在这里插入图片描述
让我们访问不同的端点以查看响应,我将使用普通浏览,但如果您不熟悉调试后端,则应检查 Postman。

让我们从控制器运行端点:

http://127.0.0.1:3000/api/users

在这里插入图片描述
我留下了很多打印输出,所以如果你在每次通话时检查你的终端,你应该会看到如下内容:在这里插入图片描述

接下来是我们按年龄对用户排序的调用:

http://127.0.0.1:3000/api/users/age

当我们请求推文时,我们会得到所有的推文:

http://127.0.0.1:3000/api/tweets

在这里插入图片描述

在这里插入图片描述
现在我们获取 ID 为 1 的用户的提要或时间线:

http://127.0.0.1:3000/api/feed/1

在这里插入图片描述

在这里插入图片描述
请记住,分页尚未准备好生产,但核心概念是相同的:

http://127.0.0.1:3000/api/feed/1/2/1

在这里插入图片描述
由于我们在服务包中的业务逻辑和我们公开的功能,所有调用都能正常工作。

Services

在服务包中,我们有当某人或某物点击其中一个端点时调用的函数。 请记住,我们通过在包中使用大写字母来公开功能。

让我们看一下 timeline_tweets.go,它包含 controller.go 文件中两个不同端点的两个函数:


package services

import (
    "fmt"
    "goexample/database"
    "goexample/models"
    "goexample/services/utils"
    "log"

    "github.com/gofiber/fiber/v2"
)

func GetFeedTweets(c *fiber.Ctx) error {

    //you shouldn't do this by the way, but it's just a demo
    // dbQuery := fmt.Sprintf("SELECT users.user_id, users.user, users.first_name, users.last_name, tweets.tweet, tweets.date_tweet FROM users INNER JOIN tweets ON users.user_id = tweets.user_id INNER JOIN followers ON users.user_id = followers.id_user WHERE followers.id_follower = %s ORDER BY tweets.date_tweet DESC;", c.Params("id"))
    // rows, err := database.DB.Query(dbQuery)

    //avoid the SQL injection by rewriting it like
    dbQuery := "SELECT users.user_id, users.user, users.first_name, users.last_name, tweets.tweet, tweets.date_tweet FROM users INNER JOIN tweets ON users.user_id = tweets.user_id INNER JOIN followers ON users.user_id = followers.id_user WHERE followers.id_follower = ? ORDER BY tweets.date_tweet DESC;"
    rows, err := database.DB.Query(dbQuery, c.Params("id"))

    //check for errors
    if err != nil {
        return utils.DefaultErrorHandler(c, err)
    }
    //close db connection
    defer rows.Close()

    //create a slice of tweets
    var timelineTweets []models.TimelineTweet
    //loop through the result set
    for rows.Next() {
        timelineTweet := models.TimelineTweet{}
        err := rows.Scan(&timelineTweet.User_id, &timelineTweet.User, &timelineTweet.First_name, &timelineTweet.Last_name, &timelineTweet.Tweet, &timelineTweet.Date_tweet)
        if err != nil {
            log.Fatal(err)
        }
        timelineTweets = append(timelineTweets, timelineTweet)
    }
    fmt.Print(timelineTweets)

    utils.ResponseHelperJSON(c, timelineTweets, "timeline", "No timeline found")

    return err
}

func GetFeedTweetsPaginated(c *fiber.Ctx) error {

    // dbQuery := fmt.Sprintf("SELECT users.user_id, users.user, users.first_name, users.last_name, tweets.tweet, tweets.date_tweet FROM users INNER JOIN tweets ON users.user_id = tweets.user_id INNER JOIN followers ON users.user_id = followers.id_user WHERE followers.id_follower = %s ORDER BY tweets.date_tweet DESC LIMIT %s OFFSET %s;", c.Params("id"), c.Params("limit"), c.Params("offset"))
    // avoid a SQL injection by rewriting it like
    dbQuery := "SELECT users.user_id, users.user, users.first_name, users.last_name, tweets.tweet, tweets.date_tweet FROM users INNER JOIN tweets ON users.user_id = tweets.user_id INNER JOIN followers ON users.user_id = followers.id_user WHERE followers.id_follower = ? ORDER BY tweets.date_tweet DESC LIMIT ? OFFSET ?;"

    rows, err := database.DB.Query(dbQuery, c.Params("id"), c.Params("limit"), c.Params("offset"))
    if err != nil {
        return utils.DefaultErrorHandler(c, err)
    }

    defer rows.Close()

    var timelineTweets []models.TimelineTweet
    for rows.Next() {
        timelineTweet := models.TimelineTweet{}
        err := rows.Scan(&timelineTweet.User_id, &timelineTweet.User, &timelineTweet.First_name, &timelineTweet.Last_name, &timelineTweet.Tweet, &timelineTweet.Date_tweet)
        if err != nil {
            log.Fatal(err)
        }
        timelineTweets = append(timelineTweets, timelineTweet)
    }
    //TODO: implement a response with pages and all that pagination jazz
    utils.ResponseHelperJSON(c, timelineTweets, "timeline", "No timeline found")

    return err
}

您首先注意到的是我们如何将上下文传递给 GetFeedTweets,然后我们使用变量“c”来使用 Fiber 库所说的上下文。

之后,我们使用包数据库中公开的变量 DB 打开 DB,读取数据,然后关闭它。

为了正确存储和扫描数据,我们使用模型包中的结构。

之后您将看到 utils 包中的几个函数。 这些函数主要是辅助函数,因此您可以查看 Golang 如何执行循环和某些其他操作。

我们的服务包中的其他两个文件非常相似,我们在这里的工作是从数据库中获取数据,通过我们的结构数组扫描它,然后以 JSON 格式将其发送回用户。 以及我们运行 utils 包中的一些功能。

Utils

这个包包含辅助函数,也许最有趣的是在 user_helper 里面,因为它有与切片数据交互的函数,但是要小心,它们的实现并不像你想象的那么好。

让我们看看最有帮助的,response_helper.go

package utils

import (
    "github.com/gofiber/fiber/v2"
)

//response JSON for services after you loop and scan
func ResponseHelperJSON(c *fiber.Ctx, data any, dataType string, dataError string) {
    if data != nil {
        c.Status(200).JSON(&fiber.Map{
            "success": true,
            dataType:  data,
        })
    } else {
        c.Status(404).JSON(&fiber.Map{
            "success": false,
            "error":   dataError,
        })
    }
}

请注意,此函数包含通用类型的数据,因为我使用关键字 any 并且我正在与 Fiber 通过变量“c”提供的上下文进行交互。

Models

models 文件夹包含我们用作与数据库交互的实体的不同结构,如 services 文件夹中所示。

这是一个示例,请注意,即使您想公开整个结构,所有内容都必须以大写字母开头:

package models

type UserWithAge struct{
    Id         int   `json:"id"`
    User       string `json:"user"`
    Passhash   string `json:"passhash"`
    Email      string `json:"email"`
    First_name string `json:"first_name"`
    Last_name  string `json:"last_name"`
    Age        int    `json:"age"`
}

Database

数据库包非常简单,请记住使用配置文件和 .env 来存储您的敏感数据。

package database

import (
    "database/sql"
    "fmt"
    "log"
)

var DB *sql.DB

func Connect() error{
    var err error
    //use a config file for this
    DB, err = sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/twitterdb")

    if err != nil {
        log.Fatal(err)
        return err
    }

    if err = DB.Ping(); err != nil {
        log.Fatal(err)
        return err
    }

    fmt.Println("Connected to database")

    return nil

}

Main.go

最后,这是我们启动服务器的地方:

package main

import (
    "github.com/gofiber/fiber/v2"
    "goexample/database"
    "log"

    "goexample/api"

    _ "github.com/go-sql-driver/mysql"
)

func main() {

    if err := database.Connect(); err != nil {
        log.Fatal(err)
    }

    app := fiber.New()
    api.SetupRoutes(app)


    log.Fatal(app.Listen(":3000"))

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/344454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用NGROK将本地网站发布为一个公开网站

一般与第三方服务集成时,需要提供https的回调URL,本地开发阶段可以利用NGROK将本地网站发布为公开的https网站。https://ngrok.com/downloadWindow下载地址:https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip以Window…

echarts问题汇总

因为工作中经常使用echarts,做数据可视化相关需求,需要实现各种各样图表的需求。 有遇到过很多问题,一些网上不太好搜索到解决方案的,一直想总结一下解决过程。方便自己查阅,也方便别人参考。 一:echarts…

【C++】set/multiset、map/multimap的使用

目录 一、关联式容器 二、set的介绍 1、接口count与容器multiset 2、接口lower_bound和upper_bound 三、map的介绍 1、接口insert 2、接口insert和operator[]和at 3、容器multimap 四、map和set相关OJ 1、前K个高频单词 2、两个数组的交集 一、关联式容器 vector、…

【LeetCode】环形链表 II [M](链表)

142. 环形链表 II - 力扣(LeetCode) 一、题目 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链…

Python网络爬虫 学习笔记(1)requests库爬虫

文章目录Requests库网络爬虫requests.get()的基本使用框架requests.get()的带异常处理使用框架(重点)requests库的其他方法和HTTP协议(非重点)requests.get()的可选参数网络爬虫引发的问题(非重点)常见问题…

【C/C++】Windows下VS创建Linux项目

如果不想在Linux下用vim编写代码,可以在Windows下使用VS远程连接Linux(Linux下是不支持安装使用VS的),将VS上编写的代码通过 SSH协议 推送到Linux下,注意文件编写是在Windows上进行的,编译是在Linux下进行的…

【Hello Linux】Linux工具介绍 (make/makefile git)

作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:介绍Linux的常用工具make/makefile git Linux项目自动化构建工具 – make/Makefile 背景 会不会写Makefile 从侧面说明了一个人是否具…

Spring Cloud微服务网关Gateway组件

目录 网关简介 什么是Spring Cloud Gateway Spring Cloud Gateway 功能特征 核心概念 工作原理 Spring Cloud Gateway快速开始 环境搭建 集成Nacos 路由断言工厂(Route Predicate Factories)配置 自定义路由断言工厂 过滤器工厂( …

window 配置深度学习环境GPU

CUDA 11.6 CUDNN Anaconda pytorch 参考网址:https://zhuanlan.zhihu.com/p/460806048 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 (aliyun.com) 电脑信息 RTX 2060 GPU0 1. CUDA 11.6 1.1 确认信息 C:\Users\thzn>nvidia-smi (CUDA Versi…

秒杀项目之消息推送

目录一、创建消费者二、创建订单链路配置2.1 定义RabbitMQ配置类2.2 创建RabbitmqOrderConfig配置类三、如何实现RabbitMQ重复投递机制3.1 开启发送者消息确认模式3.2 消费发送确认3.2.1 创建ConfirmCallBack确认模式3.2.2 创建ReturnCallBack退回模式3.3 创建生产者3.4 创建消…

金三银四必看软件测试面试题,上百家公司面试都是这些套路

自我介绍说一下测试用例嗯,做测试,好多时间是在琢磨分析测试用例怎么去写,这个每个公司规范可能不太一样,但是大致思想是一致的。都是想要通过测试用例,把每一个分析到位,进行测试。就拿我上家公司来说吧&a…

数据库(2)--加深对统计查询的理解,熟练使用聚合函数

一、内容要求 利用sql建立学生信息数据库,并定义以下基本表: 学生(学号,年龄,性别,系号) 课程(课号,课名,学分,学时) 选课&#xff0…

融云入围「2022 云办公平台 TOP50」,进入「中国协同办公产业图谱」

2 月 10 日,中国科学院旗下《互联网周刊》颁布“2022 云办公平台 TOP50”,融云荣登榜单。 2 月 13 日,艾瑞咨询发布《2023 年中国协同办公行业研究报告》(下简称《报告》),对协同办公行业的供需动态和迭代方…

黑马】后台管理-项目优化和上线

一。项目优化优化1,加载进度条显示安装一个运行依赖,nprogress然后导包,调用对象展示和隐藏在main中基于拦截器实现展示进度条和隐藏进度条的效果如果触发请求拦截器,证明发起请求,希望展示进度条,如果触发…

消防应急照明和疏散指示系统——集中控制型系统的设计与应用

安科瑞 李亚俊 V:Acrel8757 摘要:伴随着建筑领域的良好发展,建筑工程建设越来越复杂,相应的消防配套设施也越来越先进,火灾发生时,人在燃烧产生的噪音和烟气中会产生恐惧、不安等不良的心理状态,进而影响他…

NLP篇章2:理解Transformer

Transformer编码,解码大的结构的理解 编码部分,每一个的小编码器的输入是前一个小编码器的输出, 而每一个小解码器的输入不光是它的前一个解码器的输出,还包括了整个编码部分的输出。 self-attention 自注意力机制 顾名思义就是…

【数据库】 MySQL备份恢复

目录 MySQL日志管理 一, MySQL日志类型 二,错误日志 三, 通用查询日志 四, 慢查询日志 五,二进制日志 1,开启日志 2,二进制日志的管理 3,日志查看 5,二进制日志还原数据…

MAC OSX安装Python环境 + Visual Studio Code

MAC上开发python怎么能少得了python3环境呢,而安装python3环境的方式也有多种,这里仅选用并记录本人认为比较方便的方式 安装Homebrew Homebrew是macOS 缺失的软件包管理器, 使用它可以在MAC上安装很多没有预装的东西,详细说明可…

上海霄腾自动化装备盛装亮相2023生物发酵展

上海霄腾自动化携液体膏体粉剂颗粒等灌装生产线解决方案亮相2023生物发酵展BIO CHINA2023生物发酵展,作为生物发酵产业一年一度行业盛会,由中国生物发酵产业协会主办,上海信世展览服务有限公司承办,2023第10届国际生物发酵产品与技…

SAS应用入门学习笔记5

input 操作符: 代码说明: 1)1 表示第1列字符;7表示第7列字符; 2)col1 表示第一列数据;col2 表示第二列数据; 3)4.2 表示的是4个字符,2表示小数点后两位&a…