## Go写后台API
项目很渣,大佬可以提意见~~
### 技术栈
用的gin框架,登录验证使用jwt-go,权限管理是casbin,数据库使用mongodb
### 为什么要写这篇文章
因为想记录自己踩的坑,顺便记录心得。然后关于casbin这个库的使用资料很少,对于新手来说很难看明白,大神那肯定刷一遍就懂了。
### 正题
go操作mongodb 使用mgo这个包。因为项目不考虑并发 ,如果项目并发很高,我这样写肯定是不行的不建议参考,因为Session 会被全局使用,当在实际的程序中,我们可以开启goroutine 来处理每个连接,多个goroutine 可以通过 session.Clone() 来创建或复用连接,使用完成之后通过 session.Close() 来关闭这个连接。mongodb默认连接是4096,如果session使用后不close( )很容易堵塞。回到正题,将mgo的操作封装一下 ,名字为什么叫mongoose因为node有个操作mongo的叫这个还挺喜欢这个名字就用了。
```go
package mongoose
import (
"fmt"
"log"
"gopkg.in/mgo.v2"
)
//定义MongoDB 连接字符串
const (
host = "localhost:27017"
source = "leaseapp"
user = "martin"
pass = "123456"
)
// 定义mongo session
var globalS *mgo.Session
var MongoUrl = host + "/" + source
//初始化连接MongoDB
func init() {
dialInfo := &mgo.DialInfo{
Addrs: []string{host},
Source: source,
Username: user,
Password: pass,
}
session, err := mgo.DialWithInfo(dialInfo)
if err != nil {
log.Fatalln("create session error", err)
}
fmt.Println("MongoDB Connect")
globalS = session
}
//连接MongoDB 返回一个session会话和一个集合c
func connect(db, collection string) (*mgo.Session, *mgo.Collection) {
s := globalS.Copy()
c := s.DB(db).C(collection)
return s, c
}
//插入
func Insert(db, collection string, docs ...interface{}) error {
ms, c := connect(db, collection)
defer ms.Close()
return c.Insert(docs...)
}
//查找某一个函数
func FindOne(db, collection string, query, selector, result interface{}) error {
ms, c := connect(db, collection)
defer ms.Close()
return c.Find(query).Select(selector).One(result)
}
//是否存在
func IsExist(db, collection string, query interface{}) bool {
ms, c := connect(db, collection)
defer ms.Close()
count, _ := c.Find(query).Count()
return count > 0
}
//查找所有
func FindAll(db, collection string, query, selector, result interface{}) error {
ms, c := connect(db, collection)
defer ms.Close()
return c.Find(query).Select(selector).All(result)
}
//更新
func Update(db, collection string, query, update interface{}) error {
ms, c := connect(db, collection)
defer ms.Close()
return c.Update(query, update)
}
//删除
func Remove(db, collection string, query interface{}) error {
ms, c := connect(db, collection)
defer ms.Close()
return c.Remove(query
```
然后写一下JWT ,JWT(Jason Web Token) 包含三部分 头部,载荷(Claims或Payload) ,签证,载荷里我们是可以放权限的。
```go
//JWT签名结构
type JWT struct {
SigningKey []byte
}
//载荷
type Customclaims struct {
Empname string `json:"empname"`
Phone string `json:"phone"`
Role string `json:"role"`
jwt.StandardClaims
}
```
然后写创建token 解析token 刷新token
```go
//创建Token
func (j *JWT) CreateToken(claims Customclaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.SigningKey)
}
//解析token
func (j *JWT) ParseToken(tokenString string) (*Customclaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Customclaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, TokenMalformed
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
// Token is expired
return nil, TokenExpired
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, TokenNotValidYet
} else {
return nil, TokenInvalid
}
}
}
if claims, ok := token.Claims.(*Customclaims); ok && token.Valid {
return claims, nil
}
return nil, TokenInvalid
}
//更新Token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
jwt.TimeFunc = func() time.Time {
return time.Unix(0, 0)
}
token, err := jwt.ParseWithClaims(tokenString, &Customclaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*Customclaims); ok && token.Valid {
jwt.TimeFunc = time.Now
claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
return j.CreateToken(*claims)
}
return "", TokenInvalid
}
````
最后一步就是写个验证token 的中间件
```go
// JWTAuth 中间件,检查token
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Request.Header.Get("token")
if token == "" {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "请求未携带token,无权限访问",
})
c.Abort()
return
}
log.Print("get token: ", token)
j := NewJWT()
// parseToken 解析token包含的信息
claims, err := j.ParseToken(token)
if err != nil {
if err == TokenExpired {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "授权已过期",
})
c.Abort()
return
}
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": err.Error(),
})
c.Abort()
return
}
// 继续交由下一个路由处理,并将解析出的信息传递下去
c.Set("claims", claims)
}
}
```
然后搞model吧。首先弄个员工的model,员工肯定有增删改和查找,就简单先弄个增加和查找。有了查找在登录的时候可以直接通过用户名验证用户是否存在然后在进一步验证。
```go
const (
db = "leaseapp"
collection = "EmployeeModel"
)
type Employee struct {
ID bson.ObjectId `json:"id" bson:"_id"`
Phone string `json:"phone" bson:"phone"`
Password string `json:"-" bson:"passoword"`
EmpName string `json:"empname" bson:"empname"`
Role string `json:"role" bson:"role"`
}
//新增用户
func (e *Employee) AddEmployee(emp Employee) error {
return mongoose.Insert(db, collection, emp)
}
//查找用户
func (e *Employee) FindUserByPhone(phone string) (Employee, error) {
var result Employee
err := mongoose.FindOne(db, collection, bson.M{
"phone": phone,
}, nil, &result)
return result, err
}
```
这里就差不多了。然后我们可以把得到的员工模型的数据塞进claims里面生成我们的token。然后登陆成功后我们返回一个token 和员工的一些信息
```go
type LoginResult struct {
Token string `json:"token"`
model.Employee
}
//生成token
func GenerateToken(c *gin.Context, emp model.Employee) {
j := &jwt.JWT{
[]byte("martin"),
}
claims := jwt.Customclaims{
emp.EmpName,
emp.Phone,
emp.Role,
jwtgo.StandardClaims{
NotBefore: int64(time.Now().Unix() - 1000), //签名生效时间
ExpiresAt: int64(time.Now().Unix() + 3600), //签名过期时间 一小时
Issuer: "martin", //签名发行者
},
}
token, err := j.CreateToken(claims)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": err.Error(),
})
}
log.Println(token)
data := LoginResult{
Token: token,
Employee: emp,
}
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "登录成功",
"data": data,
})
return
}
```
差不多了。在写api的部分。负责添加员工 和登陆验证,首先得加个员工,不然怎么登陆。这里用bcrypt这个包来加密。注意的是加密后的类型是[]byte类型。mongodb是不支持这个类型的,所以要转一下string。后面再我们登录验证的时候也要注意这个问题就好了
```go
func AddEmployee(c *gin.Context) {
phonenum := c.PostForm("phone")
pwd := c.PostForm("password")
ename := c.PostForm("empname")
emprole := c.PostForm("role")
exit := mongoose.IsExist(db, collection, bson.M{"phone": phonenum})
if exit {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"msg": "用户已存在",
})
} else {
hashpwd, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
if err != nil {
log.Fatalln(err)
}
hashedpwd := string(hashpwd)
employee := model.Employee{
ID: bson.NewObjectId(),
Phone: phonenum,
Password: hashedpwd,
EmpName: ename,
Role: emprole,
}
err = dao.AddEmployee(employee)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"msg": "保存失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"msg": "保存成功",
})
}
}
```
然后添加完了 就可以登陆了。路由就很简单就不写了。后面会贴用完中间件和分组后的路由。写一个处理登录的函数,当用户名和密码都验证成功后我们把数据添加到generatetoken函数里生成token,
```go
func Login(c *gin.Context) {
phonenum := c.PostForm("phone")
pwd := c.PostForm("password")
emp := model.Employee{}
exist := mongoose.IsExist(db, collection, bson.M{"phone": phonenum})
if exist {
result, err := emp.FindUserByPhone(phonenum)
err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(pwd))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"msg": "用户名或密码错误",
})
return
}
gwt.GenerateToken(c, result)
} else {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "登录失败",
})
}
}
```
好像很长了。在开一篇把。。