用gin写简单的crud后端API接口

提要

使用gin框架(go的web框架)来创建简单的几个crud接口)
使用技术: gin + sqlite3 + sqlx

创建初始工程

新建文件夹,创建三个子文件夹

分别初始化工程 go mod

如果没有.go文件,执行go mod tidy可能报错(warning: "all" matched no packages), 可以先不弄,只初始化模块就行(go mod init 模块名)
# 项目根目录创建模块
go mod init go_manager 
go mod tidy
# 进入db目录
cd db
# 初始化模块
go mod init go_manager_db 
go mod tidy
# 进入utils目录
cd ../utils
# 初始化模块
go mod init go_manager_utils 
go mod tidy
# 进入web目录
cd ../web
# 初始化模块
go mod init go_manager_web 
go mod tidy

go_manager_db模块编写

创建数据库连接(sqlite如果没有库会自动建)

// db\main.go
package go_manager_db

import (
    "fmt"
    "github.com/jmoiron/sqlx"
    _ "github.com/mattn/go-sqlite3"
)

// 数据库相关操作
var db *sqlx.DB

// 初始化数据库连接
func InitDB() (err error) {
    dsn := "./manager.db"
    // 连接
    // Open可能仅校验参数,而没有与db间创建连接,
    // 要确认db是否可用,需要调用Ping。Connect则相当于Open+Ping。
    db, err = sqlx.Connect("sqlite3", dsn)
    if err != nil {
        fmt.Printf("connect DB failed, err:%v\n", err)
        return
    }
    // 最大连接数
    db.SetMaxOpenConns(100)
    // 最大空闲连接数
    db.SetMaxIdleConns(16)
    // 初始化方法,建表+插入原始数据
    CreateRoleTable()
    CreateUserTable()
    return
}

添加建表方法(初始化权限表和用户表)

// db\main.go
package go_manager_db

import (
    "fmt"
    "github.com/jmoiron/sqlx"
    _ "github.com/mattn/go-sqlite3"
)

// 数据库相关操作
var db *sqlx.DB

// 初始化数据库连接
func InitDB() (err error) {......}
// 创建用户表
func CreateUserTable() error {
    sqlc := `
    CREATE TABLE IF NOT EXISTS "mal_user" (
      -- sqlite 不能用 comment 添加注释
      "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , -- '主键'
      "uname" varchar(20) NOT NULL UNIQUE , -- '用户昵称'
      "upass" varchar(50) NOT NULL, -- '密码(md5加密)'
      "rid" INTEGER NOT NULL UNIQUE DEFAULT 1 -- '角色id'
    ); 
    `
    _, err := db.Exec(sqlc)
    if err != nil {
        fmt.Println(err)
        return err
    }
    // 初始化表
    //因为有unique约束,所以不会重复添加
    // sqlStr := "insert into mal_user(uname,upass,rid) values(?,?,?)"
    Insert("mal_user", []string{"uname", "upass", "rid"}, "admin", "e120012d113ff6ea124a2493453c6dd5", 2)
    return nil
}

// 创建权限表
func CreateRoleTable() error {
    sqlc := `
    CREATE TABLE IF NOT EXISTS "mal_role" (
      "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- '主键' 
      "role" varchar(20) NOT NULL UNIQUE DEFAULT 'user' -- '角色(权限)' 
    ); 
    `
    _, err := db.Exec(sqlc)
    if err != nil {
        return err
    }
    // 初始化表
    // 因为有unique约束,所以不会重复添加 
    // 有四种权限,id(自增)越大代表权限越大,root>super>admin>user
    Insert("mal_role", []string{"role"}, "user")
    Insert("mal_role", []string{"role"}, "admin")
    Insert("mal_role", []string{"role"}, "super")
    Insert("mal_role", []string{"role"}, "root")
    return nil
}

base.go: 通用插入和删除方法

// db\base.go
package go_manager_db

import (
    "fmt"
    _ "github.com/mattn/go-sqlite3"
    utils "go_manager_utils"
)

// 插入数据
func Insert(tableName string, params []string, datas ...interface{}) (err error) {
    // 拼接 表名(参数1,参数2,...)
    paramStr := utils.ParamsStr(params)
    // 拼接 values(?,?,...)
    values := utils.ValueStr(len(params))
    var sqlStr = "insert into " + tableName + paramStr + " values" + values
    fmt.Println(sqlStr)
    _, err = db.Exec(sqlStr, datas...) // 要用...展开
    if err != nil {
        fmt.Println(err)
        fmt.Println("插入数据失败")
        return
    }
    return
}

// 删除数据
func Delete(tableName string, id int64) (err error) {
    sqlStr := "delete from " + tableName + " where id=?"
    fmt.Println(sqlStr)
    _, err = db.Exec(sqlStr, id)
    if err != nil {
        fmt.Println("删除数据失败")
        return
    }
    return
} 

model.go: 定义数据表对应的结构体

package go_manager_db
 
// 专门定义与数据库交互的结构体

// 用户表
type MalUser struct {
    Id    int64  `db:"id" json:"Rd"`
    Uname string `db:"uname" json:"Uname"`
    Upass string `db:"upass" json:"Upass"`
    Rid   int64  `db:"rid" json:"Rid"`
}
// 角色表
type MalRole struct {
    Id   int64  `db:"id" json:"Id"`
    Role string `db:"role" json:"Role"`
}

mal_user.go和mal_role.go: 定义用户表和角色表的crud方法

mal_user.go

package go_manager_db

import (
    "fmt" 
    utils "go_manager_utils"

    _ "github.com/mattn/go-sqlite3"
)

// 查数据
func GetAllUser() (users []*MalUser, err error) {
    sqlStr := `select * from mal_user`
    // 查询,记录到booklist
    err = db.Select(&users, sqlStr)
    if err != nil {
        fmt.Println("查询信息失败")
        fmt.Println(err)
        return
    }
    return
}

// 根据id查数据
func GetUserById(id int64) (user MalUser, err error) {
    // 如果返回的是指针,需要初始化
    //book=&Book{}
    sqlStr := "select * from mal_user where id=?"
    err = db.Get(&user, sqlStr, id)
    if err != nil {
        fmt.Println("查询信息失败")
        return
    }
    return
}

// 根据name查数据
func GetUserByName(uname string, upass string) (user MalUser, err error) {
    sqlStr := "select * from mal_user where uname=? and upass=?"
    err = db.Get(&user, sqlStr, uname, upass)
    if err != nil {
        fmt.Println("查询信息失败")
        return
    }
    return
}

// 根据id改
func UptUserById(uid string, params []string, datas ...interface{}) (err error) {
    // 拼接参数列表 xxx=?,xxx=?
    paramsStr := utils.UptParamsStr(params)
    // uid直接传字符串拼接
    sqlStr := "update mal_role set " + paramsStr + " where id=" + uid
    _, err = db.Exec(sqlStr, datas...)
    if err != nil {
        fmt.Println("修改信息失败")
        return
    }
    return
}

mal_role.go

package go_manager_db

import (
    "fmt"
    _ "github.com/mattn/go-sqlite3"
)

// 应该id越大,权限越高,比较方便区分权限
// user < admin < super < root
// 查数据
func GetAllRole() (roles []*MalRole, err error) {
    sqlStr := `select * from mal_role`
    // 查询,记录到booklist
    err = db.Select(&roles, sqlStr)
    if err != nil {
        fmt.Println("查询信息失败")
        fmt.Println(err)
        return
    }
    return
}

// 根据id查数据
func GetRoleById(id int64) (role MalRole, err error) {
    // 如果返回的是指针,需要初始化
    //book=&Book{}
    sqlStr := "select * from mal_role where id=?"
    err = db.Get(&role, sqlStr, id)
    if err != nil {
        fmt.Println("查询信息失败")
        return
    }
    return
}

// 根据id改数据
func UptRoleById(id int64, roleName string) (err error) {
    // 如果返回的是指针,需要初始化
    //book=&Book{}
    sqlStr := "update mal_role set role=? where id=?"
    _, err = db.Exec(sqlStr, roleName, id)
    if err != nil {
        fmt.Println("修改信息失败")
        return
    }
    return
}

引入项目里的其他模块: utils

在go.mod末尾添加

replace go_manager_utils => ../utils

运行 go mod tidy

go_manager_utils模块编写

jwt.go: 编写加密方法,定时销毁token方法

package go_manager_util

import (
    "crypto/md5"
    "fmt"
    "gopkg.in/square/go-jose.v2"
    "gopkg.in/square/go-jose.v2/jwt"
    "time"
)

// sign 签名
// 传入密码,加密
func SignJWT(secret string, uname string, upass string) (jwtStr string) {
    key := []byte(secret)
    fmt.Println(secret)
    sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key},
        (&jose.SignerOptions{}).WithType("JWT"))
    if err != nil {
        panic(err)
    }

    cl := jwt.Claims{
        // Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐
        // 比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
        Issuer:    uname,
        Subject:   upass,
        NotBefore: jwt.NewNumericDate(time.Now()),
        Audience:  jwt.Audience{"name", "admin"},
    }
    raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
    if err != nil {
        panic(err)
    }

    // fmt.Println(raw)
    return raw
}

// 解析jwt
// 传入key(之前加密的密码),raw(jwt令牌)
func ParseJWT(key string, raw string) {
    var sharedKey = []byte(key)
    tok, err := jwt.ParseSigned(raw)
    if err != nil {
        panic(err)
    }
    out := jwt.Claims{}
    // 解析出issuer(uname)和subject(upass),校验
    if err := tok.Claims(sharedKey, &out); err != nil {
        panic(err)
    }
    fmt.Printf("iss: %s, sub: %s\n", out.Issuer, out.Subject)
}

// DM5加密
func MD5(str string) string {
    data := []byte(str) //切片
    has := md5.Sum(data)
    md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
    return md5str
}

// 销毁TokenMap的方法
// 定时销毁token(默认2小时)
func DestoryTokenMap(tokenMap map[string]string) {
    for k := range tokenMap {
        delete(tokenMap, k)
    }
}

myTime.go: 定义定时器方法

package go_manager_util

import ( 
    "time"
)

// 定义函数类型
type Fn func() error

// 定时器中的成员
type MyTicker struct {
    MyTick *time.Ticker
    Runner Fn
}
type MyTimer struct {
    MyTime *time.Timer
    Runner Fn
}

func NewMyTick(interval int, f Fn) *MyTicker {
    return &MyTicker{
        MyTick: time.NewTicker(time.Duration(interval) * time.Second),
        Runner: f,
    }
}

// 一次性
func NewMyTimer(interval int, f Fn) *MyTimer {
    return &MyTimer{
        MyTime: time.NewTimer(time.Duration(interval) * time.Second),
        Runner: f,
    }
}

// 启动定时器需要执行的任务
func (t *MyTicker) Start() {
    for {
        select {
        case <-t.MyTick.C:
            t.Runner()
        }
    }
}

// 启动定时器需要执行的任务
func (t *MyTimer) Start() { 
    select {
    case <-t.MyTime.C:
        t.Runner()
    } 
}

// func over() error {
//     fmt.Println("token过期")
//     return nil
// }
// 测试
// func main() {
//     t := NewMyTimer(2, over)
//     t.Start()
// }

res.go: 响应前端请求的方法

package go_manager_util

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

/* 通用响应方法 */
func R(c *gin.Context, err error, msg interface{}, data interface{}) {
    // 如果有err,就说明是有错误,就返回错误响应(msg)
    if err != nil {
        fmt.Println(err)
        c.JSON(http.StatusInternalServerError, gin.H{
            "status": 500,
            "msg":    msg,
        })
        return
    }
    // 返回正确响应(data)
    c.JSON(http.StatusOK, gin.H{
        "status": 200,
        "msg":    data,
    })
}

stringUtils.go: 封装字符串操作方法

package go_manager_utils

// 拼接sql语句的value
// len是语句有几个参数
func ValueStr(len int) (values string) {
    // 拼接 values(?,?,...)
    values = "("
    for i := 0; i < len-1; i++ {
        values += "?"
        values += ","
    }
    values += "?"
    values += ")"
    return
}

// 拼接sql语句update的param
// params是参数名数组
func UptParamsStr(params []string) (paramStr string) {
    // 拼接参数列表 xxx=?,xxx=?
    paramStr = ""
    for i := 0; i < len(params)-1; i++ {
        paramStr += params[i]
        paramStr += "=?,"
    }
    paramStr += params[len(params)-1]
    paramStr += "=?"
    return
}

// 拼接sql语句的param
// params是参数名数组
func ParamsStr(params []string) (paramStr string) {
    // 拼接 表名(参数1,参数2,...)
    paramStr = "("
    for i := 0; i < len(params)-1; i++ {
        paramStr += params[i]
        paramStr += ","
    }
    paramStr += params[len(params)-1]
    paramStr += ")"
    return
}

运行go mod tidy处理go文件里的依赖

go_manager_web模块编写

main.go: 主要逻辑,创建web实例,注册路由...

package go_manager_web

import (
    "fmt"
    "github.com/gin-gonic/gin"
    db "go_manager_db"
    utils "go_manager_utils"
    "net/http"
)

// 定义路由组
// 组中组(嵌套路由组)
func DefineRouteGroup(fatherGroup *gin.RouterGroup, groupName string, r *gin.Engine) *gin.RouterGroup {
    var group *gin.RouterGroup
    if fatherGroup != nil {
        // v1/groupName
        group = fatherGroup.Group(groupName)
    } else {
        // /groupName
        group = r.Group(groupName)
    }
    // 返回路由组
    return group
}

// 存放 token (不同ip不同token)
var TokenMap = make(map[string]string, 10)

// 定时销毁token
func timeDT() {
    // 两小时后销毁
    t := utils.NewMyTimer(2*60*60, func() error {
        utils.DestoryTokenMap(TokenMap)
        return nil
    })
    t.Start()
    fmt.Println(TokenMap)
}

// 路由和处理函数放在不同文件好像会使中间件失效
func Login(c *gin.Context) { 
    user := db.MalUser{}
    // 绑定json和结构体(接收json,数据放入结构体)
    if err := c.BindJSON(&user); err != nil {
        return
    }
    uname := user.Uname
    upass := user.Upass 
    userModel, err := db.GetUserByName(uname, upass)
    if err != nil || &userModel == nil {
        fmt.Println(err)
        c.JSON(500, gin.H{
            "status": 500,
            "msg":    "登录失败",
        })
        return
    } 
    token := utils.SignJWT("malred", uname, upass)
    // 存入map
    // fmt.Println(c.ClientIP(),c.RemoteIP())
    TokenMap[c.ClientIP()] = token
    fmt.Println(TokenMap)
    c.JSON(http.StatusOK, gin.H{
        "status": 200,
        "msg":    "登录成功",
        // 返回jwt令牌(密码因为前端md5加密过,所以直接放入jwt)
        "token": token,
    })
    go timeDT()
}

// 路由器
// 启动默认的路由
var r = gin.Default()

// user路由组
var v1 *gin.RouterGroup

func Run() {
    // 使用中间件
    // 日志
    r.Use(gin.Logger())
    // 错误恢复
    r.Use(gin.Recovery())
    // 跨域
    r.Use(Core())
    // 阻止缓存响应
    r.Use(NoCache())
    // 安全设置
    r.Use(Secure())
    // 创建路由组v1
    v1 = DefineRouteGroup(nil, "v1", r)
    v1.POST("login", Login)
    // 注册user的路由
    registerUser(Token(), Core())
    // 注册role的路由
    registerRole(Token(), Core())
    // 启动webserver,监听本地127.0.0.1(默认)端口
    r.Run(":10101")
}

moddilewares.go: 中间件

package go_manager_web

import ( 
    utils "go_manager_utils"
    "net/http"
    "strconv"
    "time"

    "github.com/gin-gonic/gin"
)

//解决跨域问题
func Core() gin.HandlerFunc {
    return func(c *gin.Context) {
        method := c.Request.Method
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Headers", "*")
        c.Header("Access-Control-Allow-Methods", "*")
        c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type")
        c.Header("Access-Control-Max-Age", "3600")
        c.Header("Access-Control-Allow-Credentials", "true")
        //放行索引options
        if method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
        }
        //处理请求
        c.Next()
    }
}

// 权限认证(验证token)
func Token() gin.HandlerFunc {
    return func(c *gin.Context) {
        // for k, v := range c.Request.Header {
        //     fmt.Println(k, v)
        // }
        secret := c.Request.Header["Secret"] // 获取前端传来的secret
        token := c.Request.Header["Token"]
        if len(token) == 0 {
            // 验证不通过,不再调用后续的函数处理
            c.Abort()
            c.JSON(http.StatusUnauthorized, gin.H{
                "code":    401,
                "message": "访问未授权",
            })
            return
        }
        timeInt64 := strconv.FormatInt(time.Now().UnixNano()/1e6/1000/60, 10)
        md5Str := utils.MD5(timeInt64 + TokenMap[c.ClientIP()])
        // fmt.Println(TokenMap[c.ClientIP()], timeInt64)
        // fmt.Println(timeInt64 + TokenMap[c.ClientIP()])
        // fmt.Println(md5Str, secret[0])
        if md5Str != secret[0] {
            // 验证不通过,不再调用后续的函数处理
            c.Abort()
            c.JSON(http.StatusUnauthorized, gin.H{
                "code":    401,
                "message": "访问未授权",
            })
            return
        }
        // 验证jwt
        // utils.ParseJWT(secret[0][8:11]+secret[0][19:22], token[0])
        //处理请求
        c.Next()
    }
}

// 阻止缓存响应
func NoCache() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ctx.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
        ctx.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
        ctx.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
        ctx.Next()
    }
}

// 响应 options 请求, 并退出
// func Options() gin.HandlerFunc {
//     return func(ctx *gin.Context) {
//         if ctx.Request.Method != "OPTIONS" {
//             ctx.Next()
//         } else {
//             ctx.Header("Access-Control-Allow-Origin", "*")
//             ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
//             ctx.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
//             ctx.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
//             ctx.Header("Content-Type", "application/json")
//             ctx.AbortWithStatus(200)
//         }
//     }
// }

// 安全设置
func Secure() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ctx.Header("Access-Control-Allow-Origin", "*")
        ctx.Header("X-Frame-Options", "DENY")
        ctx.Header("X-Content-Type-Options", "nosniff")
        ctx.Header("X-XSS-Protection", "1; mode=block")
        if ctx.Request.TLS != nil {
            ctx.Header("Strict-Transport-Security", "max-age=31536000")
        }

        // Also consider adding Content-Security-Policy headers
        // ctx.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
    }
}

// todo 权限控制(token携带当前用户的权限信息,过滤低于指定权限的请求)

role.go和user.go: 真的role和user表的web操作

role.go

package go_manager_web

import (
    "fmt"
    db "go_manager_db"
    utils "go_manager_utils"
    "strconv" 
    "github.com/gin-gonic/gin"
)

func GetAllRoleHandler(c *gin.Context) {
    roles, err := db.GetAllRole()
    // 通用响应
    utils.R(c, err, "获取角色列表失败", roles)
}
func AddRoleHandler(c *gin.Context) {
    // Role := c.PostForm("Role")
    // fmt.Println(Role)
    role := db.MalRole{}
    //绑定json和结构体
    if err := c.BindJSON(&role); err != nil {
        return
    }
    Role := role.Role
    err := db.Insert("mal_role", []string{"role"}, Role)
    // 通用响应
    utils.R(c, err, "添加角色失败", "添加角色成功")
}
func DelRoleHandler(c *gin.Context) {
    // 从url获取参数
    idStr := c.Query("rid")
    // fmt.Println(idStr)
    rid, err := strconv.ParseInt(idStr, 10, 64)
    err = db.Delete("mal_role", rid)
    // 通用响应
    utils.R(c, err, "删除角色失败", "删除角色成功")
}
func GetOneRoleHandler(c *gin.Context) {
    // 从url获取参数
    idStr := c.Query("rid")
    fmt.Println(idStr)
    rid, _ := strconv.ParseInt(idStr, 10, 64)
    one, err2 := db.GetRoleById(rid)
    // 通用响应
    utils.R(c, err2, "查询角色失败", one)
}
func UptRoleHandler(c *gin.Context) {
    role := db.MalRole{}
    //绑定json和结构体
    if err := c.BindJSON(&role); err != nil {
        return
    } 
    rid := role.Id
    roleName := role.Role
    fmt.Println(role)
    err := db.UptRoleById(rid, roleName)
    // 通用响应
    utils.R(c, err, "修改角色失败", "修改角色成功")
}
func registerRole(middles ...gin.HandlerFunc) {
    // 创建路由组v1/user
    role := DefineRouteGroup(v1, "role", r)
    // 添加中间件
    if middles != nil {
        role.Use(middles...)
    }
    // 获取所有
    role.GET("all", GetAllRoleHandler)
    // 添加
    role.POST("add", AddRoleHandler)
    // 删除
    role.DELETE("del", DelRoleHandler)
    // 根据id获取
    role.GET("id", GetOneRoleHandler)
    // 根据id修改
    role.PUT("upt", UptRoleHandler)
}

user.go

package go_manager_web

import (
    "fmt"
    db "go_manager_db"
    utils "go_manager_utils"
    "strconv" 

    "github.com/gin-gonic/gin"
)

func GetAllUserHandler(c *gin.Context) {
    users, err := db.GetAllUser()
    // 通用响应
    utils.R(c, err, "查询角色失败", users)
}
func AddUserHandler(c *gin.Context) {
    // uname := c.PostForm("uname")
    // upass := c.PostForm("upass")
    // idStr := c.PostForm("rid")
    user := db.MalUser{}
    //绑定json和结构体
    if err := c.BindJSON(&user); err != nil {
        return
    }
    uname := user.Uname
    upass := user.Upass
    rid := user.Rid
    fmt.Println(user)
    // rid, err := strconv.ParseInt(idStr, 10, 64)
    err := db.Insert("mal_user", []string{"uname", "upass", "rid"}, uname, upass, rid)
    // 通用响应
    utils.R(c, err, "添加角色失败", "添加角色成功")
}
func DelUserHandler(c *gin.Context) {
    // 从url获取参数
    idStr := c.Query("uid")
    // fmt.Println(idStr)
    uid, err := strconv.ParseInt(idStr, 10, 64)
    err = db.Delete("mal_user", uid)
    // 通用响应
    utils.R(c, err, "删除角色失败", "删除角色成功")
}
func GetOneUserHandler(c *gin.Context) {
    // 从url获取参数
    idStr := c.Query("uid")
    fmt.Println(idStr)
    uid, _ := strconv.ParseInt(idStr, 10, 64)
    one, err2 := db.GetUserById(uid)
    // 通用响应
    utils.R(c, err2, "查询角色失败", one)
}
func UptUserHandler(c *gin.Context) {
    // 从url获取参数
    // uid := c.PostForm("uid")
    // uname := c.PostForm("uname")
    // upass := c.PostForm("upass")
    // ridStr := c.PostForm("rid")
    user := db.MalUser{}
    //绑定json和结构体
    if err := c.BindJSON(&user); err != nil {
        return
    }
    uname := user.Uname
    upass := user.Upass
    rid := user.Rid
    uid := user.Id
    // fmt.Println(idStr, UserName)
    // rid, _ := strconv.ParseInt(ridStr, 10, 64)
    err := db.UptUserById(strconv.FormatInt(uid, 10), []string{"uname", "upass", "rid"}, uname, upass, rid)
    // 通用响应
    utils.R(c, err, "修改角色失败", "修改角色成功")
}
func registerUser(middles ...gin.HandlerFunc) {
    // 创建路由组v1/user
    user := DefineRouteGroup(v1, "user", r)
    // 添加中间件
    if middles != nil {
        user.Use(middles...)
    }
    user.GET("all", GetAllUserHandler)
    // 添加
    user.POST("add", AddUserHandler)
    // 删除
    user.DELETE("del", DelUserHandler)
    // 根据id获取
    user.GET("id", GetOneUserHandler)
    // 根据id修改
    user.PUT("upt", UptUserHandler)
}

运行go mod tidy

忘了,要引用项目里的其他包

replace go_manager_utils => ../utils
replace go_manager_db => ../db

go mod tidy

编写根目录的go_manager模块

main.go

package main

import ( 
    db "go_manager_db"
    web "go_manager_web"
)

func main() {
    // 初始化数据库
    db.InitDB()   
    // 开启服务
    web.Run()
}

go.mod

module go_manager

go 1.18

replace go_manager_web => ./web

replace go_manager_db => ./db

replace go_manager_utils => ./utils

go mod tidy

测试(可以用go build打包)

完整目录结构

go run main.go

因为后端存的密码是md5加密过的,所以前端也要传md5加密的密码,二者相同才能通过

安全: 我的安全不咋地,加密的方法是前端根据当前时间戳(转为分钟,防止因为前后端延迟而导致时间戳不一致)+登录后从后端获取的token来md5,每次请求都会验证这个md5(后端也加密(时间戳/60+token)然后对比),这个就不测试了

代码仓库:

https://gitee.com/malguy/go-manager

配套前端管理系统(react18):

https://github.com/malred/base-manager

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
通常来说,连接数据库需要以下三个步骤: 1. 导入相应的数据库驱动 2. 使用驱动建立数据库连接 3. 使用连接对象创建一个指向数据库的游标对象 以下是一个使用gin框架连接MySQL数据库的示例代码: ```go import ( "database/sql" "fmt" "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" ) func main() { // 建立数据库连接 db, err := sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/database_name") if err != nil { fmt.Println("数据库连接失败:", err.Error()) return } defer db.Close() // 创建一个gin路由器 router := gin.Default() // 定义一个路由处理函数 router.GET("/", func(c *gin.Context) { // 查询数据库 rows, err := db.Query("SELECT * FROM users") if err != nil { fmt.Println("查询失败:", err.Error()) return } defer rows.Close() // 处理查询结果 for rows.Next() { var id int var name string var age int err = rows.Scan(&id, &name, &age) if err != nil { fmt.Println("结果解析失败:", err.Error()) return } fmt.Printf("%d %s %d\n", id, name, age) } }) // 启动web服务 router.Run(":8080") } ``` 其中,`sql.Open()`函数的第一个参数指定了数据库驱动,这里使用的是MySQL数据库驱动;第二个参数指定了数据库连接信息,格式为`username:password@tcp(127.0.0.1:3306)/database_name`,其中`username`和`password`分别是数据库用户名和密码,`tcp(127.0.0.1:3306)`表示连接的MySQL服务器地址和端口号,`/database_name`表示连接的数据库名称。在示例代码中,我们使用`defer`语句来确保在函数结束之前关闭数据库连接。 在路由处理函数中,我们首先使用`db.Query()`函数执行SQL查询语句,然后使用`rows.Next()`函数遍历查询结果。`rows.Scan()`函数用于解析每行结果,并将其赋值给变量。在这里,我们假设查询结果为一个名为`users`的表,该表包含三个字段:`id`、`name`和`age`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞鸟malred

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值