Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Chengka committed Sep 13, 2018
0 parents commit bfd645a
Show file tree
Hide file tree
Showing 21 changed files with 711 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
MYSQL_DSN="db_user@/db_name?charset=utf8&parseTime=True&loc=Local"
REDIS_ADDR="127.0.0.1:6379"
REDIS_PW=""
REDIS_DB=""
SESSION_SECRE=""
GIN_MODE="debug"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode/
.env
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Golang API范例项目

## 目的

本项目采用了一系列Golang中比较流行的组件,可以以本项目为基础快速搭建Restful Web API

## 特色

本项目已经整合了许多开发API所必要的组件:

1. Gin: 轻量级Web框架,自称路由速度是golang最快的
2. GORM: ORM工具。本项目需要配合Mysql使用
3. Gin-Session: Gin框架提供的Session操作工具
4. Go-Redis: Golang Redis客户端
5. godotenv: 开发环境下的环境变量工具,方便使用环境变量
6. Gin-Cors: Gin框架提供的跨域中间件
7. 自行实现了国际化i18n的一些基本功能

本项目已经预先实现了一些常用的代码方便参考和复用:

1. 创建了用户模型
2. 实现了```/api/v1/user/register```用户注册接口
3. 实现了```/api/v1/user/login```用户登录接口
4. 实现了```/api/v1/user/me```用户资料接口(需要登录后获取session)
5. 实现了```/api/v1/user/logout```用户登出接口(需要登录后获取session)

本项目已经预先创建了一系列文件夹划分出下列模块:

1. api文件夹就是MVC框架的controller,负责协调各部件完成任务
2. model文件夹负责存储数据库模型和数据库操作相关的代码
3. service负责处理比较复杂的业务,把业务代码模型化可以有效提高业务代码的质量(比如用户注册,充值,下单等)
4. serializer储存通用的json模型,把model得到的数据库模型转换成api需要的json对象
5. cache负责redis缓存相关的代码
6. auth权限控制文件夹
7. util一些通用的小工具
8. conf放一些静态存放的配置文件,其中locales内放置翻译相关的配置文件

## Godotenv

项目在启动的时候依赖以下环境变量,但是在也可以在项目根目录创建.env文件设置环境变量便于使用(建议开发环境使用)

```shell
MYSQL_DSN="db_user:db_password@/db_name?charset=utf8&parseTime=True&loc=Local" # Mysql连接地址
REDIS_ADDR="127.0.0.1:6379" # Redis端口和地址
REDIS_PW="" # Redis连接密码
REDIS_DB="" # Redis库从0到10
SESSION_SECRE="" # Seesion密钥,必须设置而且不要泄露
GIN_MODE="debug"
```

## Go 依赖

```shell
go get -u github.com/gin-gonic/gin
go get -u github.com/jinzhu/gorm
go get -u github.com/gin-contrib/cors
go get -u github.com/gin-contrib/sessions
go get -u github.com/joho/godotenv
go get -u github.com/go-redis/redis
```

## 运行

```shell
go run main.go
```

项目运行后启动在3000端口(可以修改,参考gin文档)
58 changes: 58 additions & 0 deletions api/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package api

import (
"encoding/json"
"fmt"
"gin_example/model"
"gin_example/serializer"
"gin_example/util"

"github.com/gin-gonic/gin"
validator "gopkg.in/go-playground/validator.v8"
)

// Ping 状态检查页面
func Ping(c *gin.Context) {
c.JSON(200, serializer.Response{
Status: 0,
Msg: "Pong",
})
}

// CurrentUser 获取当前用户
func CurrentUser(c *gin.Context) *model.User {
if user, _ := c.Get("user"); user != nil {
if u, ok := user.(*model.User); ok {
return u
}
}
return nil
}

// ErrorResponse 返回错误消息
func ErrorResponse(err error) serializer.Response {
if ve, ok := err.(validator.ValidationErrors); ok {
for _, e := range ve {
field := util.T(fmt.Sprintf("Field.%s", e.Field))
tag := util.T(fmt.Sprintf("Tag.Valid.%s", e.Tag))
return serializer.Response{
Status: 40001,
Msg: fmt.Sprintf("%s%s", field, tag),
Error: fmt.Sprint(err),
}
}
}
if _, ok := err.(*json.UnmarshalTypeError); ok {
return serializer.Response{
Status: 40001,
Msg: "JSON类型不匹配",
Error: fmt.Sprint(err),
}
}

return serializer.Response{
Status: 40001,
Msg: "参数错误",
Error: fmt.Sprint(err),
}
}
63 changes: 63 additions & 0 deletions api/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package api

import (
"gin_example/serializer"
"gin_example/service"

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

// UserRegister 用户注册接口
func UserRegister(c *gin.Context) {
var service service.UserRegisterService
if err := c.ShouldBind(&service); err == nil {
if user, err := service.Register(); err != nil {
c.JSON(200, err)
} else {
res := serializer.BuildUserResponse(user)
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}

// UserLogin 用户登录接口
func UserLogin(c *gin.Context) {
var service service.UserLoginService
if err := c.ShouldBind(&service); err == nil {
if user, err := service.Login(); err != nil {
c.JSON(200, err)
} else {
// 设置Session
s := sessions.Default(c)
s.Clear()
s.Set("user_id", user.ID)
s.Save()

res := serializer.BuildUserResponse(user)
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}

// UserMe 用户详情
func UserMe(c *gin.Context) {
user := CurrentUser(c)
res := serializer.BuildUserResponse(*user)
c.JSON(200, res)
}

// UserLogout 用户登出
func UserLogout(c *gin.Context) {
s := sessions.Default(c)
s.Clear()
s.Save()
c.JSON(200, serializer.Response{
Status: 0,
Msg: "登出成功",
})
}
Empty file added auth/.keep
Empty file.
35 changes: 35 additions & 0 deletions cache/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cache

import (
"os"
"strconv"

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

// RedisClient Redis缓存客户端单例
var RedisClient *redis.Client

// Redis 在中间件中初始化redis链接
func Redis() gin.HandlerFunc {
db, _ := strconv.ParseUint(os.Getenv("REDIS_DB"), 10, 64)
client := redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_ADDR"),
Password: os.Getenv("REDIS_PW"),
DB: int(db),
})

_, err := client.Ping().Result()

if err != nil {
panic(err)
}

RedisClient = client

return func(c *gin.Context) {
c.Set("RedisClient", client)
c.Next()
}
}
11 changes: 11 additions & 0 deletions conf/locales/zh-cn.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Tag:
required: "必须存在,而且不能为空"
min: "不够长"
max: "太长"
Field:
Name: "名称"
Nickname: "用户昵称"
UserName: "用户名"
Password: "密码"
PasswordConfirm: "密码校验"

54 changes: 54 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"gin_example/api"
"gin_example/cache"
"gin_example/middleware"
"gin_example/model"
"gin_example/util"
"os"

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

func main() {
godotenv.Load()

// 从配置文件读取配置
if err := util.LoadLocales("conf/locales/zh-cn.yaml"); err != nil {
panic(err)
}

r := gin.Default()

// 中间件, 顺序不能改
r.Use(model.Database(os.Getenv("MYSQL_DSN")))
r.Use(cache.Redis())
r.Use(middleware.Session(os.Getenv("SESSION_SECRET")))
r.Use(middleware.Cors())
r.Use(middleware.CurrentUser())

// 路由
v1 := r.Group("/api/v1")
{
v1.POST("ping", api.Ping)

// 用户登录
v1.POST("user/register", api.UserRegister)

// 用户登录
v1.POST("user/login", api.UserLogin)

// 需要登录保护的
v1.Use(middleware.AuthRequired())
{
// User Routing
v1.GET("user/me", api.UserMe)
v1.DELETE("user/logout", api.UserLogout)
}
}

// 监听
r.Run(":3000")
}
42 changes: 42 additions & 0 deletions middleware/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package middleware

import (
"gin_example/model"
"gin_example/serializer"

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

// CurrentUser 获取登录用户
func CurrentUser() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
uid := session.Get("user_id")
if uid != nil {
user, err := model.GetUser(uid)
if err == nil {
c.Set("user", &user)
}
}
c.Next()
}
}

// AuthRequired 需要登录
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if user, _ := c.Get("user"); user != nil {
if _, ok := user.(*model.User); ok {
c.Next()
return
}
}

c.JSON(200, serializer.Response{
Status: 401,
Msg: "需要登录",
})
c.Abort()
}
}
16 changes: 16 additions & 0 deletions middleware/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package middleware

import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)

// Cors 跨域配置
func Cors() gin.HandlerFunc {
config := cors.DefaultConfig()
config.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}
config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Cookie"}
config.AllowOrigins = []string{"http://www.example.com"}
config.AllowCredentials = true
return cors.New(config)
}
15 changes: 15 additions & 0 deletions middleware/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package middleware

import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)

// Session 初始化session
func Session(secret string) gin.HandlerFunc {
store := cookie.NewStore([]byte(secret))
//Also set Secure: true if using SSL, you should though
store.Options(sessions.Options{HttpOnly: true, MaxAge: 7 * 86400, Path: "/"})
return sessions.Sessions("gin-session", store)
}
Loading

0 comments on commit bfd645a

Please sign in to comment.