使用gin框架(go的web框架)来创建简单的几个crud接口)
使用技术: gin + sqlite3 + sqlx
如果没有.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
// 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
}
// 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
}
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"`
}
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
}
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
}
replace go_manager_utils => ../utils
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)
}
}
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()
// }
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,
})
}
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
}
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")
}
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携带当前用户的权限信息,过滤低于指定权限的请求)
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)
}
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)
}
replace go_manager_utils => ../utils
replace go_manager_db => ../db
go mod tidy
package main
import (
db "go_manager_db"
web "go_manager_web"
)
func main() {
// 初始化数据库
db.InitDB()
// 开启服务
web.Run()
}
module go_manager
go 1.18
replace go_manager_web => ./web
replace go_manager_db => ./db
replace go_manager_utils => ./utils
https://gitee.com/malguy/go-manager
https://github.com/malred/base-manager