Go 进阶:Go + gin 极速搭建 EcommerceSys 电商系统

news2024/11/28 13:34:11

Go 进阶:Go + gin 极速搭建 EcommerceSys 电商系统


前言

本章节适合有一定基础的 Golang 初学者,通过简单的项目实践来加深对 Golang 的基本语法和 Web 开发的理解。
具体请联系作者

项目结构

项目流程图

  1. 技术栈
    请添加图片描述

  2. 项目结构
    请添加图片描述

  3. 项目路由

请添加图片描述
4. 项目模型
请添加图片描述

项目初始化

  1. 初始化项目文件夹
md ecommerce-sys
  1. 初始化 mod 文件
cd ecommerce-sys
go mod init github.com/your_username/ecommerce-sys

注意,此处的 your_username 请替换为你的 GitHub 用户名
本项目中,将会使用自己的 GitHub 用户名,请自行修改

  1. 检查 go.mod 文件是否创建成功并启动 VS Code
dir # linux 下使用 ls 命令

code .
  1. 创建 ecommerce-sys 数据库

打开 MongoDB ,输入以下命令创建 ecommerce-sys 数据库:

use database_name

其中,database_name 请替换为你自己喜欢的数据库名称。

  1. 初始化项目结构

一行代码在项目根目录下创建目录和空文件

# Windows 系统
mkdir controllers database middleware models routes tokens & echo. > controllers\address.go & echo. > controllers\cart.go & echo. > controllers\controllers.go & echo. > database\cart.go & echo. > database\databasetup.go & echo. > middleware\middleware.go & echo. > models\models.go & echo. > routes\routes.go & echo. > tokens\tokengen.go

# Linux 系统
mkdir -p controllers database middleware models routes tokens && touch controllers/address.go controllers/cart.go controllers/controllers.go database/cart.go database/databasetup.go middleware/middleware.go models/models.go routes/routes.go tokens/tokengen.go
  1. 安装 gin 包 和 Air
go get -u github.com/gin-gonic/gin
go install github.com/air-verse/air@latest
  1. 配置 Air 热重载

将具有默认设置的 .air.toml 配置文件初始化到当前目录

air init

Air 配置教程:如果有特殊需要请自行参考

如果以上都正常,您只需执行 air 命令,就能使用 .air.toml 文件中的配置热重载你的项目了。

air

搭建项目骨架

  1. 编写 routes/routes.go 文件

为什么要最先编写路由?

优先选择编写路由文件的原因在于路由决定了用户访问的 URL 所对应的页面和内容。也就是说,路由是用户请求的起点。

因为所有操作都从请求接口开始,定义好路由可以帮助我们明确应用的整体结构。

在路由确定之后,我们可以进一步编写控制器和模型,这样可以确保应用的各个部分都能协调工作。

虽然每个人的开发习惯和业务逻辑可能不同,但从路由入手通常是一个推荐的方法,它能帮助你更清晰地组织代码, 并且让你曾经觉得难以完成的独立开发一个项目变得轻松可行。

package routes

import (
	"github.com/Done-0/ecommerce-sys/controllers"
	"github.com/gin-gonic/gin"
)

// UserRoutes 定义用户相关的路由
func UserRoutes(incomingRoutes *gin.Engine) { // 创建 *gin.Engine 实例, 即 incomingRoutes 参数
	incomingRoutes.POST("/users/signup", controllers.SignUp()) // 注册
	incomingRoutes.POST("/users/login", controllers.Login()) // 登录
	incomingRoutes.POST("/admin/addproduct", controllers.ProductViewerAdmin()) // 管理员浏览商品
	incomingRoutes.GET("/users/productview", controllers.SearchProduct()) // 查询所有商品
	incomingRoutes.GET("/users/search", controllers.SearchProductByQuery()) // 通过 ID 查询商品
}
  1. 编写 main.go 文件
package main

import (
	"os"
	"log"

	"github.com/Done-0/ecommerce-sys/routes"
	"github.com/Done-0/ecommerce-sys/controllers"
	"github.com/Done-0/ecommerce-sys/database"
	"github.com/Done-0/ecommerce-sys/middleware"
	"github.com/gin-gonic/gin"
)

func main() {
	// 获取环境变量PORT的值, 如果不存在则赋值8000
	port := os.Getenv("PORT")
	if port == "" {
		port = "8000"
	}

	// 创建应用程序实例
	app := controllers.NewApplication(
		database.ProductData(database.Client, "Products"),
		database.UserData(database.Client, "Users"),
	)

	router := gin.New()
	router.Use(gin.Logger())
	router.Use(gin.Recovery())

	// 注册
	routes.UserRoutes(router) // 调用routs包中的UserRoutes函数,注册路由,并命名为router
	router.Use(middleware.Authentication())


	// 定义用户路由之外的路由
	router.GET("/addtocart", app.AddToCart())
	router.GET("/removeitem", app.RemoveItem())
	router.GET("/cartcheckout", app.BuyFromCart())
	router.GET("/instantbuy", app.InstantBuy())

	log.Fatal(router.Run(":" + port))
}
  1. 编写 models/models.go 文件
package models

import (
	"time"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type User struct {
	ID	            	primitive.ObjectID		  `json:"_id" bson:"_id"`
	Name	            *string					  `json:"name" validate:"required,min=6,max=30"`
	Password	        *string					  `json:"password" validate:"required,min=6,max=30"`
	Email				*string					  `json:"email" validate:"email,required"`
	Phone				*string					  `json:"phone" validate:"required"`
	Token				*string					  `json:"token"`
	Refresh_Token		*string					  `json:"refresh_token"`
	Created_At			time.Time				  `json:"created_at"`
	Updated_At			time.Time				  `json:"updated_at"`
	User_ID				string					  `json:"user_id"`
	// 切片本身已经是一个引用类型,能够提供对底层数据的引用,因此不加*号
	UserCart			[]ProductUser 			  `json:"usercart" bson:"usercart"`
	Address_Details		[]Address				  `json:"address" bson:"address"`
	Order_Status		[]Order					  `json:"order" bson:"order"`
}

type Product struct {
	Product_ID          primitive.ObjectID		  `bson:"_id"`
	Product_Name	    *string					  `json:"product_name"`
	//uint64: 是一种无符号 64 位整数类型。它可以存储从 0 到 2^64-1 之间的整数。
	Price				*uint64  				  `json:"price"`
	Rating				*uint8					  `json:"rating"`
	// Image 只存储一个网址,则为 string 类型	
	Image				*string  				  `json:"image"`
}

type ProductUser struct {
	Product_ID			primitive.ObjectID		  `bson:"_id"`
	Product_Name		*string					  `json:"product_name"`
	Price				*uint64					  `json:"price"`
	Rating				*uint8					  `json:"rating"`
	Image 				*string					  `json:"image"`
}

type Address struct {
	Address_id			primitive.ObjectID		   `bson:"_id"`
	House				*string					   `json:"house_name" bson:"house_name"`
	Street				*string					   `json:"street_name" bson:"street_name"`
	City				*string					   `json:"city_name" bson:"city_name"`
	PostalCode			*string					   `json:"postalcode" bson:"postalcode"`
}

type Order struct {
	Order_ID			primitive.ObjectID		   `bson:"_id"`
	Order_Cart			[]ProductUser			   `json:"order_list" bson:"order_list"`
	Ordered_At			time.Time				   `json:"ordered_at" bson:"ordered_at"`
	Price				int						   `json:"price" bson:"price"`
	Discount			*int					   `json:"discount" bson:"discount"`
	Payment_Method		Payment					   `json:"payment_method" bson:"payment_method"`
}

type Payment struct {
	Digital				bool			
	COD					bool
}

知识小课堂为什么结构体中字段名的首字母大写?
在 Go 语言中,结构体字段名的首字母决定了该字段的可见性:

  • 首字母大写的字段名:这些字段是 “导出” 的,意味着它们可以在包外部访问。这类似于其他编程语言中的 “public” 访问级别。例如:
type User struct {
   Name  string  // 导出字段,可以在包外访问
}

在这个例子中,Name 字段是导出的,可以在其他包中通过 user.Name 访问。

  • 首字母小写的字段名:这些字段是 “未导出” 的,仅在定义它们的包内部可见。这类似于其他编程语言中的 “private” 访问级别。例如:
type User struct {
   name  string  // 未导出字段,只能在包内访问
}

知识小课堂结构体标签中的 jsonbson有什么不同?
在 Go 语言的结构体定义中,标签(tag)用于指示序列化库如何处理字段。常见的标签包括 jsonbson

  • json 标签:用于指定当结构体字段被序列化为 JSON 时,使用的字段名。例如:
type User struct {
    Name  string  `json:"name"`
}  

在这个例子中,即使 NameGo 代码中是大写的,在 JSON 输出中,它将会被序列化为小写的 "name" 键。

  • bson 标签:用于指定当结构体字段被序列化为 BSON(MongoDB 的文档格式)时,使用的字段名。例如:
type User struct {
    ID  primitive.ObjectID  `bson:"_id"`
}  

在这个例子中,ID 字段会被映射到 MongoDB 文档的 _id 字段,这是 MongoDB 中常用的主键字段名。

标签中的 _ 和不同点
bson:"_id" 标签:_idMongoDB 的标准字段名,表示文档的唯一标识符。Go 语言中的字段名可以不同,但通过 bson 标签,你可以将其映射到 MongoDB 的 _id 字段。

type User struct {
   UserID  primitive.ObjectID  `bson:"_id"`
}

这里,UserID 字段会被存储为 MongoDB 中的 _id 字段。
使用标签的好处:通过 jsonbson 标签,你可以将 Go 结构体字段名与 JSONBSON 中的字段名分开管理,这在处理不同的命名约定时非常有用。标签也可以控制序列化和反序列化时的行为,比如忽略某些字段或者使用自定义名称。

  1. 搭建 controllers 控制器骨架
  1. 首先,搭建 controllers/controllers.go 业务逻辑层骨架
package controllers

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"
	"github.com/Done-0/ecommerce-sys/database"
	"github.com/Done-0/ecommerce-sys/models"
	generate "github.com/Done-0/ecommerce-sys/tokens"
	"github.com/go-playground/validator/v10"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"golang.org/x/crypto/bcrypt"
	"github.com/gin-gonic/gin"
)

// 用户路由逻辑函数

func HashPassword (password string) string {

}


func VertifyPassword (userPassword string, givenPassword string) (bool, string) {

}



func SignUp () gin.HandleFunc {

}


func Login () gin.HandlerFunc {

}


func ProductViewerAdmin () gin.HandlerFunc {

}


func SearchProduct() gin.HandlerFunc {

}

func SearchProductByQuery() gin.HandlerFunc {

}
  1. 其次,搭建 controllers/cart.go 业务逻辑层骨架
package controllers

import (

)

type Application struct {
	prodCollection *mongo.Collection // 用于存储与产品相关的 MongoDB 集合。
	userCollection *mongo.Collection // 用于存储与用户相关的 MongoDB 集合。
}

// NewApplication 创建一个新的 Application 实例。
// prodCollection 和 userCollection 是 MongoDB 的集合。
func NewApplication(prodCollection, userCollection *mongo.Collection) *Application {
	// 确保传入的集合有效
	if prodCollection == nil || userCollection == nil {
		// 可以在这里处理空指针情况,确保传入的集合有效
		log.Fatal("prodCollection or userCollection is nil")
	}
	// 如果参数有效,函数创建并返回一个 Application 实例,并将 prodCollection 和 userCollection 分别初始化为传入的集合。
	return &Application{
		prodCollection: prodCollection,
		userCollection: userCollection,
	}
}


func AddToCart() gin.HandlerFunc {

}

func RemoveItem() gin.HandlerFunc {

}

func GetItemFromCart() gin.HandlerFunc {

}

func BuyFromCart() gin.HandlerFunc {

}

func InstantBuy() gin.HandlerFunc {

}
  1. 最后,搭建 controllers/address.go 业务逻辑层骨架
package controllers

import (

)

func AddAdress() gin.HandlerFunc {

}

func EditHomeAddress() gin.HandlerFunc {

}

func EditWorkAddress() gin.HandlerFunc {

}
func DeleteAddress() gin.HandlerFunc {
	
}
  1. 配置 database 数据库
  1. 首先,搭建 database/cart.go 数据库层骨架
package database

import (

)

var (
	ErrCantFindProduct = errors.New("can't find the product")  // 表示找不到产品的错误。
	ErrCantDecodeProducts = errors.New("can't find the product")  // 表示解码产品失败的错误
	ErrUserIdIsNotValid = errors.New("this user is not valid")  // 表示用户 ID 无效的错误。
	ErrCantUpdateUser = errors.New("cannot add this product to the cart")  // 表示无法更新用户的错误。
	ErrCantRemoveItemCart = errors.New("cannot remove this item from the cart")  // 表示无法从购物车中移除项的错误。
	ErrCantGetItem = errors.New("was unnable to get the item from the cart")  //表示无法从购物车中获取项的错误。
	ErrCantBuyCartItem = errors.New("cannot update the purchase")  // 表示无法更新购买的错误。
)

func AddProductToCart() {

}

func RemoveCartItem() {

}

func BuyItemFromCart() {

}

func InstantBuyer() {

}
  1. 其次,搭建 database/databasetup.go 数据库层骨架
package database

import (
	"context"
	"log"
	"fmt"
	"time"
	
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func DBSet() *mongo.Client {
	// 创建一个带有 10 秒超时限制的上下文 ctx
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// 使用 mongo.Connect 方法创建并连接到 MongoDB 客户端
	client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
	if err != nil {
		log.Fatal(err)
	}

	// 使用 Ping 方法检查连接是否成功
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Println("failed to connect to mongodb :(", err)
		return nil
	}

	fmt.Println("Successfully connected to mongodb")

	// 连接成功,返回配置完成的 MongoDB 客户端实例
	return client
}
// 调用 DBSet() 函数,获取一个 MongoDB 客户端实例,并将其赋值给全局变量 Client
// Client 可以在程序的其他部分使用,以与 MongoDB 进行交互。
var Client *mongo.Client = DBSet()

func UserData(client *mongo.Client, collectionName string) *mongo.Collection{
	// 从数据库 "Ecommerce" 中获取指定名称的集合
	var collection *mongo.Collection = client.Database("Ecommerce").Collection(collectionName)
	// 返回获取到的集合
	return collection
}

func ProductData(client *mongo.Client, collectionName string) *mongo.Collection{
	// 从数据库 "Ecommerce" 中获取指定名称的集合
	var productCollection *mongo.Collection = client.Database("Ecommerce").Collection(collectionName)
	// 返回获取到的集合
	return productCollection
}

编写业务逻辑

实现登录注册接口
  1. 编写 controllers/controllers.go 业务逻辑层
  1. 密码哈希处理
// HashPassword 接受一个明文密码,并返回其加密后的哈希值。
func HashPassword(password string) string {
	// 生成密码哈希
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
	if err != nil {
		// 如果生成哈希过程中发生错误,则记录错误并引发Panic
		log.Panic(err)
	}
	// 返回密码哈

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

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

相关文章

归并排序【C语言版-笔记】

目录 一、概念二、排序流程理解三、代码实现3.1主调函数3.2 merge函数 四、性能分析 一、概念 归并是一种算法思想,是将两个或两个一上的有序表合并成一个长度较大的有序表。若一开始无序表中有n个元素,可以把n个元素看作n个有序表,把它们两…

Java中数据转换以及字符串的“+”操作

隐式转换(自动类型转换) 较小范围的数据类型转成较大范围的数据类型 强制转换(显式转换) 将数据范围大的数据类型转换为数据范围小的数据类型 基本数据类型之间的转换 当需要将一个较大的数据类型(如float或double…

Linux:进程控制(一)

目录 一、写时拷贝 1.创建子进程 2.写时拷贝 二、进程终止 1.函数返回值 2.错误码 3.异常退出 4.exit 5._exit 一、写时拷贝 父子进程,代码共享,不作写入操作时,数据也是共享的,当任意一方试图写入,便通过写时拷…

影刀RPA实战:excel相关图片操作指令解

1.实战目标 excel是工作中必不缺少的工具,今天我们继续使用影刀RPA来实现excel操作的便利性,让影刀自动化来帮我们完成工作。 2.单元格填充图片 2.1 指令说明 功能:向 Excel 单元格插入本地图片或网络图片,支持Office和WPS&…

波阻抗,是电场矢量的模值/磁场矢量的模值

波阻抗是电场复振幅除以磁场复振幅,最后只与介质με有关 所以磁场可用电场强度表示(利用波阻抗) 问题,复振幅是矢量,波阻抗的定义是复振幅的比值?答案:不是,很明显,波阻…

Web3 游戏周报(9.22 - 9.28)

回顾上周的区块链游戏概况,查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【9.22-9.28】Web3 游戏行业动态: Axie Infinity 将 Fortune Slips 的冷却时间缩短至 24 小时,从而提高玩家的收入。 Web3 游戏开发商 Darkbright Studios…

Pikachu-Sql Inject-搜索型注入

MySQL的搜索语句: select * from table where column like %text%; 如:使用引号闭合左边的引号, or 11 把所有数据查询出来; # 注释掉后面的 引号等; test or 11# 查询出结果: 注入的核心点…

Cloneable接口(浅拷贝和深拷贝的区别)

前言 Object类中存在这一个clone方法,调用这个方法可以创建一个对象的“拷贝”。但是想要合法调用clone方法,必须要先实现Clonable接口,否则就会抛出CloneNotSupportedException异常。 1 Cloneable接口 //Cloneable接口声明 public interf…

CentOS 7文件系统

从centos7开始,默认的文件系统从ext4变成了XFS。随着虚拟化的应用越来越广泛,作为虚拟化磁盘来源的大文件(单个文件几GB级别)越来越常见。 1.XFS组成部分: XFS文件系统在数据的分布上主要划分为三部分:数据…

QT篇:QT介绍

一.QT概述 Qt 是一个跨平台的应用程序和用户界面框架,用于开发图形用户界面(GUI)应用程序以及命令行工 具。它最初由挪威的 Trolltech (奇趣科技)公司开发,现在由 Qt Company 维护,2020年12月8…

如何在网格中模拟腐烂扩散:如何使用广度优先搜索(BFS)解题

问题描述 你需要在一个二维的网格中处理橘子的腐烂扩散过程,网格中的每个单元格可以有三种状态: 0:表示空格,没有橘子。1:表示一个新鲜的橘子。2:表示一个腐烂的橘子,它可以在 1 分钟内让上下…

模拟算法(1)_替换所有的问号

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 模拟算法(1)_替换所有的问号 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌 目录 1. …

MHA携手Atlas:打造高效读写分离解决方案,引领数据库性能飞跃

作者简介:我是团团儿,是一名专注于云计算领域的专业创作者,感谢大家的关注 座右铭: 云端筑梦,数据为翼,探索无限可能,引领云计算新纪元 个人主页:团儿.-CSDN博客 目录 前言&#…

npm切换到淘宝镜像

1、输入以下命令后回车,npm切换至淘宝镜像 npm config set registry https://registry.npmmirror.com 2、输入以下命令后回车,检查是否切换成功 npm config get registry 若返回此信息,表示切换成功 3、切换后就可使用淘宝镜像加快npm包的…

C语言 | Leetcode C语言题解之第447题回旋镖的数量

题目: 题解: int cmpfunc(const void *a, const void *b) {return (*(int *)a - *(int *)b); } //计算组合数*2 int every(int count) {if (count 1) {return 0;} else {return count * (count - 1);} } //计算每个锚点能产生的回旋镖总数 int part(in…

【嵌入式系统】第18章 脉宽调试器(PWM)

目录 18.1 结构框图 18.3 功能说明 18.3.4 PWM 信号发生器 18.3.5 死区发生器 18.3.6 中断/ADC 触发选择器 18.3.7 同步方法 18.3.8 故障条件 18.3.9 输出控制块 LES 硬件介绍(12)正交编码接口QEI 19.1 结构框图 19.2 信号描述 19.3 功能说明…

4M-21: An Any-to-Any Vision Model for Tens of Tasks and Modalities论文精度

贡献:在21种高度不同的模态中训练一个统一的模型,并且对比专有模型不会有性能损失做法:将不同模态映射到不同的token空间,并且可以生成不同的模态token【Any-to-any】关键点:如何在不同的模态中应用tokenization进行映…

【MySQL 07】内置函数

目录 1.日期函数 日期函数使用场景: 2.字符串函数 字符串函数使用场景: 3.数学函数 4.控制流函数 1.日期函数 函数示例: 1.在日期的基础上加日期 在该日期下,加上10天。 2.在日期的基础上减去时间 在该日期下减去2天 3.计算两…

【MySQL】服务器管理与配置

MySQL服务器 服务器默认配置 查看服务器默认选项和系统变量 mysqld --verbose --help 查看运行时的系统变量,可以通过like去指定自己要查询的内容 状态变量的查看 系统变量和状态变量的作用域 全局作用域: 对于每个会话都会生效当前会话:只…

通信工程学习:什么是SNMP简单网络管理协议

SNMP:简单网络管理协议 SNMP(Simple Network Management Protocol,简单网络管理协议)是一种用于在计算机网络中管理网络节点(如服务器、工作站、路由器、交换机等)的标准协议。它属于OSI模型的应用层&#…