Go语言Gin框架前后端分离项目开发工程化实例

news2024/12/24 1:16:35

文章目录

  • 基本数据配置
    • 配置文件管理
    • 数据库配置
    • 路由配置
    • 封装公共方法
  • 数据库模型
    • 数据表内容
    • model文件
    • DTO文件
  • 中间件
    • 错误异常捕获中间件
    • 跨域中间件
    • token认证中间件
    • JWT
  • 控制器
    • UserController
  • 运行调试
    • 注册接口
    • 登录接口
    • 获取用户信息
  • 构建发布项目
  • 前端VUE调用接口

基本数据配置

配置文件管理

添加依赖 go get github.com/spf13/viper,支持 JSON, TOML, YAML, HCL等格式的配置文件。在项目根目录下面新建 conf 目录,然后新建 application.yml 文件,写入内容如下:

server:
  port: 9988 #启动应用程序的端口号
datasource: #数据库配置信息
  driverName: mysql
  host: 127.0.0.1
  port: "3306"
  database: gin_demo
  username: root
  password: rx123456
  charset: utf8
  loc: Asia/Shanghai

数据库配置

创建 common/database.go 文件,使用 gorm 初始化数据库配置:

package common

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/spf13/viper"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"net/url"
)

var DB *gorm.DB

func InitDB() *gorm.DB {
	//从配置文件中读取数据库配置信息
	host := viper.GetString("datasource.host")
	port := viper.Get("datasource.port")
	database := viper.GetString("datasource.database")
	username := viper.GetString("datasource.username")
	password := viper.GetString("datasource.password")
	charset := viper.GetString("datasource.charset")
	loc := viper.GetString("datasource.loc")
	args := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=true&loc=%s",
		username,
		password,
		host,
		port,
		database,
		charset,
		url.QueryEscape(loc))
	fmt.Println(args)
	db, err := gorm.Open(mysql.Open(args), &gorm.Config{})
	if err != nil {
		fmt.Println(err)
		panic("failed to connect database, err: " + err.Error())
	}
	DB = db
	return db
}

路由配置

新建 router/routes.go 文件:

package router

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

func CollectRoute(r *gin.Engine) *gin.Engine {
	r.Use(middleware.CORSMiddleware(), middleware.RecoverMiddleware())    //使用中间件
	r.POST("/api/auth/register", controller.Register)                     //注册
	r.POST("/api/auth/login", controller.Login)                           //登录
	r.GET("/api/auth/userinfo", middleware.AuthMiddleware(), controllers.UserDetail) //获取详情
	return r
}

封装公共方法

新建 response/response.go 文件:

package response

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 封装的响应体
func Response(ctx *gin.Context, httpStatus int, code int, data gin.H, msg string) {
	ctx.JSON(httpStatus, gin.H{
		"code": code,
		"data": data,
		"msg":  msg,
	})
}
func Success(ctx *gin.Context, data gin.H, msg string) {
	Response(ctx, http.StatusOK, 200, data, msg)
}
func Fail(ctx *gin.Context, data gin.H, msg string) {
	Response(ctx, http.StatusOK, 400, data, msg)
}

新建 util/util.go 文件

package util

import (
	"math/rand"
	"time"
)

// 生成随机字符串
func RandomString(n int) string {
	var letters = []byte("asdfghjklzxcvbnmqwertyuiopASDFGHJKLZXCVBNMQWERTYUIOP")
	result := make([]byte, n)
	rand.Seed(time.Now().Unix())
	for i := range result {
		result[i] = letters[rand.Intn(len(letters))]
	}
	return string(result)
}

数据库模型

数据表内容

CREATE TABLE `user_infos` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL DEFAULT '',
  `telephone` varchar(11) NOT NULL DEFAULT '',
  `password` varchar(255) NOT NULL DEFAULT '',
  `created_at` datetime(3) DEFAULT NULL,
  `updated_at` datetime(3) DEFAULT NULL,
  `deleted_at` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

model文件

新建 model/User.go 文件:

package model

import "gorm.io/gorm"

type UserInfo struct {
	gorm.Model        //继承gorm的Model,里面包含了ID、CreatedAt、UpdatedAt、DeletedAt
	Name       string `gorm:"type:varchar(20);not null"`
	Telephone  string `gorm:"varchar(11);not null;unique"`
	Password   string `gorm:"size:255;not null"`
}

DTO文件

DTO就是数据传输对象(Data Transfer Object)的缩写;用于展示层与服务层之间的数据传输对象。
新建 response/user_dto.go文件:

package response

import (
	model2 "gin-demo/model"
)

type UserDto struct {
	Name      string `json:"name"`
	Telephone string `json:"telephone"`
}

// DTO就是数据传输对象(Data Transfer Object)的缩写;用于 展示层与服务层之间的数据传输对象
func ToUserDto(user model2.UserInfo) UserDto {
	return UserDto{
		Name:      user.Name,
		Telephone: user.Telephone,
	}
}

中间件

错误异常捕获中间件

新建 middleware/RecoveryMiddleware.go 文件:

package middleware

import (
	"fmt"
	response2 "gin-demo/response"
	"github.com/gin-gonic/gin"
)

func RecoverMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				response2.Fail(c, nil, fmt.Sprint(err))
				c.Abort()
				return
			}
		}()
	}
}

跨域中间件

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。新建 middleware/CORSMiddleware.go 文件:

package middleware

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 跨域中间件
func CORSMiddleware() gin.HandlerFunc { //CORS是跨源资源分享(Cross-Origin Resource Sharing)中间件
	return func(ctx *gin.Context) {
		//指定允许其他域名访问
		//ctx.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080")
		ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") //跨域:CORS(跨来源资源共享)策略
		//预检结果缓存时间
		ctx.Writer.Header().Set("Access-Control-Max-Age", "86400")
		//允许的请求类型(GET,POST等)
		ctx.Writer.Header().Set("Access-Control-Allow-Methods", "*")
		//允许的请求头字段
		ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*")
		//是否允许后续请求携带认证信息(cookies),该值只能是true,否则不返回
		ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		if ctx.Request.Method == http.MethodOptions {
			ctx.AbortWithStatus(200)
		} else {
			ctx.Next()
		}
	}
}

token认证中间件

新建 middleware/AuthMiddleware.go 文件:

package middleware

import (
	common2 "gin-demo/common"
	model2 "gin-demo/model"
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
)

// token认证中间件(权限控制)
func AuthMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		auth := "jiangzhou"
		// 获取authorization header
		tokenString := ctx.GetHeader("Authorization") //postman测试:在Headers中添加: key:Authorization;value:jiangzhou:xxx(token值)
		//fmt.Println(tokenString)
		//fmt.Println(strings.HasPrefix(tokenString,auth+""))
		// 无效的token
		//if tokenString == "" || !strings.HasPrefix(tokenString, "Bearer ") { //验证token的前缀为:
		if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") { //验证token的前缀为:
			ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
			ctx.Abort()
			return
		}
		index := strings.Index(tokenString, auth+":") //找到token前缀对应的位置
		tokenString = tokenString[index+len(auth)+1:] //截取真实的token(开始位置为:索引开始的位置+关键字符的长度+1(:的长度为1))
		//fmt.Println("截取之后的数据:",tokenString)
		token, claims, err := common2.ParseToken(tokenString)
		if err != nil || !token.Valid { //解析错误或者过期等
			ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
			ctx.Abort()
			return
		}

		// 验证通过后获取claim 中的userId
		userId := claims.UserId
		//判定
		var user model2.UserInfo
		common2.DB.First(&user, userId)
		if user.ID == 0 { //如果没有读取到内容,说明token值有误
			ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})
			ctx.Abort()
			return
		}
		ctx.Set("user", user) //将key-value值存储到context中
		ctx.Next()

	}
}

JWT

新建 common/jwt.go 文件:

package common

import (
	model2 "gin-demo/model"
	"github.com/dgrijalva/jwt-go"
	"time"
)

var jwtKey = []byte("a_secret_key") //证书签名秘钥(该秘钥非常重要,如果client端有该秘钥,就可以签发证书了)

type Claims struct {
	UserId uint
	jwt.StandardClaims
}

// 分发证书
func ReleaseToken(user model2.UserInfo) (string, error) {
	expirationTime := time.Now().Add(7 * 24 * time.Hour) //截止时间:从当前时刻算起,7天
	claims := &Claims{
		UserId: user.ID,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expirationTime.Unix(), //过期时间
			IssuedAt:  time.Now().Unix(),     //发布时间
			Issuer:    "jiangzhou",           //发布者
			Subject:   "user token",          //主题
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) //生成token
	tokenString, err := token.SignedString(jwtKey)             //签名

	if err != nil {
		return "", err
	}

	return tokenString, nil
}

// 解析证书
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
	claims := &Claims{}
	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
		return jwtKey, nil
	})
	return token, claims, err
}

控制器

UserController

新建 controllers/UserController.go 文件:

package controllers

import (
	"fmt"
	common2 "gin-demo/common"
	model2 "gin-demo/model"
	response2 "gin-demo/response"
	util2 "gin-demo/util"
	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
	"net/http"
)

// 注册
func UserRegister(ctx *gin.Context) {
	var requestUser model2.UserInfo
	ctx.Bind(&requestUser)
	name := requestUser.Name
	telephone := requestUser.Telephone
	password := requestUser.Password
	// 数据验证
	if len(telephone) != 11 {
		//422 Unprocessable Entity 无法处理的请求实体
		response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "手机号必须为11位")
		fmt.Println(telephone, len(telephone))
		return
	}
	if len(password) < 6 {
		response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "密码不能少于6位")
		return
	}
	// 如果名称没有传,给一个10位的随机字符串
	if len(name) == 0 {
		name = util2.RandomString(10)
	}
	// 判断手机号是否存在
	if isTelephoneExist(common2.DB, telephone) {
		response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "用户已经存在")
		return
	}
	// 创建用户
	//返回密码的hash值(对用户密码进行二次处理,防止系统管理人员利用)
	hashPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		response2.Response(ctx, http.StatusInternalServerError, 500, nil, "加密错误")
		return
	}
	newUser := model2.UserInfo{
		Name:      name,
		Telephone: telephone,
		Password:  string(hashPassword),
	}
	common2.DB.Create(&newUser) // 新增记录
	// 发放token
	token, err := common2.ReleaseToken(newUser)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500, "msg": "系统异常"})
		return
	}
	// 返回结果
	response2.Success(ctx, gin.H{"token": token}, "注册成功")
}
func UserLogin(ctx *gin.Context) {
	var requestUser model2.UserInfo
	ctx.Bind(&requestUser)
	//name := requestUser.Name
	telephone := requestUser.Telephone
	password := requestUser.Password
	// 数据验证
	if len(telephone) != 11 {
		//422 Unprocessable Entity 无法处理的请求实体
		response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "手机号必须为11位")
		fmt.Println(telephone, len(telephone))
		return
	}
	if len(password) < 6 {
		response2.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "密码不能少于6位")
		return
	}
	// 依据手机号,查询用户注册的数据记录
	var user model2.UserInfo
	common2.DB.Where("telephone=?", telephone).First(&user)
	if user.ID == 0 {
		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422, "msg": "用户不存在"})
		return
	}
	// 判断密码收否正确
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"code": 400, "msg": "密码错误"})
		return
	}
	// 发放token
	token, err := common2.ReleaseToken(user)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500, "msg": "系统异常"})
		return
	}
	// 返回结果
	response2.Success(ctx, gin.H{"token": token}, "登录成功")

}
func UserDetail(ctx *gin.Context) {
	user, _ := ctx.Get("user")
	response2.Success(ctx, gin.H{
		"user": response2.ToUserDto(user.(model2.UserInfo))}, "响应成功")
}
func isTelephoneExist(db *gorm.DB, telephone string) bool {
	var user model2.UserInfo
	db.Where("telephone=?", telephone).First(&user)
	//如果没有查询到数据,对于uint数据,默认值为:0
	if user.ID != 0 {
		return true
	}
	return false
}

运行调试

注册接口

在这里插入图片描述
在这里插入图片描述

登录接口

在这里插入图片描述

获取用户信息

在header中传递token数据
在这里插入图片描述

构建发布项目

在项目根目录下执行 go build,然后会生成 gin-demo 的文件,然后可以将这个二进制文件拷贝到任意目录下,另外需要将项目下面的 conf 目录也拷贝过去。
在这里插入图片描述
然后执行 ./gin-demo 即可运行服务:
在这里插入图片描述

以上代码参考:https://gitee.com/rxbook/gin-demo

前端VUE调用接口

准备了一个简单的前端页面,代码在https://gitee.com/rxbook/vue-demo1 ,本地运行:

#安装依赖
npm install 
#运行
npm run serve

发布构建:

npm run build

构建完成后会生成 dist 目录,然后在nginx中配置虚拟主机:

server {
        listen          80;
        server_name     vue-demo1.cc;
        root    /home/rx/web_front/vue-demo1/dist;

        location ^~ /api/ {
                proxy_pass http://127.0.0.1:9988;
        }
}

配置 /etc/hosts 后,在浏览器访问:
在这里插入图片描述

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

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

相关文章

持续集成交付CICD:Jenkins Pipeline与远程构建触发器

目录 一、实验 1.Jenkins Pipeline本地构建触发器 2.Jenkins Pipeline与远程构建触发器&#xff08;第一种方式&#xff09; 3.Jenkins Pipeline与远程构建触发器&#xff08;第二种方式&#xff09; 4.Jenkins Pipeline与远程构建触发器&#xff08;第三种方式&#xff0…

Android 安卓 Soong构建系统——Blueprint Android.bp配置文件解析

文章目录 Android.bp起源Android.bp文件结构如何编写Android.bp文件实例详解实例1实例2 常见问题解答1. 如何确定使用哪种模块类型&#xff1f;2. 如何指定模块的依赖项&#xff1f;其他疑问可参考官方文档 参考文章&#xff1a;Android.bp 语法和使用 Android.bp起源 早期的A…

Python二级 每周练习题25

如果你感觉有收获&#xff0c;欢迎给我打赏 ———— 以激励我输出更多优质内容 练习一: 运算规则如下: (1) 若该数是偶数&#xff0c;&#xff0c;则变为原数的一半 (2) 若该数是奇数&#xff0c;则变为原数的3倍加1 (3) 重复 (1) (2)&#xff0c;直到该数变为1。 编写程序实…

Gradle笔记 二 Gradle的基础Groovy

学习Groovy的必要性 首先Gradle是由Groovy写成的&#xff0c;而且构建脚本的语法都遵循Groovy的语法&#xff0c;所以要学好Gradle的前提是要基本了解Groovy的语法。 Groovy 简介 在某种程度上&#xff0c;Groovy可以被视为Java的一种脚本化改良版,Groovy也是运行在JVM上&am…

什么是微服务?与分布式又有什么区别?

什么是微服务&#xff0c;我们先从传统的单体结构进行了解&#xff0c;对两者进行对比。 单体结构 单体结构是一种传统的软件架构模式&#xff0c;它将应用程序划分为一组相互依赖的模块和组件。这些模块和组件通常都是构建在同一个平台上的&#xff0c;并且紧密耦合在一起。…

本地生活商家想选择靠谱的服务商就这样做,还可以借助批量剪辑来进一步提升营销价值

本地生活商家怎么选择靠谱的服务商&#xff1f; 在抖音本地生活的赛道里&#xff0c;商家除了花精力去搭建自己的团队之外&#xff0c;还可以选择和服务商合作&#xff0c;来实现商单的分发与销售&#xff0c;那么如何和服务商建立合作呢&#xff1f; 今天&#xff0c;来为商…

MG-Soft MIB Browser使用教程

图片 MG-Soft公司是一家老牌的监控工具&#xff0c;是目前全球领先的网络管理&#xff0c;SNMP监控的领导厂商&#xff1b; 我发现很多客户都在使用该软件&#xff0c;比如近期参加的某大型企业招标测试就使用的该软件&#xff0c;该软件比我之前写的ireasoning MIB Browser …

做什么数据表格啊,要做就做数据可视化

是一堆数字更易懂&#xff0c;还是图表更易懂&#xff1f;很明显是图表&#xff0c;特别是数据可视化图表。数据可视化是一种将大量数据转化为视觉形式的过程&#xff0c;通过图形、图表、图像等方式呈现数据&#xff0c;以便更直观地理解和分析。 数据可视化更加生动、形象地…

文件怎么加密丨4种文件加密方法盘点

一 、如何给word文件加密&#xff1f; 1. 打开word&#xff0c;点击“文件”。 2. 点击“信息”&#xff0c;选择“保护文档”&#xff0c;并选择“用密码进行加密”。 3. 在弹出的小窗口&#xff0c;我们可以添加密码&#xff0c;并点击确定即可。 二、如何给excel表格进行加…

什么是数据库?数据库有哪些基本分类和主要特点?

数据库是以某种有组织的方式存储的数据集合。本文从数据库的基本概念出发&#xff0c;详细解读了数据库的主要类别和基本特点&#xff0c;并就大模型时代备受瞩目的数据库类型——向量数据库进行了深度剖析&#xff0c;供大家在了解数据库领域的基本概念时起到一点参考作用。 …

Linux 服务器监控

服务器几乎与任何 IT 基础设施密不可分&#xff0c;Linux 是服务器兼容性最强的开源操作系统&#xff0c;因为它具有灵活性、一致性和安全性。大多数 Linux 服务器都设置了以下 Linux 操作系统的任何变体&#xff1a;Red Hat Enterprise Linux &#xff08;RHEL&#xff09;、D…

分享5款有趣的软件,你都知道吗?

​ 今天我想分享几个有趣但或许不那么多人知道的软件&#xff0c;各位喜欢的朋友可以自行下载呢。 1.文本比较软件——Diffchecker ​ Diffchecker是一款在线文本比较软件&#xff0c;可以找出两个文本文件之间的差异。Diffchecker可以比较文本、代码、PDF、Word等多种格式的…

day10-内核实验项目概述与内核信号

1.在系统中什么是信号&#xff0c;都有什么信号 2.在系统接收到信号后&#xff0c;他是如何处理的 3.信号作用 信号处理流程&#xff1a; _system_call: call _sys_call_table(,%eax,4) sys_signal sys_signal int sys_signal(int signum, long handler, long restorer) {/…

打造工业数字基础设施坚实底座,麒麟信安参展2023工业软件生态大会

11月5日&#xff0c;2023工业软件生态大会在深圳揭开帷幕。以“共建新一代工业软件体系&#xff0c;引领制造业高质量发展”为主题&#xff0c;本次大会邀请政府、高校、行业专家和企业代表齐聚一堂&#xff0c;展示工业全产业链前沿技术、创新产品和解决方案&#xff0c;围绕核…

零代码编程:用ChatGPT批量合并文件夹

一个文件夹里面有很多个子文件夹&#xff0c;其中一些是互相配对的&#xff0c;比如&#xff1a; 动物寓言王国_合作力量大 &#xff08;上&#xff09;国英语版 动物寓言王国_合作力量大 &#xff08;下&#xff09;国英语版 动物寓言王国-狮子与猎狗 &#xff08;上&#…

出现 Daemons using outdated libraries 的解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 使用apt安装某些包的时候,弹出如下界面: Daemons using outdated libraries的选择框 并问我需要重启的服务,which services should be restarted? 不知什么情况,选择esc之后,所安装的包并没有成功!于是深入剖析 2.…

falsk框架中安装flask-mysqldb报错解决方案

错误示例 我的是py37版本&#xff0c;无法直接安装flask-mysqldb pip install flask-mysqldb报错如下 解决方案 先去第三方库 https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient 下载mysqlclient 这个是我的版本 mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl 下…

麒麟信安协办 | 2023工业控制系统产业大会隆重举办

11月3日&#xff0c;由中国高科技产业化研究会、湖南省科学技术协会、国防科技大学、湖南大学、中南大学共同主办&#xff0c;工业控制系统产业联盟、长沙经济技术开发区管委会等单位共同承办&#xff0c;麒麟信安等单位协办的“2023 工业控制系统产业大会”在长沙召开。此次会…

中创|多家AI大模型获批上线,“百模”大战已打响,掀起新一轮AI风暴!

9个AI大模型获批上线 “百模”大战已正式打响 拼算法、争数据、卷算力...... 大模型们到底该比拼什么&#xff1f; 它们将给生活和产业变革 带来哪些惊喜和挑战&#xff1f; 今年8月&#xff0c;8家大模型备案通过&#xff0c;百度、字节、智谱华章、商汤、中科院、百川智…

Mach Systems—总线接口转换工具

产品概述 在汽车研发过程中&#xff0c;需要大量的总线数据支持&#xff0c;总线转换工具可以将总线数据转换为我们计算机常见接口&#xff0c;达到总线监视、采集、仿真等目的。MACH SYSTEMS来自于捷克的一家公司&#xff0c;专注于嵌入式和车载网络转换&#xff0c;为LIN、S…