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

文章目录

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

基本数据配置

配置文件管理

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

server:port: 9988 #启动应用程序的端口号
datasource: #数据库配置信息driverName: mysqlhost: 127.0.0.1port: "3306"database: gin_demousername: rootpassword: rx123456charset: utf8loc: Asia/Shanghai

数据库配置

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

package commonimport ("fmt"_ "github.com/go-sql-driver/mysql""github.com/spf13/viper""gorm.io/driver/mysql""gorm.io/gorm""net/url"
)var DB *gorm.DBfunc 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 = dbreturn db
}

路由配置

新建 router/routes.go 文件:

package routerimport ("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 responseimport ("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 utilimport ("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 modelimport "gorm.io/gorm"type UserInfo struct {gorm.Model        //继承gorm的Model,里面包含了ID、CreatedAt、UpdatedAt、DeletedAtName       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 responseimport (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 middlewareimport ("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 middlewareimport ("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 middlewareimport (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 headertokenString := 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 中的userIduserId := claims.UserId//判定var user model2.UserInfocommon2.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 commonimport (model2 "gin-demo/model""github.com/dgrijalva/jwt-go""time"
)var jwtKey = []byte("a_secret_key") //证书签名秘钥(该秘钥非常重要,如果client端有该秘钥,就可以签发证书了)type Claims struct {UserId uintjwt.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) //生成tokentokenString, 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 controllersimport ("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.UserInfoctx.Bind(&requestUser)name := requestUser.Nametelephone := requestUser.Telephonepassword := 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) // 新增记录// 发放tokentoken, 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.UserInfoctx.Bind(&requestUser)//name := requestUser.Nametelephone := requestUser.Telephonepassword := 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.UserInfocommon2.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}// 发放tokentoken, 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.UserInfodb.Where("telephone=?", telephone).First(&user)//如果没有查询到数据,对于uint数据,默认值为:0if 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.mzph.cn/news/133171.shtml

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

相关文章

第8章_聚合函数

文章目录 1 聚合函数介绍1.1 AVG和SUM函数1.2 MIN和Max函数1.3 COUNT函数演示代码 2 GROUP BY2.1 基本使用2.2 使用多个列分组2.3 演示代码 3 HAVING3.1 基本使用3.2 WHERE和HAVING的对比3.3 演示代码 4 SELECT的执行过程4.1 查询的结构4.2 SELECT执行顺序4.3 SQL的执行原理演示…

VScode连接Xshell 并解决【过程试图写入的管道不存在】报错

一.下载vscode 国内镜像&#xff1a; https://vscode.cdn.azure.cn/stable/6c3e3dba23e8fadc360aed75ce363ba185c49794/VSCodeUserSetup-x64-1.81.1.exe二.打开vscode在扩展搜索SSH并安装 三.添加主机 按F1选择添加新的ssh主机 按格式输入后在左边会出现电视的图标 之后输入…

Web3.0的测试题

任务&#xff1a; 在前端开发一个查询UI&#xff0c;查询当前用户账户的ETH余额和指定ERC20合约中的余额 目标&#xff1a; UI框架指定使用 MUI (https://mui.com)需要查询到当前账户的ETH余额并展示在UI界面上需要输入ERC20合约地址后&#xff0c;查询到到当前账户在此ERC20…

Plist编辑软件 PlistEdit Pro mac中文版功能介绍

PlistEdit Pro mac是一款功能强大的Plist文件编辑软件。Plist文件是苹果公司开发的一种XML文件格式&#xff0c;用于存储应用程序的配置信息和数据。PlistEdit Pro可以帮助用户轻松地编辑和管理Plist文件。 PlistEdit Pro具有直观的用户界面和丰富的功能。用户可以使用该软件打…

关于Vue使用props传值遇到的一些问题

一、The data property “tableData” is already declared as a prop. Use prop default value instead. 翻译过来&#xff1a;数据属性“tableData”已声明为prop。请改prop默认值。 将父组件的prop传过去变量改一下 二、prop传值&#xff0c;子组件比父组件先渲染&#…

每天五分钟计算机视觉:池化层的反向传播

本文重点 卷积神经网络(Convolutional Neural Network,CNN)作为一种强大的深度学习模型,在计算机视觉任务中取得了巨大成功。其中,池化层(Pooling Layer)在卷积层之后起到了信息压缩和特征提取的作用。然而,池化层的反向传播一直以来都是一个相对复杂和深奥的问题。本…

企业如何搭建智能客服系统?

在数字化时代&#xff0c;企业面临着客户需求多样化、市场竞争日益激烈等多重挑战。为了更好地满足客户的需求、提供高效的服务&#xff0c;越来越多的企业开始搭建智能客服系统。智能客服系统结合了人工智能和自然语言处理技术&#xff0c;可以实现自动回复、智能推荐以及数据…

老电脑升级内存、固态硬盘、重新装机过程记录

基础环境&#xff1a; 电脑型号&#xff1a;联想XiaoXin700-15ISK系统版本&#xff1a;Windows10 家庭中文版 版本22H2内存&#xff1a;硬盘&#xff1a; 升级想法&#xff1a; 内存升级&#xff0c;固态硬盘升级&#xff0c;系统重装&#xff08;干净一点&#xff09; 升级内存…

基础课18——智能客服系统架构

1.基础设施层 基础设施主要包括以下几点&#xff1a; 1. 硬件设施&#xff1a;包括服务器、存储设备、网络设备等&#xff0c;这是整个系统运行的物理基础。 2. 软件设施&#xff1a;包括操作系统、数据库管理系统、自然语言处理(NLP)工具和机器学习算法等&#xff0c;这些是…

linux下实现电脑开机后软件自启动

实现linux的软件自启动&#xff0c;需要四个文件 第一个【displayScreen.desktop】文件&#xff0c;.desktop文件就是一个用来运行程序的快捷方式,也叫启动器&#xff0c;常用来自启动用的文件&#xff0c;内容如下 [Desktop Entry] #要执行的脚本位置 Exec/home/yicaobao/te…

从零开始搭建React+TypeScript+webpack开发环境-使用iconfont构建图标库

创建iconfont项目 进入iconfont官网&#xff0c;完成注册流程&#xff0c;即可创建项目。 无法访问iconfont可尝试将电脑dns改为阿里云镜像223.5.5.5和223.6.6.6 添加图标 在图标库里选择图标&#xff0c;加入购物车 将图标添加到之前创建的项目中 生成代码 将代码配置到项目…

Flink SQL时间属性和窗口介绍

&#xff08;1&#xff09;概述 时间属性&#xff08;time attributes&#xff09;&#xff0c;其实就是每个表模式结构&#xff08;schema&#xff09;的一部分。它可以在创建表的 DDL 里直接定义为一个字段&#xff0c;也可以在 DataStream 转换成表时定义。 一旦定义了时间…

06_es分布式搜索引擎2

一、DSL查询文档 1.DSL查询分类 ①查询所有&#xff1a;match_all ②全文检索&#xff1a;利用分词器对用户输入的内容分词&#xff0c;倒排索引去匹配 match_query multi_match_query ③精确查询&#xff1a;根据精确词条查找数据&#xff0c;查找的是keyword,数值,日期,b…

大数据毕业设计选题推荐-智慧小区大数据平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【C++】构造函数和析构函数第三部分(各种构造函数调用规则、多个对象的构造和析构、初始化列表)--- 2023.11.6

目录 各种构造函数的调用规则对象以值的方式给函数参数用一个已有的对象去初始化另一个对象函数的局部对象以值的方式从函数返回调用规则1调用规则2 多个对象的构造和析构初始化列表结束语 各种构造函数的调用规则 对象以值的方式给函数参数 实例&#xff1a; class Maker {…

Linux上编译sqlite3库出现undefined reference to `sqlite3_column_table_name‘

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 在Ubuntu 18上编译sqlite3库后在运行程序时出现undefined reference to sqlite3_column_table_name’的错误。网上的说法是说缺少SQLITE_ENABLE_COLUMN_M…

libevent

libevent 库概念和特点 开源。精简。跨平台&#xff08;Windows、Linux、maxos、unix&#xff09;。专注于网络通信&#xff08;不一定非用在网络当中&#xff0c;比如下面的读写管道&#xff09;。 libevent特性&#xff1a;基于"事件"&#xff0c;面向“文件描述符…

软件开发项目文档系列之十如何撰写测试用例

目录 1 概述1.1 编写目的1.2 定义1.3 使用范围1.4 参考资料1.5 术语定义 2 测试用例2.1 功能测试2.1.1 用户登录功能2.1.2 商品搜索功能 2.2 性能测试2.2.1 网站响应时间2.2.2 并发用户测试 附件&#xff1a; 测试用例撰写的要素和注意事项附件1 测试用例要素附件2 测试用例的注…

【鸿蒙软件开发】ArkUI容器组件之Grid(网格布局)

文章目录 前言一、Grid1.1 子组件GridItem是什么子组件接口属性事件示例代码 1.2 接口参数 1.3 属性1.4 Grid的几种布局模式1.5 GridDirection枚举说明1.6事件ItemDragInfo对象说明 1.7 示例代码 总结 前言 Grid容器组件&#xff1a;网格容器&#xff0c;由“行”和“列”分割…

【数据结构初级(2)】单链表的基本操作和实现

文章目录 Ⅰ 概念及结构1. 单链表的概念2. 单链表的结构 Ⅱ 基本操作实现1. 定义单链表结点2. 创建新结点3. 单链表打印4. 单链表尾插5. 单链表头插6. 单链表尾删7. 单链表头删8. 单链表查找9. 在指定 pos 位置前插入结点10. 删除指定 pos 位置的结点11. 单链表销毁 本章实现的…