1 创建数据库
表结构如下图
2 创建 GitGub 项目仓库
在 GitHub 上创建 GinCoBlog
仓库,并将其clone
到本地
3 初始化项目
在clone
下来的目录下创建:
- config // 配置文件所在目录
- controller // 控制器所在目录
- entity // 数据库实体类所在目录
- logs // 运行日志所在目录
- middleware // 中间件所在目录
- model // 数据库连接所在目录
- request // 参数实体类所在目录
- router // 路由所在目录
- temp // 缓存文件
- utils // 工具所在目录
- .gitignore // git 不上传的文件列表
- Application.yaml // 项目常量定义列表
- main.go // 项目主入口
3.1 加载依赖
## 加载 Gin 框架
go get -u github.com/gin-gonic/gin
## 加载 bcrypt 依赖
go get golang.org/x/crypto/bcrypt
## token依赖
go get github.com/dgrijalva/jwt-go
## 跨域依赖
go get github.com/gin-contrib/cors
## 日志依赖
go get github.com/sirupsen/logrus
## 邮箱依赖
go get gopkg.in/gomail.v2
## yaml依赖
go get gopkg.in/yaml.v2
## mysql驱动
go get gorm.io/driver/mysql
## 数据库依赖
go get gorm.io/gorm
## redis 驱动
go get github.com/redis/go-redis/v9
## UUID 生成器
go get -u github.com/satori/go.uuid
3.2 编写项目主入口
在main.go
中编写
/**
* @projectName: GinCoBlog
* @package: GinCoBlog
* @className: main
* @author: 张杰
* @description: TODO
* @date: 2023/10/4 9:23
* @version: 1.0
*/
package main
import (
"github.com/gin-gonic/gin"
"net/http")
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "HelloWorld!")
})
err := router.Run(":4001")
if err != nil {
panic("项目启动失败!")
return
}
}
运行:
go run main.go
测试:
在浏览器输入
127.0.0.1:4001
3.3 编写项目配置文件
在 Application.yaml
中
# 项目端口号
port: 4001
在 config
目录下创建 ConfigDefault.go
package config
import (
"gopkg.in/yaml.v2"
"os")
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
}
// Default /** 获取 yaml 配置
func Default() Config {
// 实例化配置对象
var configObj Config
// 读取配置文件
yamlFil, err := os.ReadFile("Application.yaml")
// 读取失败
if err != nil {
panic(err)
}
// 将读到的文件解析为配置对象
err = yaml.Unmarshal(yamlFil, &configObj)
// 解析失败
if err != nil {
panic(err)
}
// 抛出配置文件对象
return configObj
}
在 main.go
下:
/**
* @projectName: GinCoBlog *
* @package: GinCoBlog *
* @className: main * @author: 张杰
* @description: TODO
* @date: 2023/10/4 9:23 *
* @version: 1.0 *
*/
package main
// 引入相关依赖
import (
"GinCoBlog/config"
"github.com/gin-gonic/gin" "net/http")
// 项目主方法
func main() {
// 获取项目路由
router := gin.Default()
// 编写项目基础接口
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "HelloWorld!")
})
// 开启端口监听
err := router.Run(":" + config.Default().Port)
// 开启监听失败
if err != nil {
// 写入日志
panic("项目启动失败!")
return
}
}
运行:
go run main.go
测试:
在浏览器输入
127.0.0.1:4001
![[gin_co_blog_assets/Pasted image 20231008150332.png]]
3.4 配置基础中间件
3.4.1 配置日志中间件
在 Application.yaml
中添加
# 日志配置
log_config:
# 日志存储位置
path: 'logs/'
# 日志名称
name: 'gin.log'
在 config/ConfigDefalult.go
中添加
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"` // 日志存放路径
Name string `yaml:"name"` // 日志名称
} `yaml:"log_config"`
}
// LogFilePath /**日志存放地址
var LogFilePath = Default().LogConfig.Path
// LogFileName /**日志文件名
var LogFileName = Default().LogConfig.Name
在 middleware
目录下创建 LoggerMiddleware.go
package middleware
import (
"GinCoBlog/config"
"fmt" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "os" "path" "time")
// LoggerToFile 日志记录到文件
func LoggerToFile() gin.HandlerFunc {
logFilePath := config.LogFilePath
logFileName := config.LogFileName
//日志文件
fileName := path.Join(logFilePath, logFileName)
//写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, os.ModeAppend)
if err != nil {
logrus.Error("err", err)
}
//实例化
logger := logrus.New()
//设置输出
logger.Out = src
//设置日志级别
logger.SetLevel(logrus.DebugLevel)
//设置日志格式
logger.SetFormatter(&logrus.TextFormatter{})
return func(c *gin.Context) {
// 开始时间
startTime := time.Now()
// 处理请求
c.Next()
// 结束时间
endTime := time.Now()
// 执行时间
latencyTime := endTime.Sub(startTime)
// 请求方式
reqMethod := c.Request.Method
// 请求路由
reqUri := c.Request.RequestURI
// 状态码
statusCode := c.Writer.Status()
// 请求IP
clientIP := c.ClientIP()
// 日志格式
logger.Infof("| %3d | %13v | %15s | %s | %s |",
statusCode,
latencyTime,
clientIP,
reqMethod,
reqUri,
)
}
}
// LoggerToMongo 日志记录到 MongoDBfunc LoggerToMongo() gin.HandlerFunc {
return func(c *gin.Context) {
}}
// LoggerToES 日志记录到 ESfunc LoggerToES() gin.HandlerFunc {
return func(c *gin.Context) {
}}
// LoggerToMQ 日志记录到 MQfunc LoggerToMQ() gin.HandlerFunc {
return func(c *gin.Context) {
}
}
在 main.go
中挂载此中间件
// 挂载中间件
router.Use(gin.Logger(), gin.Recovery(), middleware.LoggerToFile())
3.4.2 挂载代理中间件
在 main.go
中添加
// 加载代理中间件
err := router.SetTrustedProxies([]string{"192.168.1.0/24"})
if err != nil {
fmt.Println("代理失败!")
return
}
3.4.3 配置跨域中间件
在 Application.yaml
中
# 跨域IP
cors:
ip:
- 'http://127.0.0.1'
- 'http://39.101.72.168'
- 'http://localhost'
methods:
- 'GET'
- 'POST'
- 'PUT'
- 'DELETE'
在 config/ConfigDefault.go
中
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"`
Name string `yaml:"name"`
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"`
Email string `yaml:"email"`
} `yaml:"regular"`
// 邮箱配置
EmailConfig struct {
EmailAddress string `yaml:"email_address"`
EmailName string `yaml:"email_name"`
Password string `yaml:"password"`
SmtpServer string `yaml:"smtp_server"`
SmtpPort int `yaml:"smtp_port"`
} `yaml:"email_config"`
// redis 配置
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
Password string `yaml:"password"`
DB int `yaml:"db"`
} `yaml:"redis_config"`
// 加密配置
Encryption struct {
// 私钥
PrivateKey struct {
Password string `yaml:"password"`
} `yaml:"private_key"`
// 盐值
Salt struct {
Password int `yaml:"password"`
} `yaml:"salt"`
} `yaml:"encryption"`
// token 配置
Token struct {
PrivateKey string `yaml:"private_key"`
} `yaml:"token"`
// 绕过中间件验证的地址
NotVerifyUrl []string `yaml:"not_verify_url"`
// 跨域中间件验证地址
Cors struct {
Ip []string `yaml:"ip"`
Methods []string `yaml:"methods"`
} `yaml:"cors"`
}
// Cors 跨域
var Cors = Default().Cors
在 middleware
目录下创建 CorsMiddleware.go
下
package middleware
import (
"GinCoBlog/config"
"github.com/gin-contrib/cors" "github.com/gin-gonic/gin")
// CorsMiddleware 跨域中间件
func CorsMiddleware() gin.HandlerFunc {
corsConfig := cors.DefaultConfig()
allowAccess := config.Cors.Ip
corsConfig.AllowOrigins = allowAccess // 允许访问的域名
corsConfig.AllowMethods = config.Cors.Methods
return cors.New(corsConfig)
}
在 main.go
中
// 挂载中间件
router.Use(gin.Logger(), gin.Recovery(), middleware.CorsMiddleware(), middleware.LoggerToFile(), middleware.JwtVerifyMiddle())
3.4.4 编写Token验证中间件
在 Application.yaml
中
# 不验证的接口地址
not_verify_url:
- '/api/user/phoneOccupy'
- '/api/user/emailOccupy'
- '/api/user/register'
- '/api/user/phoneLogin'
- '/api/user/emailLogin'
- '/api/user/sendEmailCode'
- '/api/user/forgotPassword'
在 config/ConfigDefault.go
中
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"`
Name string `yaml:"name"`
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"`
Email string `yaml:"email"`
} `yaml:"regular"`
// 邮箱配置
EmailConfig struct {
EmailAddress string `yaml:"email_address"`
EmailName string `yaml:"email_name"`
Password string `yaml:"password"`
SmtpServer string `yaml:"smtp_server"`
SmtpPort int `yaml:"smtp_port"`
} `yaml:"email_config"`
// redis 配置
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
Password string `yaml:"password"`
DB int `yaml:"db"`
} `yaml:"redis_config"`
// 加密配置
Encryption struct {
// 私钥
PrivateKey struct {
Password string `yaml:"password"`
} `yaml:"private_key"`
// 盐值
Salt struct {
Password int `yaml:"password"`
} `yaml:"salt"`
} `yaml:"encryption"`
// token 配置
Token struct {
PrivateKey string `yaml:"private_key"`
} `yaml:"token"`
// 绕过中间件验证的地址
NotVerifyUrl []string `yaml:"not_verify_url"`
}
在 utils/index.go
下
// IsContainArr /**Token URL过滤
func IsContainArr(noVerify []string, requestUrl string) bool {
for _, str := range noVerify {
if str == requestUrl {
return true
}
}
return false
}
在 service/UserService.go
下
// VerUserByToken 通过Token查找用户
func VerUserByToken(token string) (string, error) {
// 验证数据库中是否存有此token
user, err := RedisClient().Get(ctx, token).Result()
return user, err
}
在 middleware
目录下创建 TokenMiddleware.go
package middleware
import (
"GinCoBlog/config"
"GinCoBlog/request" "GinCoBlog/service" "GinCoBlog/utils" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "log")
// JwtVerifyMiddle /**验证token
func JwtVerifyMiddle() gin.HandlerFunc {
return func(c *gin.Context) {
//过滤是否验证token
if utils.IsContainArr(config.NotVerifyUrl, c.Request.URL.Path) {
return
}
token := c.GetHeader("Authorization")
if token == "" {
log.Println("")
utils.AuthorizationResult(c, "权限验证失败,无法访问系统资源!")
// 终止请求
c.Abort()
return
}
// 判断是否错误
claims := parseToken(c, token)
//验证token,并存储在请求中
c.Set("user", claims)
}
}
// 解析Token
func parseToken(c *gin.Context, tokenString string) *request.TokenParams {
// 验证数据库中是否存有此token
user, err := service.VerUserByToken(tokenString)
// 没查到
if err != nil || user == "" {
utils.AuthorizationResult(c, "权限验证失败,无法访问系统资源!")
c.Abort()
return nil
}
//解析token
token, err := jwt.ParseWithClaims(tokenString, &request.TokenParams{}, func(token *jwt.Token) (interface{}, error) {
return []byte(config.TokenPrivateKey), nil
})
// 解析成功
if err != nil {
utils.AuthorizationResult(c, "权限验证失败,无法访问系统资源!")
c.Abort()
return nil
}
// 将Token内存的数据转化为 token.Claims claims, ok := token.Claims.(*request.TokenParams)
// 转化失败
if !ok {
utils.AuthorizationResult(c, "登录失效!")
c.Abort()
return nil
}
// 抛出数据
return claims
}
4 正式编写
4.1 封装路由
在 router
目录下新建 UserRouter.go
package router
import (
"github.com/gin-gonic/gin"
"net/http")
// UserRouter 定义用户路由(二级)
func UserRouter(router *gin.RouterGroup) {
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "HelloUser!")
})
}
在 router
目录下新建 index.go
package router
import "github.com/gin-gonic/gin"
// SetupRouterGroup 项目主路由(一级)
func SetupRouterGroup(router *gin.RouterGroup) {
// 调取用户路由
UserRouter(router.Group("/user"))
}
在 main.go
中
/**
* @projectName: GinCoBlog
* @package: GinCoBlog
* @className: main
* @author: 张杰
* @description: TODO
* @date: 2023/10/4 9:23 * @version: 1.0
*/
package main
// 引入相关依赖
import (
"GinCoBlog/config"
routes "GinCoBlog/router"
"github.com/gin-gonic/gin" "net/http")
// 项目主方法
func main() {
// 获取项目路由
router := gin.Default()
// 编写项目基础接口
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "HelloWorld!")
})
// 调用项目主路由
routes.SetupRouterGroup(router.Group("/api"))
// 开启端口监听
err := router.Run(":" + config.Default().Port)
// 开启监听失败
if err != nil {
// 写入日志
panic("项目启动失败!")
return
}
}
4.2 提取控制器
在 controller
目录下创建 UserController.go
package controller
import (
"github.com/gin-gonic/gin"
"net/http")
// HelloUser 测试
func HelloUser(c *gin.Context) {
// 返回数据
c.JSON(http.StatusOK, gin.H{"msg": "HelloUser!"})
}
修改 router/UserRouter.go
package routes
import (
"GinCoBlog/controller"
"github.com/gin-gonic/gin")
// UserRouter 定义用户路由(二级)
func UserRouter(router *gin.RouterGroup) {
// 用户相关接口
router.GET("/", controller.HelloUser)
}
运行
go run main.go
测试
在浏览器输入
http://localhost:4001/api/user
4.3 编写数据库实体类
- SysUser
- ArticleType
- Article
- ArticleComments
- FeedBack
entity/SysUser.go
package entity
// SysUser 用户表结构
type SysUser struct {
UID uint32 `json:"uid" gorm:"primaryKey;autoIncrement;not null"` // 主键 用户ID
UserName string `json:"user_name"` // 用户名
Phone string `json:"phone" gorm:"not null;unique;size:11"` // 电话号码 不允许为空 唯一
HeadSculpture string `json:"head_sculpture" gorm:"default:'http://39.101.72.168:81/image/icon.jpg'"` // 头像 默认值
Password string `json:"password" gorm:"type:text;not null"` // 密码 默认值
Email string `json:"email" gorm:"not null;unique"` // 邮箱 不允许为空 唯一
Available bool `json:"available" gorm:"not null;default:0"` // 是否注销 不允许为空 默认值
Limit uint8 `json:"limit" gorm:"not null;default:2"` // 权限 不允许为空 默认值
Integral uint32 `json:"integral" gorm:"not null;default:0"` //用户积分
Member uint8 `json:"member" gorm:"not null;default:2"` // 会员: 0 超级会员、1 会员、2 非会员
UUID string `json:"uuid" gorm:"not null;type:uuid;default:uuid_generate_v4()"` // 用户身份
}
// TableName /** 复写默认方法,设置表名
func (SysUser) TableName() string {
return "sys_user"
}
entity/ArticleType.go
package entity
// ArticleType 文章类型表
type ArticleType struct {
ID uint32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` // 文章类型ID
RootID uint32 `json:"root_id" gorm:"not null"` // 文章类型根ID
TypeName string `json:"type_name" gorm:"not null;unique"` // 文章类型名称
TypeVisible bool `json:"type_visible" gorm:"not null;default:1"` // 类型是否显示
Order uint8 `json:"order" gorm:"not null;unique"` // 排序
Picture string `json:"picture" gorm:"not null"` // 类型图片
AddStatus bool `json:"edit_status" gorm:"not null; default:1"` // 是否可加入文章
}
// TableName /** 复写默认方法,设置表名
func (ArticleType) TableName() string {
return "article_type"
}
entity/Article.go
package entity
// Article 用户表结构
type Article struct {
ID int32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` // 文章ID
TypeId int32 `json:"typeId" gorm:"not null"` // 文章类型ID
UserId int32 `json:"userId" gorm:"not null"` // 用户ID
Title string `json:"title" gorm:"not null;unique"` // 文章标题
Context string `json:"context" gorm:"not null"` // 文章内容
ArticleVisible bool `json:"article_visible" gorm:"not null;default:1"` // 文章是否显示
Read int32 `json:"read" gorm:"not null;default:0"` // 阅读量
Date string `json:"date" gorm:"not null"` // 上传时间
Icon string `json:"icon" gorm:"not null"` // 文章图像
}
// TableName /** 复写默认方法,设置表名
func (Article) TableName() string {
return "article"
}
entity/ArticleCommits.go
package entity
// ArticleComments 文章评论表
type ArticleComments struct {
ID uint32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` // 评论ID
ArticleID uint32 `json:"article" gorm:"not null"` // 文章ID
UserID uint32 `json:"user_id" gorm:"not null"` // 用户ID
Context string `json:"context" gorm:"not null"` // 评论内容
}
// TableName /** 复写默认方法,设置表名
func (ArticleComments) TableName() string {
return "article_comments"
}
entity/FeedBack.go
package entity
// FeedBack 反馈表
type FeedBack struct {
ID uint32 `json:"id" gorm:"primaryKey;autoIncrement;not null"` // 评论ID
UserID uint32 `json:"user_id" gorm:"not null"` // 用户ID
Context string `json:"context" gorm:"not null"` // 反馈内容
}
// TableName /** 复写默认方法,设置表名
func (FeedBack) TableName() string {
return "feed_back"
}
4.4 编写数据库配置
在 Application.yaml
下添加
# 服务器配置
dataBase:
# 服务器地址
host: 'localhost'
# 服务器端口
port: '3306'
# 用户名
userName: 'root'
# 密码
password: '555555'
# 数据库名
schema: 'gin_co_blog'
# redis
redis_config:
host: '127.0.0.1'
port: '6379'
password: ''
db: 0
在 config/ConfigDefault.go
下修改
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// redis 配置
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
Password string `yaml:"password"`
DB int `yaml:"db"`
} `yaml:"redis_config"`
}
4.5 创建数据库连接并自动生成数据库表
在 service
目录下创建 index.go
package service
import (
"GinCoBlog/config"
"GinCoBlog/entity" "fmt" "gorm.io/driver/mysql" "gorm.io/gorm")
// Pool /** 创建数据库连接池
func Pool() *gorm.DB {
// 获取数据库配置
dbConfig := config.Default().DataBase
fmt.Println(dbConfig.Host)
// 创建数据库连接
db, err := gorm.Open(mysql.Open(dbConfig.UserName+":"+dbConfig.Password+"@tcp("+dbConfig.Host+":"+dbConfig.Port+")/"+dbConfig.Schema+"?charset=utf8mb4&parseTime=True&loc=Local"), &gorm.Config{})
// 连接失败
if err != nil {
panic("连接数据库失败")
}
// 抛出数据库连接池
return db
}
// VerDataBase /** 验证数据库/表是否已经创建
func VerDataBase() {
// 获取数据库连接池
pool := Pool()
// 验证sys_user表是否存在
err := pool.AutoMigrate(&entity.SysUser{})
err = pool.AutoMigrate(&entity.ArticleType{})
err = pool.AutoMigrate(&entity.Article{})
err = pool.AutoMigrate(&entity.ArticleComments{})
err = pool.AutoMigrate(&entity.FeedBack{})
// 表创建失败
if err != nil {
panic(err)
}
}
// RedisClient Redis 数据库连接
func RedisClient() *redis.Client {
// 拿到 Redis 数据库配置
redisConfig := config.Default().RedisConfig
rdb := redis.NewClient(&redis.Options{
Addr: redisConfig.Host + ":" + redisConfig.Port,
Password: redisConfig.Password,
DB: redisConfig.DB,
})
ctx := context.Background()
if _, err := rdb.Ping(ctx).Result(); err != nil {
log.Fatalln(err.Error())
}
return rdb
}
在 main.go
下
/**
* @projectName: GinCoBlog
* @package: GinCoBlog
* @className: main
* @author: 张杰
* @description: TODO
* @date: 2023/10/4 9:23
* @version: 1.0 */
package main
// 引入相关依赖
import (
"GinCoBlog/config"
"GinCoBlog/middleware" routes "GinCoBlog/router"
"GinCoBlog/service" "fmt" "github.com/gin-gonic/gin" "net/http")
// 项目主方法
func main() {
// 获取项目路由
router := gin.Default()
// 挂载中间件
router.Use(gin.Logger(), gin.Recovery(), middleware.LoggerToFile())
// 加载代理中间件
err := router.SetTrustedProxies([]string{"192.168.1.0/24"})
if err != nil {
fmt.Println("代理失败!")
return
}
// 验证数据表是否存在
service.VerDataBase()
// 编写项目基础接口
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "HelloWorld!")
})
// 调用项目主路由
routes.SetupRouterGroup(router.Group("/api"))
// 开启端口监听
err = router.Run(":" + config.Default().Port)
// 开启监听失败
if err != nil {
// 写入日志
panic("项目启动失败!")
return
}
}
4.6 定义返回值类型
在 config/ConfigDefault.go
下加入
// Response 返回值类型
type Response struct {
Code int `json:"code"` // 响应值
Message string `json:"msg"` // 提示信息
Data interface{} `json:"data"` // 数据
}
在 utils
下创建 index.go
// resultType 返回值方法
func resultType(code int, msg string, data interface{}) config.Response {
return config.Response{
Code: code,
Message: msg,
Data: data,
}
}
// SuccessResult 成功响应
func SuccessResult(c *gin.Context, msg string, data interface{}) {
c.JSON(http.StatusOK, resultType(http.StatusOK, msg, data))
}
// FailResult 错误响应
func FailResult(c *gin.Context, msg string) {
c.JSON(http.StatusBadRequest, resultType(http.StatusBadRequest, msg, nil))
}
// ServerErrorResult 服务器错误响应
func ServerErrorResult(c *gin.Context) {
c.JSON(http.StatusInternalServerError, resultType(http.StatusInternalServerError, "服务器错误!", nil))
}
// AuthorizationResult 权限错误响应
func AuthorizationResult(c *gin.Context, msg string) {
c.JSON(http.StatusUnauthorized, resultType(http.StatusUnauthorized, msg, nil))
}
4.7 编写用户名查重接口
接口地址:
/api/user/userNameOccupy
在 service
目录下创建 UserService.go
package service
import (
"GinCoBlog/entity"
"GinCoBlog/utils" "errors")
// 获取数据库连接池
var pool = Pool()
// UserNameOccupyService 用户名查重
func UserNameOccupyService(userName string) (error, bool) {
// 创建数据库数组对象
var user []entity.SysUser
// 验证用户名参数
// 验证是否为空
if utils.StrIsEmpty(userName) {
return errors.New("参数为空"), false
}
// 判断长度是否超出
if len(userName) > 15 {
return errors.New("参数超出 15 个字符"), false
}
// 判断用户名中是否含有非法字符串
// 通过调用 gorm api 进行查找数据库
pool.Where("user_name=?", userName).Find(&user)
// 判断查询的数组长度是否大于1
if len(user) > 1 {
return errors.New("数据库存储异常!"), false
}
// 返回用户是否存在
return nil, len(user) == 0
}
在 requset/UserRequest.go
下
package request
// UserNameOccupyParams 用户名查重参数
type UserNameOccupyParams struct {
UserName string `json:"user_name" binding:"required,min=3,max=15"`
}
在 controller/UserController.go
下添加
// UserNameOccupy 用户名查重
func UserNameOccupy(c *gin.Context) {
// 获取参数接口实例
var param request.UserNameOccupyParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 调用接口查找用户名是否已存在
err, isUserName := service.UserNameOccupyService(param.UserName)
// 如果发生错误
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 判断用户名是否存在
if isUserName {
utils.FailResult(c, "用户名已存在!")
return
}
// 如果用户名不存在
utils.SuccessResult(c, "可以使用", nil)
}
在 router/UserRouter.go
下添加
// 用户名查重
router.GET("/userNameOccupy", controller.UserNameOccupy)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.8 编写电话号码查重接口
接口地址:
/api/user/phoneOccupy
在 Application.yaml
下添加:
# 正则
regular:
# 电话号码正则
phone: '^(13[0-9]|14[5-9]|15[0-35-9]|16[6]|17[0-8]|18[0-9]|19[0-9]|147|166|17[0-1]|162)\d{8}$'
在 config/ConfigDefault.go
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"` // 日志存放路径
Name string `yaml:"name"` // 日志名称
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"` // 电话号码正则
} `yaml:"regular"`
}
// PhoneReg /** 定义电话号码正则
var PhoneReg = Default().Regular.Phone
// EmailReg /** 定义邮箱正则
var EmailReg = Default().Regular.Email
在 utils/index.go
下
// VerPhoneReg /** 验证电话号码格式
func VerPhoneReg(phone string) bool {
phoneReg := regexp.MustCompile(config.PhoneReg)
return !phoneReg.MatchString(phone)
}
在 service
目录下创建 UserService.go
// PhoneOccupyService 电话号码查重
func PhoneOccupyService(phone string) (error, bool) {
// 创建数据库数组对象
var user []entity.SysUser
// 验证用户名参数
// 验证是否为空
if utils.StrIsEmpty(phone) {
return errors.New("参数为空"), false
}
// 判断长度是否超出
if len(phone) != 11 {
return errors.New("电话号码格式错误"), false
}
// 判断用户名中是否含有非法字符串
// 通过调用 gorm api 进行查找数据库
pool.Where("phone=?", phone).Find(&user)
// 判断查询的数组长度是否大于 1 if len(user) > 1 {
// 将错误写入日志
logrus.Error("数据库内存有多个相同的电话号码")
// 将信息返回
return errors.New("电话号码已存在"), false
}
// 返回用户是否存在
return nil, len(user) == 1
}
在 requset/UserRequest.go
下
// PhoneOccupyParams 电话号码查重参数
type PhoneOccupyParams struct {
Phone string `json:"phone" binding:"required,len=11"`
}
在 controller/UserController.go
下
// PhoneOccupy 电话号码查重
func PhoneOccupy(c *gin.Context) {
// 获取参数接口实例
var param request.PhoneOccupyParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 调用接口查找用户名是否已存在
err, isUserName := service.PhoneOccupyService(param.Phone)
// 如果发生错误
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 判断用户名是否存在
if isUserName {
utils.FailResult(c, "电话号码已存在!")
return
}
// 如果用户名不存在
utils.SuccessResult(c, "可以使用", nil)
}
在 router/UserRouter.go
下
// 电话号码查重
router.GET("/phoneOccupy", controller.PhoneOccupy)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.9 编写邮箱查重接口
接口地址:
/api/user/emailOccupy
在 Application.yaml
下
# 正则
regular:
# 电话号码正则
phone: '^(13[0-9]|14[5-9]|15[0-35-9]|16[6]|17[0-8]|18[0-9]|19[0-9]|147|166|17[0-1]|162)\d{8}$'
# 邮箱正则
email: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
在 config/ConfigDefault.go
下
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"` // 日志存放路径
Name string `yaml:"name"` // 日志名称
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"` // 电话号码正则
Email string `yaml:"email"` // 邮箱正则
} `yaml:"regular"`
}
// EmailReg /** 定义邮箱正则
var EmailReg = Default().Regular.Email
在 utils/index.go
下
// VerEmailReg /** 验证电话号码格式
func VerEmailReg(email string) bool {
emailReg := regexp.MustCompile(config.EmailReg)
return !emailReg.MatchString(email)
}
在 service
目录下创建 UserService.go
// EmailOccupyService 邮箱查重
func EmailOccupyService(email string) (error, bool) {
// 创建数据库数组对象
var user []entity.SysUser
// 验证用户名参数
// 验证是否为空
if utils.StrIsEmpty(email) {
return errors.New("参数为空"), false
}
// 判断邮箱格式是否正确
if utils.VerEmailReg(email) {
return errors.New("邮箱格式错误"), false
}
// 判断用户名中是否含有非法字符串
// 通过调用 gorm api 进行查找数据库
pool.Where("email=?", email).Find(&user)
// 判断查询的数组长度是否大于 1 if len(user) > 1 {
// 将错误写入日志
logrus.Error("数据库内存有多个相同的邮箱")
// 将信息返回
return errors.New("邮箱已存在"), false
}
// 返回用户是否存在
return nil, len(user) == 1
}
在 requset/UserRequest.go
下
// EmailOccupyParams 邮箱查重参数
type EmailOccupyParams struct {
Email string `json:"email" binding:"required"`
}
在 controller/UserController.go
下
// EmailOccupy 邮箱查重
func EmailOccupy(c *gin.Context) {
// 获取参数接口实例
var param request.EmailOccupyParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 调用接口查找用户名是否已存在
err, isUserName := service.EmailOccupyService(param.Email)
// 如果发生错误
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 判断用户名是否存在
if isUserName {
utils.FailResult(c, "邮箱已存在!")
return
}
// 如果用户名不存在
utils.SuccessResult(c, "可以使用", nil)
}
在 router/UserRouter.go
下
// 邮箱查重
router.GET("/emailOccupy", controller.EmailOccupy)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.10 编写邮箱验证码接口
接口地址:
/api/user/sendEmailCode
在 Application.yaml
下添加:
# 邮箱配置
emailConfig:
emailAddress: 'co-blog@qq.com'
emailName: 'CoBlog站长'
password: 'qgiegwujfquqbjfd'
smtpServer: 'smtp.qq.com'
smtpPort: 587
在 config/ConfigDefault.go
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"`
Name string `yaml:"name"`
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"`
Email string `yaml:"email"`
} `yaml:"regular"`
// 邮箱配置
EmailConfig struct {
EmailAddress string `yaml:"email_address"`
EmailName string `yaml:"email_name"`
Password string `yaml:"password"`
SmtpServer string `yaml:"smtp_server"`
SmtpPort int `yaml:"smtp_port"`
} `yaml:"email_config"`
}
// EmailConfig /** 邮箱发送配置
var EmailConfig = Default().EmailConfig
在 utils/index.go
下
// generateVerificationCode /** 生成验证码
func generateVerificationCode() string {
source := rand.NewSource(time.Now().UnixNano())
r := rand.New(source)
code := ""
for i := 0; i < 6; i++ {
code += fmt.Sprintf("%d", r.Intn(10))
}
return code
}
func SendEmail(email string) string {
// 生成验证码
code := generateVerificationCode()
// 创建消息
m := gomail.NewMessage()
// 设置发件地址和发件人
m.SetAddressHeader("From", config.EmailConfig.EmailAddress, config.EmailConfig.EmailName)
// 发送地址
m.SetHeader("To", email)
// 设置标题
m.SetHeader("Subject", "验证码")
// 设置内容
m.SetBody("text/html", `
<p>您好!</p>
<p>您的验证码是:<strong style="color:orangered;">`+code+`</strong></p>
<p>此验证码在 5 分钟内有效</p>
<p>如果不是您本人操作,请无视此邮件</p>
`)
// 使用 smtp发送邮件
s := gomail.NewDialer(config.EmailConfig.SmtpServer, config.EmailConfig.SmtpPort, config.EmailConfig.EmailAddress, config.EmailConfig.Password)
if err := s.DialAndSend(m); err != nil {
panic("发送失败!")
}
return code
}
在 service
目录下创建 UserService.go
// SendMsgCodeService 发送邮箱验证码
func SendMsgCodeService(email string) (error, bool) {
// 验证用户名参数
// 验证是否为空
if utils.StrIsEmpty(email) {
return errors.New("参数为空"), false
}
// 判断邮箱格式是否正确
if utils.VerEmailReg(email) {
return errors.New("邮箱格式错误"), false
}
// 判断用户名中是否含有非法字符串
// 发送验证码
code := utils.SendEmail(email)
// 判断生成的验证码格式是否正确
if len(code) != 6 {
return errors.New("验证码发送失败"), false
}
// 将 code 存入 redis 5 分钟有效期
if _, err := RedisClient().Set(ctx, email, code, 5*time.Minute).Result(); err != nil {
return errors.New("发送失败"), false
}
// 返回
return nil, true
}
在 requset/UserRequest.go
下
// SendEmailParams 邮箱查重参数
type SendEmailParams struct {
Email string `json:"email" binding:"required"`
}
在 controller/UserController.go
下
// SendEmailCode 获取邮箱验证码
func SendEmailCode(c *gin.Context) {
// 获取参数接口实例
var param request.SendEmailParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 调用接口发送验证码
err, sendStatus := service.SendMsgCodeService(param.Email)
// 如果发生错误
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 发送失败
if !sendStatus {
utils.FailResult(c, "发送失败!")
return
}
// 发送成功
utils.SuccessResult(c, "发送成功!", nil)
}
在 router/UserRouter.go
下
// 获取邮箱验证码
router.POST("/sendEmailCode", controller.SendEmailCode)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
邮箱截图
4.11 编写注册接口
接口地址:
/api/user/register
在 Application.yaml
下添加:
# 加密
encryption:
# 私钥
private_key:
password: '1b6b260b-67d1-4b5a-917e-10ec233329ca'
# 盐值
salt:
password: 10
在 config/ConfigDefault.go
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"`
Name string `yaml:"name"`
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"`
Email string `yaml:"email"`
} `yaml:"regular"`
// 邮箱配置
EmailConfig struct {
EmailAddress string `yaml:"email_address"`
EmailName string `yaml:"email_name"`
Password string `yaml:"password"`
SmtpServer string `yaml:"smtp_server"`
SmtpPort int `yaml:"smtp_port"`
} `yaml:"email_config"`
// redis 配置
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
Password string `yaml:"password"`
DB int `yaml:"db"`
} `yaml:"redis_config"`
// 加密配置
Encryption struct {
// 私钥
PrivateKey struct {
Password string `yaml:"password"`
} `yaml:"private_key"`
// 盐值
Salt struct {
Password int `yaml:"password"`
} `yaml:"salt"`
} `yaml:"encryption"`
}
// Encryption 加密配置
var Encryption = Default().Encryption
在 utils/index.go
下
// CreateUUID 生成 uuid
func CreateUUID() string {
uCode := uuid.NewV4()
return uCode.String()
}
// EncryptionPassword 密码加密
func EncryptionPassword(pwd string) string {
password, err := bcrypt.GenerateFromPassword([]byte(pwd+config.Encryption.PrivateKey.Password), config.Encryption.Salt.Password)
if err != nil {
return ""
}
return string(password)
}
在 service
目录下创建 UserService.go
// RegisterService 注册
func RegisterService(param *request.RegisterParams) (error, bool) {
// 判断数据是否为空
if utils.StrIsEmpty(param.UserName) || utils.StrIsEmpty(param.Phone) || utils.StrIsEmpty(param.Email) || utils.StrIsEmpty(param.Password) {
return errors.New("参数为空"), false
}
// 验证电话号码格式
if utils.VerPhoneReg(param.Phone) {
return errors.New("电话号码格式错误"), false
}
// 验证邮箱格式
if utils.VerEmailReg(param.Email) {
return errors.New("邮箱格式错误"), false
}
// 验证电话号码是否已存在
if err, _ := PhoneOccupyService(param.Phone); err != nil {
return err, false
}
// 验证邮箱是否已存在
if err, _ := EmailOccupyService(param.Email); err != nil {
return err, false
}
// 判断密码格式
if len(param.Password) != 32 || len(param.VerPassword) != 32 {
return errors.New("密码为空"), false
}
// 判断两次密码是否一致
if param.Password != param.VerPassword {
return errors.New("两次密码不一致"), false
}
// 判断验证码是否正确
result, err := RedisClient().Get(ctx, param.Email).Result()
if err != nil {
return errors.New("验证码失效"), false
}
if result != param.EmailCode {
return errors.New("验证码错误!"), false
}
// 将密码进行二次加密
encryptionPwd := utils.EncryptionPassword(param.Password)
// 将用户数据存入数据库
if err := pool.Create(&entity.SysUser{
UserName: param.UserName,
Phone: param.Phone,
Password: encryptionPwd,
Email: param.Email,
UUID: utils.CreateUUID(),
}).Error; err != nil {
return errors.New("注册失败!"), false
}
// 判断
return nil, true
}
在 requset/UserRequest.go
下
// RegisterParams 注册参数
type RegisterParams struct {
UserName string `json:"user_name" binding:"required,min=3,max=15"`
Phone string `json:"phone" binding:"required,len=11"`
Email string `json:"email" binding:"required"`
EmailCode string `json:"email_code" binding:"required,len=6"`
Password string `json:"password" binding:"required,len=32"`
VerPassword string `json:"ver_password" binding:"required,len=32"`
}
在 controller/UserController.go
下
// Register 注册
func Register(c *gin.Context) {
// 获取参数接口实例
var param request.RegisterParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 验证完成后
err, status := service.RegisterService(¶m)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 注册失败
if !status {
utils.FailResult(c, "注册失败")
return
}
// 注册成功
utils.SuccessResult(c, "注册成功!", nil)
}
在 router/UserRouter.go
下
// 注册
router.POST("/register", controller.Register)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.12 编写电话号码登录接口
接口地址:
/api/user/phoneLogin
在 Application.yaml
下添加:
# token 配置
token:
private_key: 'af28c9fe-9426-41dd-8ea7-cba762ec0f1e'
在 config/ConfigDefault.go
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"`
Name string `yaml:"name"`
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"`
Email string `yaml:"email"`
} `yaml:"regular"`
// 邮箱配置
EmailConfig struct {
EmailAddress string `yaml:"email_address"`
EmailName string `yaml:"email_name"`
Password string `yaml:"password"`
SmtpServer string `yaml:"smtp_server"`
SmtpPort int `yaml:"smtp_port"`
} `yaml:"email_config"`
// redis 配置
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
Password string `yaml:"password"`
DB int `yaml:"db"`
} `yaml:"redis_config"`
// 加密配置
Encryption struct {
// 私钥
PrivateKey struct {
Password string `yaml:"password"`
} `yaml:"private_key"`
// 盐值
Salt struct {
Password int `yaml:"password"`
} `yaml:"salt"`
} `yaml:"encryption"`
// token 配置
Token struct {
PrivateKey string `yaml:"private_key"`
} `yaml:"token"`
}
// TokenPrivateKey Token 私钥配置
var TokenPrivateKey = Default().Token.PrivateKey
// TokenEffectAge Token 生命周期配置
const TokenEffectAge = 1 * 24 * time.Hour
在 utils/index.go
下
// ComparePassword 密码验证
func ComparePassword(hashPwd string, pwd string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashPwd), []byte(pwd+config.Encryption.PrivateKey.Password))
if err != nil {
return false
}
return true
}
// GenerateToken 生成 Token
func GenerateToken(claims *request.TokenParams) string {
//设置token有效期,也可不设置有效期,采用redis的方式
// 1)将token存储在redis中,设置过期时间,token如没过期,则自动刷新redis过期时间,
// 2)通过这种方式,可以很方便的为token续期,而且也可以实现长时间不登录的话,强制登录
claims.ExpiresAt = time.Now().Add(config.TokenEffectAge).Unix()
//生成token
sign, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(config.TokenPrivateKey))
if err != nil {
//这里因为项目接入了统一异常处理,所以使用panic并不会使程序终止,如不接入,可使用原始方式处理错误
log.Panicln("Token生成异常")
return ""
}
return sign
}
在 service
目录下创建 UserService.go
// PhoneLoginService 电话号码登录
func PhoneLoginService(param *request.PhoneLoginParams) (error, bool, string) {
// 创建数据库数组对象
var user []entity.SysUser
// 验证参数为空
if utils.StrIsEmpty(param.Phone) || utils.StrIsEmpty(param.Password) {
return errors.New("参数为空"), false, ""
}
// 验证电话号码格式
if utils.VerPhoneReg(param.Phone) {
return errors.New("电话号码格式错误"), false, ""
}
// 验证密码格式
if len(param.Password) != 32 {
return errors.New("密码格式错误"), false, ""
}
// 判断此用户是否存在
// 通过电话号码查询用户
err := pool.Where("phone=?", param.Phone).Find(&user).Error
if err != nil {
return errors.New("登录失败"), false, ""
}
// 通过判断用户列表长度,判断是否有此用户
if len(user) < 1 {
return errors.New("此用户暂未注册!"), false, ""
}
// 验证密码正确性
if !utils.ComparePassword(user[0].Password, param.Password) {
return errors.New("账号/密码错误!"), false, ""
}
// 生成 Token token := utils.GenerateToken(&request.TokenParams{
UserInfo: user[0],
StandardClaims: jwt.StandardClaims{},
})
if utils.StrIsEmpty(token) {
return errors.New("登录失败"), false, ""
}
// 将 Token 存入 Redis if _, err := RedisClient().Set(ctx, token, param.Phone, config.TokenEffectAge).Result(); err != nil {
return errors.New("登录失败"), false, ""
}
return nil, true, token
}
在 requset/UserRequest.go
下
// PhoneLoginParams 电话号码登录参数
type PhoneLoginParams struct {
Phone string `json:"phone" binding:"required,len=11"`
Password string `json:"password" binding:"required,len=32"`
}
// TokenParams 定义 Token 类型
type TokenParams struct {
UserInfo entity.SysUser // 用户信息
jwt.StandardClaims // token 配置
}
在 controller/UserController.go
下
// PhoneLogin 电话号码登录
func PhoneLogin(c *gin.Context) {
// 拿到电话号码登录参数实体类
var param request.PhoneLoginParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 验证完成后
err, status, token := service.PhoneLoginService(¶m)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 注册失败
if !status {
utils.FailResult(c, "注册失败")
return
}
// 注册成功
utils.SuccessResult(c, "登录成功!", map[string]interface{}{"token": token})
}
在 router/UserRouter.go
下
// 电话号码登录
router.POST("/phoneLogin", controller.PhoneLogin)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.13 编写邮箱登录接口
接口地址:
/api/user/emailLogin
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
// EmailLoginService 邮箱登录
func EmailLoginService(param *request.EmailLoginParams) (error, bool, string) {
// 创建数据库数组对象
var user []entity.SysUser
// 验证参数为空
if utils.StrIsEmpty(param.Email) || utils.StrIsEmpty(param.Password) {
return errors.New("参数为空"), false, ""
}
// 验证电话号码格式
if utils.VerEmailReg(param.Email) {
return errors.New("电话号码格式错误"), false, ""
}
// 验证密码格式
if len(param.Password) != 32 {
return errors.New("密码格式错误"), false, ""
}
// 判断此用户是否存在
// 通过电话号码查询用户
err := pool.Where("email=?", param.Email).Find(&user).Error
if err != nil {
return errors.New("登录失败"), false, ""
}
// 通过判断用户列表长度,判断是否有此用户
if len(user) < 1 {
return errors.New("此用户暂未注册!"), false, ""
}
// 验证密码正确性
if !utils.ComparePassword(user[0].Password, param.Password) {
return errors.New("账号/密码错误!"), false, ""
}
// 生成 Token token := utils.GenerateToken(&request.TokenParams{
UserInfo: user[0],
StandardClaims: jwt.StandardClaims{},
})
if utils.StrIsEmpty(token) {
return errors.New("登录失败"), false, ""
}
// 将 Token 存入 Redis if _, err := RedisClient().Set(ctx, token, param.Email, config.TokenEffectAge).Result(); err != nil {
return errors.New("登录失败"), false, ""
}
return nil, true, token
}
在 requset/UserRequest.go
下
// EmailLoginParams 邮箱登录参数
type EmailLoginParams struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required,len=32"`
}
在 controller/UserController.go
下
// EmailLogin 邮箱登录
func EmailLogin(c *gin.Context) {
// 拿到电话号码登录参数实体类
var param request.EmailLoginParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 验证完成后
err, status, token := service.EmailLoginService(¶m)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 注册失败
if !status {
utils.FailResult(c, "注册失败")
return
}
// 注册成功
utils.SuccessResult(c, "登录成功!", map[string]interface{}{"token": token})
}
在 router/UserRouter.go
下
// 邮箱登录
router.POST("/emailLogin", controller.EmailLogin)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.14 编写忘记密码接口
接口地址:
/api/user/forgotPassword
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
// ForgotPassword 忘记密码
func ForgotPassword(param *request.ForgotPasswordParams) (error, bool) {
// 创建数据库数组对象
var user []entity.SysUser
// 验证参数为空
if utils.StrIsEmpty(param.Email) || utils.StrIsEmpty(param.EmailCode) || utils.StrIsEmpty(param.NewPassword) || utils.StrIsEmpty(param.VerPassword) {
return errors.New("参数为空"), false
}
// 验证邮箱格式
if utils.VerEmailReg(param.Email) {
return errors.New("邮箱格式错误"), false
}
// 验证密码格式
if len(param.NewPassword) != 32 || len(param.VerPassword) != 32 {
return errors.New("密码格式错误"), false
}
// 验证新密码和验证密码是否一致
if param.NewPassword != param.VerPassword {
return errors.New("两次密码不一致"), false
}
// 验证验证码格式
if len(param.EmailCode) != 6 {
return errors.New("验证码格式错误"), false
}
// 验证验证码是否正确
if result, err := RedisClient().Get(ctx, param.Email).Result(); err != nil || result != param.EmailCode {
return errors.New("验证码错误"), false
}
// 删除 redis 中的验证码
if _, err := RedisClient().Del(ctx, param.Email).Result(); err != nil {
return errors.New("修改失败"), false
}
// 判断此用户是否存在
// 通过邮箱查询用户
err := pool.Where("email=?", param.Email).Find(&user).Error
if err != nil {
return errors.New("修改失败"), false
}
// 通过判断用户列表长度,判断是否有此用户
if len(user) < 1 {
return errors.New("账号不存在"), false
}
// 对密码进行加密
encryptionPassword := utils.EncryptionPassword(param.NewPassword)
// 修改密码 SQL err = pool.Model(&entity.SysUser{}).Where("uid", user[0].UID).Update("password", encryptionPassword).Error
if err != nil {
return errors.New("修改失败"), false
}
// 修改成功
return nil, true
}
在 requset/UserRequest.go
下
// ForgotPasswordParams 忘记密码参数
type ForgotPasswordParams struct {
Email string `json:"email" binding:"required"`
EmailCode string `json:"email_code" binding:"required,len=6"`
NewPassword string `json:"new_password" binding:"required,len=32"`
VerPassword string `json:"ver_password" binding:"required,len=32"`
}
在 controller/UserController.go
下
func ForgotPassword(c *gin.Context) {
// 获取参数接口实例
var param request.ForgotPasswordParams
// 绑定参数
err := c.ShouldBindJSON(¶m)
// 参数绑定失败
if err != nil {
utils.FailResult(c, "参数错误!")
return
}
// 验证完成后
err, status := service.ForgotPassword(¶m)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 注册失败
if !status {
utils.FailResult(c, "修改失败")
return
}
// 注册成功
utils.SuccessResult(c, "修改成功!", nil)
}
在 router/UserRouter.go
下
// 忘记密码
router.POST("/forgotPassword", controller.ForgotPassword)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.15 编写查询用户信息接口
接口地址:
/api/user/userInfo
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
在 requset/UserRequest.go
下
在 controller/UserController.go
下
// UserInfo 用户信息
func UserInfo(c *gin.Context) {
// 获取用户信息
user, _ := c.Get("user")
// 判断用户信息是否存在
if user == nil {
utils.FailResult(c, "登陆失效,请重新登录!")
return
}
// 将user转化为 TokenParam类型
tokenParam, ok := user.(*request.TokenParams)
// 判断是否转化正确
if !ok {
utils.FailResult(c, "登陆失效,请重新登录!")
return
}
// 返回用户信息
utils.SuccessResult(c, "获取成功!", request.UserResponse{
Name: tokenParam.UserInfo.UserName,
Phone: tokenParam.UserInfo.Phone,
HeadSculpture: tokenParam.UserInfo.HeadSculpture,
Email: tokenParam.UserInfo.Email,
Limit: tokenParam.UserInfo.Limit,
Integral: tokenParam.UserInfo.Integral,
Member: tokenParam.UserInfo.Member,
CreateTime: tokenParam.UserInfo.CreateTime.String(),
UpdateTime: tokenParam.UserInfo.UpdateTime.String(),
UUID: tokenParam.UserInfo.UUID,
})
}
在 router/UserRouter.go
下
// 获取用户信息
router.GET("/userInfo", controller.UserInfo)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
![[gin_co_blog_assets/Pasted image 20231012144124.png]]
4.16 编写修改用户信息接口
接口地址:
/api/user/userNameOccupy
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
// GetCacheUser 获取缓存里的token信息
func GetCacheUser(c *gin.Context) (error, *request.TokenParams) {
// 获取用户信息
user, _ := c.Get("user")
// 判断用户信息是否存在
if user == nil {
return errors.New("登录状态错误,请重新登录"), nil
}
// 将user转化为 TokenParam类型
tokenParam, ok := user.(*request.TokenParams)
// 判断是否转化正确
if !ok {
return errors.New("登录状态错误,请重新登录"), nil
}
return nil, tokenParam
}
在 service
目录下创建 UserService.go
// ModifyUserInfoService ModifyUserInfo 修改用户信息
func ModifyUserInfoService(param *request.ModifyUserInfoParams, userInfo *entity.SysUser) (error, bool) {
// 判断参数是否为空
if utils.StrIsEmpty(param.UserName) || utils.StrIsEmpty(param.Email) || utils.StrIsEmpty(param.Phone) || utils.StrIsEmpty(param.HeadSculpture) {
return errors.New("参数为空"), false
}
// 验证电话号码格式
if utils.VerPhoneReg(param.Phone) {
return errors.New("电话号码格式错误"), false
}
// 验证验证码格式
if len(param.EmailCode) != 6 {
return errors.New("验证码格式错误"), false
}
// 验证验证码是否正确
if result, err := RedisClient().Get(ctx, userInfo.Email).Result(); err != nil || result != param.EmailCode {
return errors.New("验证码错误"), false
}
// 修改数据
userInfo.UserName = param.UserName
userInfo.Phone = param.Phone
userInfo.Email = param.Email
userInfo.HeadSculpture = param.HeadSculpture
// 修改数据库
err := pool.Model(&entity.SysUser{}).Where("uid", userInfo.UID).Updates(userInfo).Error
// 修改失败
if err != nil {
return errors.New("修改失败"), false
}
// 修改成功
return nil, true
}
在 requset/UserRequest.go
下
// ModifyUserInfoParams 修改用户信息参数
type ModifyUserInfoParams struct {
UserName string `json:"user_name" binding:"required,min=3,max=15"`
Phone string `json:"phone" binding:"required,len=11"`
Email string `json:"email" binding:"required"`
HeadSculpture string `json:"head_sculpture" binding:"required"`
EmailCode string `json:"email_code" binding:"required,len=6"`
}
在 controller/UserController.go
下
// ModifyUserInfo 修改用户信息
func ModifyUserInfo(c *gin.Context) {
var params request.ModifyUserInfoParams
err := c.ShouldBindJSON(¶ms)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
err, tokenInfo := utils.GetCacheUser(c)
if err != nil {
utils.AuthorizationResult(c, err.Error())
return
}
err, status := service.ModifyUserInfoService(¶ms, &tokenInfo.UserInfo)
if err != nil {
utils.FailResult(c, err.Error())
return
}
if !status {
utils.FailResult(c, "修改失败")
return
}
utils.SuccessResult(c, "修改成功", nil)
}
在 router/UserRouter.go
下
// 修改用户信息
router.POST("/modifyUserInfo", controller.ModifyUserInfo)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.17 编写修改用户密码接口
接口地址:
/api/user/userNameOccupy
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
// ModifyPasswordService 修改密码
func ModifyPasswordService(params *request.ModifyPasswordParams, userInfo *entity.SysUser) (error, bool) {
// 判断参数为空
if utils.StrIsEmpty(params.OldPassword) || utils.StrIsEmpty(params.NewPassword) {
return errors.New("参数为空"), false
}
// 判断新老密码是否一样
if params.OldPassword == params.NewPassword {
return errors.New("两次输入的密码一样"), false
}
// 验证密码正确性
if !utils.ComparePassword(userInfo.Password, params.OldPassword) {
return errors.New("旧密码错误!"), false
}
if params.NewPassword != params.VerifiedPassword {
return errors.New("新密码与验证密码不一致!"), false
}
// 将密码进行二次加密
encryptionPwd := utils.EncryptionPassword(params.NewPassword)
// 修改数据库
err := pool.Model(&entity.SysUser{}).Where("uid", userInfo.UID).Update("password", encryptionPwd).Error
if err != nil {
return errors.New("修改失败"), false
}
// 修改成功
return nil, true
}
在 requset/UserRequest.go
下
// ModifyPasswordParams 修改密码参数
type ModifyPasswordParams struct {
OldPassword string `json:"old_password" binding:"required,len=32"`
NewPassword string `json:"new_password" binding:"required,len=32"`
VerifiedPassword string `json:"verified_password" binding:"required,len=32"`
}
在 controller/UserController.go
下
// ModifyPassword 修改密码
func ModifyPassword(c *gin.Context) {
// 获取参数类型对象
var params request.ModifyPasswordParams
// 绑定参数
err := c.ShouldBindJSON(¶ms)
// 绑定失败
if err != nil {
utils.FailResult(c, "参数错误")
return
}
// 获取Token信息
err, tokenInfo := utils.GetCacheUser(c)
if err != nil {
utils.AuthorizationResult(c, "登录失效")
return
}
err, status := service.ModifyPasswordService(¶ms, &tokenInfo.UserInfo)
if err != nil {
utils.FailResult(c, err.Error())
return
}
if !status {
utils.FailResult(c, "修改失败")
return
}
utils.SuccessResult(c, "修改成功", nil)
}
在 router/UserRouter.go
下
// 修改密码
router.POST("/modifyPassword", controller.ModifyPassword)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.18 编写退出登录接口
接口地址:
/api/user/logout
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
// LogoutService 退出登录
func LogoutService(param *request.TokenParams, token string) (error, bool) {
// 判断参数是否为空
if param == nil {
return errors.New("退出失败"), false
}
// 删除 Redis
_, err := RedisClient().Del(ctx, token).Result()
if err != nil {
return err, false
}
return nil, true
}
在 controller/UserController.go
下
// Logout 退出登录
func Logout(c *gin.Context) {
// 获取用户信息
user, _ := c.Get("user")
// 获取Token
header := c.GetHeader("Authorization")
// 判断用户信息是否存在
if user == nil {
utils.FailResult(c, "登陆失效,请重新登录!")
return
}
// 将user转化为 TokenParam类型
tokenParam, ok := user.(*request.TokenParams)
// 判断是否转化正确
if !ok {
utils.FailResult(c, "登陆失效,请重新登录!")
return
}
err, logout := service.LogoutService(tokenParam, header)
if err != nil {
utils.FailResult(c, "退出失败")
return
}
if !logout {
utils.FailResult(c, "退出失败")
return
}
// 退出成功
utils.SuccessResult(c, "退出成功", nil)
}
在 router/UserRouter.go
下
// 退出登录
router.POST("/logout", controller.Logout)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.19 编写文件上传接口
接口地址:
/api/upload/userAvatar
和/api/upload/articleIcon
在 Application.yaml
下添加:
# 上传文件配置
upload:
# 基础返回地址
host: 'http://39.101.72.168:81/image/'
# 存储位置
img_load:
user: 'uploads/' # 用户头像存储目录
article: 'uploads/article/' # 文章图片存储目录
# 文件大小
max_size:
# 图片
img: 10
# 可上传的类型
img_type:
- '.jpg'
- '.jpeg'
- '.png'
- '.bmp'
- '.gif'
在 config/ConfigDefault.go
// Config 配置 yaml 结构
type Config struct {
// 端口号
Port string `yaml:"port"`
// 数据库配置
DataBase struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"user_name"`
Password string `yaml:"password"`
Schema string `yaml:"schema"`
} `yaml:"data_base"`
// 日志配置
LogConfig struct {
Path string `yaml:"path"`
Name string `yaml:"name"`
} `yaml:"log_config"`
// 正则配置
Regular struct {
Phone string `yaml:"phone"`
Email string `yaml:"email"`
} `yaml:"regular"`
// 邮箱配置
EmailConfig struct {
EmailAddress string `yaml:"email_address"`
EmailName string `yaml:"email_name"`
Password string `yaml:"password"`
SmtpServer string `yaml:"smtp_server"`
SmtpPort int `yaml:"smtp_port"`
} `yaml:"email_config"`
// redis 配置
RedisConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
Password string `yaml:"password"`
DB int `yaml:"db"`
} `yaml:"redis_config"`
// 加密配置
Encryption struct {
// 私钥
PrivateKey struct {
Password string `yaml:"password"`
} `yaml:"private_key"`
// 盐值
Salt struct {
Password int `yaml:"password"`
} `yaml:"salt"`
} `yaml:"encryption"`
// token 配置
Token struct {
PrivateKey string `yaml:"private_key"`
} `yaml:"token"`
// 绕过中间件验证的地址
NotVerifyUrl []string `yaml:"not_verify_url"`
// 跨域中间件验证地址
Cors struct {
Ip []string `yaml:"ip"`
Methods []string `yaml:"methods"`
} `yaml:"cors"`
// 上传配置
Upload struct {
Host string `yaml:"host"`
ImgLoad struct {
User string `yaml:"user"`
Article string `yaml:"article"`
} `yaml:"img_load"`
MaxSize struct {
Img int64 `yaml:"img"`
} `yaml:"max_size"`
ImgType []string `yaml:"img_type"`
} `yaml:"upload"`
}
在 utils/index.go
下
// GenerateFileName 生成文件名
func GenerateFileName(originalName string) string {
// 提取文件后缀
extension := filepath.Ext(originalName)
// 生成 UUID 字符串
uuidFilename := uuid.NewV4().String()
// 返回拼接字符串
return uuidFilename + extension
}
// IsAllowedImageType 定义允许上传的文件类型
func IsAllowedImageType(extension string) bool {
// 获取允许的类型
allowedImageTypes := config.Upload.ImgType
// 判断是否符合该类型
return contains(allowedImageTypes, extension)
}
// 判断是否允许上传
func contains(slice []string, val string) bool {
for _, item := range slice {
if item == val {
return true
}
}
return false
}
在 controller
下创建UploadController.go
package controller
import (
"GinCoBlog/config"
"GinCoBlog/utils" "fmt" "github.com/gin-gonic/gin" "path/filepath")
func UserAvatar(c *gin.Context) {
file, err := c.FormFile("image")
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 生成 UUID 作为文件名的一部分
newName := utils.GenerateFileName(file.Filename)
// 检查文件类型是否被允许上传
ext := filepath.Ext(file.Filename)
if !utils.IsAllowedImageType(ext) {
utils.FailResult(c, "只允许上传图片文件")
return
}
// 检查文件大小
if file.Size > (config.Upload.MaxSize.Img << 20) { // 限制文件大小为 5MB utils.FailResult(c, "文件大小超过限制")
return
}
// 保存上传的文件到本地
dst := fmt.Sprintf(config.Upload.ImgLoad.User+"%s", newName)
if err := c.SaveUploadedFile(file, dst); err != nil {
utils.FailResult(c, err.Error())
return
}
utils.SuccessResult(c, "上传成功", map[string]interface{}{"fileName": config.Upload.Host + newName})
}
func ArticleIcon(c *gin.Context) {
file, err := c.FormFile("image")
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 生成 UUID 作为文件名的一部分
newName := utils.GenerateFileName(file.Filename)
// 检查文件类型是否被允许上传
ext := filepath.Ext(file.Filename)
if !utils.IsAllowedImageType(ext) {
utils.FailResult(c, "只允许上传图片文件")
return
}
// 检查文件大小
if file.Size > (config.Upload.MaxSize.Img << 20) { // 限制文件大小为 5MB utils.FailResult(c, "文件大小超过限制")
return
}
// 保存上传的文件到本地
dst := fmt.Sprintf(config.Upload.ImgLoad.Article+"%s", newName)
if err := c.SaveUploadedFile(file, dst); err != nil {
utils.FailResult(c, err.Error())
return
}
utils.SuccessResult(c, "上传成功", map[string]interface{}{"fileName": config.Upload.Host + newName})
}
在 router
下创建 UploadRouter.go
package routes
import (
"GinCoBlog/controller"
"github.com/gin-gonic/gin")
func UploadRouter(router *gin.RouterGroup) {
// 用户头像
router.PUT("/userAvatar", controller.UserAvatar)
// 文章图片
router.PUT("/articleIcon", controller.ArticleIcon)
}
在 router/index.go
中添加
package routes
import "github.com/gin-gonic/gin"
// SetupRouterGroup 项目主路由(一级)
func SetupRouterGroup(router *gin.RouterGroup) {
// 调取用户路由
UserRouter(router.Group("/user"))
// 调取上传路由
UploadRouter(router.Group("/upload"))
}
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.20 编写获取文章类型接口
接口地址:
/api/article/articleType
在 Application.yaml
下添加:
# 不验证的接口地址
not_verify_url:
- '/api/article/articleType'
在 service
目录下创建 ArticleService.go
// ArticleTypeService 文章类型
func ArticleTypeService(list *[]entity.ArticleType) error {
// 获取数据库内的文章类型数据
if err := pool.Model(&entity.ArticleType{}).Where("type_visible", 1).Find(&list).Error; err != nil {
return errors.New("获取失败")
}
return nil
}
在 controller
下创建ArticleController.go
// ArticleType 文章类型
func ArticleType(c *gin.Context) {
// 定义类型
var typeList []entity.ArticleType
// 获取数据
if err := service.ArticleTypeService(&typeList); err != nil {
utils.FailResult(c, err.Error())
return
}
utils.SuccessResult(c, "获取成功", map[string][]entity.ArticleType{"articleType": typeList})
}
在 router
下创建ArticleRouter.go
package routes
import (
"GinCoBlog/controller"
"github.com/gin-gonic/gin")
// ArticleRouter 定义文章路由(二级)
func ArticleRouter(router *gin.RouterGroup) {
// 获取文章类型列表
router.GET("/articleType", controller.ArticleType)
}
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.21 编写获取文章列表接口
接口地址:
/api/article/articleList
在 Application.yaml
下添加:
# 不验证的接口地址
not_verify_url:
- '/api/user/phoneOccupy'
- '/api/user/emailOccupy'
- '/api/user/register'
- '/api/user/phoneLogin'
- '/api/user/emailLogin'
- '/api/user/sendEmailCode'
- '/api/user/forgotPassword'
- '/api/article/articleType'
- '/api/article/articleList'
在 service/ArticleService.go
// ArticleListByTypeId 根据文章类型查询文章列表
func ArticleListByTypeId(list *[]entity.Article, typeId int32) error {
// 判断参数格式
if typeId == 0 {
return errors.New("参数为空")
}
// 获取数据库内的文章类型数据
if err := pool.Model(&entity.Article{}).Where("type_id", typeId).Find(&list).Error; err != nil {
return errors.New("获取失败")
}
return nil
}
在 requset/ArticleRequest.go
下
// ArticleListByTypeParam 根据类型 ID 查询的参数
type ArticleListByTypeParam struct {
TypeId int32 `form:"type_id" binding:"required"`
}
在 controller/ArticleController.go
下
// ArticleListByType 文章列表
func ArticleListByType(c *gin.Context) {
var params request.ArticleListByTypeParam
var articleList []entity.Article
// 绑定参数
err := c.ShouldBindQuery(¶ms)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
// 查询数据库
if err := service.ArticleListByTypeId(&articleList, params.TypeId); err != nil {
utils.FailResult(c, "获取失败")
return
}
utils.SuccessResult(c, "获取成功", map[string][]entity.Article{"rows": articleList})
}
在 router/ArticleRouter.go
下
// 通过类型 ID 获取文章列表
router.GET("/articleList", controller.ArticleListByType)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.22 编写获取文章详情接口
接口地址:
/api/article/articleInfo
在 Application.yaml
下添加:
# 不验证的接口地址
not_verify_url:
- '/api/user/phoneOccupy'
- '/api/user/emailOccupy'
- '/api/user/register'
- '/api/user/phoneLogin'
- '/api/user/emailLogin'
- '/api/user/sendEmailCode'
- '/api/user/forgotPassword'
- '/api/article/articleType'
- '/api/article/articleList'
- '/api/article/articleInfo'
在 service/ArticleService.go
// ArticleInfoByIdService 根据 ID 获取文章详情
func ArticleInfoByIdService(info *entity.Article, articleId int32) error {
if articleId == 0 {
return errors.New("参数为空")
}
// 获取数据库信息
if err := pool.Model(&entity.Article{}).Where("id=? AND article_visible=?", articleId, 1).First(&info).Error; err != nil {
return errors.New("获取失败")
}
return nil
}
在 requset/ArticleRequest.go
下
// ArticleInfoByIdParam 根据 ID 获取文章详情
type ArticleInfoByIdParam struct {
ArticleId int32 `form:"article_id" binding:"required"`
}
在 controller/ArticleController.go
下
// ArticleInfoById 文章详情
func ArticleInfoById(c *gin.Context) {
var params request.ArticleInfoByIdParam
var info entity.Article
// 绑定参数
err := c.ShouldBindQuery(¶ms)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
// 查询数据库
if err := service.ArticleInfoByIdService(&info, params.ArticleId); err != nil {
utils.FailResult(c, "获取失败")
return
}
utils.SuccessResult(c, "获取成功", map[string]entity.Article{"data": info})
}
在 router/ArticleRouter.go
下
// 通过 ID 获取文章详情
router.GET("/articleInfo", controller.ArticleInfoById)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.23 编写添加文章接口
接口地址:
/api/article/addArticle
在 service/ArticleService.go
// AddArticleService 添加文章
func AddArticleService(param *request.AddArticleParam, userInfo entity.SysUser) (error, bool) {
// 判断参数是否为空
if param.TypeId == 0 || utils.StrIsEmpty(param.Title) || utils.StrIsEmpty(param.Context) || utils.StrIsEmpty(param.Icon) {
return errors.New("参数为空"), false
}
// 判断参数是否合法
// 将数据存入数据库
err := pool.Model(&entity.Article{}).Create(&entity.Article{
TypeId: param.TypeId,
UserId: userInfo.UID,
Title: param.Title,
Context: param.Context,
Icon: param.Icon,
}).Error
if err != nil {
return errors.New("添加失败"), false
}
return nil, true
}
在 requset/ArticleRequest.go
下
// AddArticleParam 添加文章参数
type AddArticleParam struct {
TypeId uint32 `json:"typeId" binding:"required"`
Title string `json:"title" binding:"required"`
Context string `json:"context" binding:"required"`
Icon string `json:"icon" binding:"required"`
}
在 controller/ArticleController.go
下
// AddArticle 添加文章
func AddArticle(c *gin.Context) {
// 获取参数实例
var params request.AddArticleParam
// 绑定参数
err := c.ShouldBindJSON(¶ms)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
err, tokenInfo := utils.GetCacheUser(c)
if err != nil {
utils.AuthorizationResult(c, "登录状态失效")
return
}
// 将数据添加到数据库
err, status := service.AddArticleService(¶ms, tokenInfo.UserInfo)
if err != nil {
utils.FailResult(c, err.Error())
return
}
if !status {
utils.FailResult(c, "添加失败")
}
utils.SuccessResult(c, "添加成功", nil)
}
在 router/ArticleRouter.go
下
// 添加文章
router.POST("/addArticle", controller.AddArticle)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.24 编写更新阅读量接口
接口地址:
/api/article/updateRead
在 Application.yaml
下添加:
not_verify_url:
- '/api/user/phoneOccupy'
- '/api/user/emailOccupy'
- '/api/user/register'
- '/api/user/phoneLogin'
- '/api/user/emailLogin'
- '/api/user/sendEmailCode'
- '/api/user/forgotPassword'
- '/api/article/articleType'
- '/api/article/articleList'
- '/api/article/articleInfo'
- '/api/article/updateRead'
在 config/ConfigDefault.go
在 utils/index.go
下
在 service/ArticleService.go
下
// UpdateReadService 更新阅读量
func UpdateReadService(param *request.UpdateReadParam) (error, bool) {
// 判断参数为空
if param.ArticleId == 0 {
return errors.New("参数为空"), false
}
// 修改数据库
if err := pool.Model(&entity.Article{}).Where("id", param.ArticleId).UpdateColumn("read", gorm.Expr("`read` + ?", 1)).Error; err != nil {
return errors.New("修改失败"), false
}
// 修改成功
return nil, true
}
在 requset/ArticleRequest.go
下
// UpdateReadParam 更新阅读量
type UpdateReadParam struct {
ArticleId int32 `json:"article_id" binding:"required"`
}
在 controller/ArticleController.go
下
// UpdateRead 更新阅读量
func UpdateRead(c *gin.Context) {
var params request.UpdateReadParam
// 绑定参数
err := c.ShouldBindJSON(¶ms)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
// 修改数据库
err, status := service.UpdateReadService(¶ms)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 判断状态
if !status {
utils.FailResult(c, "设置失败")
return
}
// 设置成功
utils.SuccessResult(c, "更新成功", nil)
}
在 router/ArticleRouter.go
下
// 更新阅读量
router.POST("/updateRead", controller.UpdateRead)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.25 编写查询评论列表接口
接口地址:
/api/article/articleComment
在 Application.yaml
下添加:
not_verify_url:
- '/api/user/phoneOccupy'
- '/api/user/emailOccupy'
- '/api/user/register'
- '/api/user/phoneLogin'
- '/api/user/emailLogin'
- '/api/user/sendEmailCode'
- '/api/user/forgotPassword'
- '/api/article/articleType'
- '/api/article/articleList'
- '/api/article/articleInfo'
- '/api/article/updateRead'
- '/api/article/articleComment'
在 config/ConfigDefault.go
在 utils/index.go
下
在 service/ArticleService.go
// ArticleCommentService 获取文章评论
func ArticleCommentService(param *request.ArticleCommentParam, comments *[]entity.ArticleComments) error {
if param.ArticleId == 0 {
return errors.New("参数为空")
}
// 获取数据库信息
if err := pool.Table("article_comments").
Where("article_id", param.ArticleId).
Joins("INNER JOIN sys_user ON article_comments.user_id = sys_user.uid").
Select("article_comments.id, article_comments.article_id, article_comments.context, sys_user.user_name, sys_user.head_sculpture, sys_user.integral, sys_user.member").
Find(&comments).Error; err != nil {
return errors.New("获取失败")
}
// 获取成功
return nil
}
在 requset
下创建 ArticleResponse.go
// ArticleCommentResponse 文章评论返回参数
type ArticleCommentResponse struct {
ID uint32 `json:"id"` // 评论ID
ArticleID uint32 `json:"article_id"` // 文章ID
Context string `json:"context"` // 评论内容
UserName string `json:"user_name"` // 用户名
HeadSculpture string `json:"head_sculpture"` // 头像 默认值
Integral uint32 `json:"integral"` // 积分
Member uint8 `json:"member"` // 会员
}
在 requset/ArticleRequest.go
下
// ArticleCommentParam 文章评论参数
type ArticleCommentParam struct {
ArticleId int32 `form:"article_id" binding:"required"`
}
在 controller/ArticleController.go
下
// ArticleComment 获取文章评论
func ArticleComment(c *gin.Context) {
var params request.ArticleCommentParam
var commentList []entity.ArticleComments
// 绑定参数
err := c.ShouldBindQuery(¶ms)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
// 查询数据库
if err := service.ArticleCommentService(¶ms, &commentList); err != nil {
utils.FailResult(c, err.Error())
return
}
// 获取成功
utils.SuccessResult(c, "获取成功", map[string][]entity.ArticleComments{"rows": commentList})
}
在 router/ArticleRouter.go
下
// 获取文章评论
router.GET("/articleComment", controller.ArticleComment)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.26 编写上传评论接口
接口地址:
/api/article/addComment
在 service/ArticleService.go
// AddArticleCommentService 添加文章评论
func AddArticleCommentService(param *request.AddArticleCommentParam, userInfo entity.SysUser) (error, bool) {
// 判断参数是否为空
if param.ArticleId == 0 || utils.StrIsEmpty(param.Context) {
return errors.New("参数为空"), false
}
// 判断参数格式
// 将数据存入数据库
if err := pool.Model(&entity.ArticleComments{}).Create(&entity.ArticleComments{
ArticleID: param.ArticleId,
UserID: userInfo.UID,
Context: param.Context,
}).Error; err != nil {
return errors.New("上传失败"), false
}
// 添加成功
return nil, true
}
在 requset/ArticleRequest.go
下
// AddArticleCommentParam 添加文章评论
type AddArticleCommentParam struct {
ArticleId uint32 `json:"article_id" binding:"required"`
Context string `json:"context" binding:"required"`
}
在 controller/ArticleController.go
下
// AddArticleComment 添加文章评论
func AddArticleComment(c *gin.Context) {
// 获取参数类型
var param request.AddArticleCommentParam
// 绑定参数
err := c.ShouldBindJSON(¶m)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
err, tokenInfo := utils.GetCacheUser(c)
if err != nil {
utils.AuthorizationResult(c, "登录状态失效")
return
}
// 将数据添加到数据库
err, status := service.AddArticleCommentService(¶m, tokenInfo.UserInfo)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 判断是否添加成功
if !status {
utils.FailResult(c, "上传失败")
return
}
utils.SuccessResult(c, "上传成功", nil)
}
在 router/ArticleRouter.go
下
// 添加文章评论
router.POST("/addComment", controller.AddArticleComment)
运行
go run main.go
测试
打开
postman/apiPost
进行测试
![[gin_co_blog_assets/Pasted image 20231018142319.png]]
4.27 编写用户反馈接口
接口地址:
/api/user/userNameOccupy
在 service
目录下创建 FeedBackService.go
// AddFeedBackService 添加用户反馈
func AddFeedBackService(param request.AddFeedBackParam, userInfo entity.SysUser) (error, bool) {
// 验证数据
if utils.StrIsEmpty(param.Context) {
return errors.New("参数为空"), false
}
// 验证数据
// 向数据库内添加数据
if err := pool.Model(&entity.FeedBack{}).Create(&entity.FeedBack{
UserID: userInfo.UID,
Context: param.Context,
}).Error; err != nil {
return errors.New("上传失败"), false
}
return nil, true
}
在 requset
下创建FeedBackRequest.go
// AddFeedBackParam 添加反馈信息参数
type AddFeedBackParam struct {
Context string `json:"context" binding:"required"`
}
在 controller
下创建FeedBackController.go
// AddFeedBack 添加用户反馈
func AddFeedBack(c *gin.Context) {
var param request.AddFeedBackParam
// 绑定数据
err := c.ShouldBindJSON(¶m)
if err != nil {
utils.FailResult(c, "参数错误")
return
}
err, tokenInfo := utils.GetCacheUser(c)
if err != nil {
utils.AuthorizationResult(c, "登录状态失效")
return
}
// 向数据库内添加
err, status := service.AddFeedBackService(param, tokenInfo.UserInfo)
if err != nil {
utils.FailResult(c, err.Error())
return
}
// 判断是否成功
if !status {
utils.FailResult(c, "反馈失败")
return
}
// 反馈成功
utils.SuccessResult(c, "反馈成功", nil)
}
在 router
下创建FeedBackRouter.go
// FeedBackRouter 定义反馈路由(二级)
func FeedBackRouter(router *gin.RouterGroup) {
// 添加反馈
router.POST("/addFeedback", controller.AddFeedBack)
}
在 router/index.go
下
// SetupRouterGroup 项目主路由(一级)
func SetupRouterGroup(router *gin.RouterGroup) {
// 调取用户路由
UserRouter(router.Group("/user"))
// 调取上传路由
UploadRouter(router.Group("/upload"))
// 调取文章路由
ArticleRouter(router.Group("/article"))
// 调取反馈路由
FeedBackRouter(router.Group("/feedback"))
}
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.28 编写查询VIP列表接口
接口地址:
/api/user/userNameOccupy
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
在 requset/UserRequest.go
下
在 controller/UserController.go
下
在 router/UserRouter.go
下
运行
go run main.go
测试
打开
postman/apiPost
进行测试
4.29 编写升级VIP接口
接口地址:
/api/user/userNameOccupy
在 Application.yaml
下添加:
在 config/ConfigDefault.go
在 utils/index.go
下
在 service
目录下创建 UserService.go
在 requset/UserRequest.go
下
在 controller/UserController.go
下
在 router/UserRouter.go
下
运行
go run main.go
测试
打开
postman/apiPost
进行测试
5 打包
5.1 生成接口文档
在 apifox 上点击项目三个点->导出
![[gin_co_blog_assets/Pasted image 20231018155343.png]]
选择导出 MarkDown 导出就行
5.2 exe 程序
将项目右键,找到 Open In --> Terminal
![[gin_co_blog_assets/Pasted image 20231018160106.png]]
## 生成与项目同名的 exe 程序
go build .
5.3 linux 执行程序
也是打开 Terminal
输入以下命令:
## 变回windows
go env -w CGO_ENABLED=1 GOOS=windows GOARCH=amd64
## linux
go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build .
5.4 Docker 镜像
5.4.1 编写 Dockerfile
在项目根目录下创建 Dockerfile
# 使用 golang 的官方镜像作为基础镜像
FROM golang:latest
# 设置工作目录
WORKDIR /app
# 将代码复制到容器中的工作目录
COPY . .
# 构建 Go 语言程序
RUN go build -o CoBlogInterface .
# 暴露需要监听的端口
EXPOSE 4001
# 运行 Go 语言程序
CMD ["./CoBlogInterface"]
5.4.2 生成 Docker Image
在 terminal打开,并输入:
docker build -t co_blog_interface:1.0 .
![[gin_co_blog_assets/Pasted image 20231018162907.png]]
6 发布
6.1 linux 发布
6.2 Docker 发布
安装 Docker
拉取