Skip to content

Commit

Permalink
初始化项目
Browse files Browse the repository at this point in the history
  • Loading branch information
Away0x committed May 1, 2019
0 parents commit 892a549
Show file tree
Hide file tree
Showing 138 changed files with 40,265 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.yml]
indent_size = 2
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.js linguist-language=go
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.DS_Store
# ide
/.idea
/.vscode

/tmp
/storage/fresh_tmp
/storage/logs
/node_modules
npm-debug.log
yarn-error.log
config.yaml
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
APP_NAME = "gin_bbs"

default:
go build -o ${APP_NAME}
# env GOOS=linux GOARCH=amd64 go build -o ${APP_NAME}

install:
go mod download

dev:
fresh -c ./fresh.conf

mock:
go run ./main.go -m

clean:
if [ -f ${APP_NAME} ]; then rm ${APP_NAME}; fi

help:
@echo "make - compile the source code"
@echo "make install - install dep"
@echo "make dev - run go fresh"
@echo "make mock - mock data"
@echo "make clean - remove binary file"
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Gin Weibo App
参考 [L01 Laravel 教程 - Web 开发实战入门](https://learnku.com/courses/laravel-essential-training/5.8),使用 Golang (Gin、Gorm) 改写。

项目结构和风格模仿 Laravel。

## 项目目录结构
<details>
<summary>展开查看</summary>
<pre><code>
├── app 项目核心逻辑代码
│ ├── auth 用户相关
│ ├── controllers 控制器
│ ├── helpers 工具方法
│ ├── models 模型
│ ├── policies 权限校验
│ ├── requests 参数校验
│ ├── services 复杂查询
│ └── view_models 数据转换
├── config 配置中心
├── database 数据库
│ └── factory 数据 mock
├── middleware 中间件
│ └── wrapper controller 包裹函数
├── pkg 项目依赖
├── public 项目静态文件
├── resources 前端源码
│ └── view go 模板文件
├── routes 路由
│ └── routes.go 路由注册
│ └── api.go api 路由注册
│ └── web.go 页面路由注册
│ └── named 命名路由模块
├── storage 存放日志等文件
├── main.go 项目入口
├── config.yaml 项目配置
├── deploy.sh 部署脚本
└── Makefile Makefile 文件
</code></pre>
</details>

## 实现功能
- [x] CSRF 验证
- [x] flash 消息闪现
- [x] 记忆上次表单提交的数据
- [x] 参数校验模块
- [x] 命名路由
- [x] 分页
- [x] 发送邮件
- [x] 用户权限模块
- [x] 日志
- [x] 前端构建 (typescript、sass ...)

![1](readme/1.png)

![2](readme/2.png)

![3](readme/3.png)

![4](readme/4.png)

![5](readme/5.png)

![6](readme/6.png)

![7](readme/7.png)

![8](readme/8.png)

![9](readme/9.png)

***
54 changes: 54 additions & 0 deletions app/auth/current_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package auth

import (
"errors"
userModel "gin_weibo/app/models/user"
"gin_weibo/config"

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

// SaveCurrentUserToContext : 保存用户数据到 context 中
func SaveCurrentUserToContext(c *gin.Context) {
user, err := getCurrentUserFromSession(c)
if err != nil {
return
}

c.Keys[config.AppConfig.ContextCurrentUserDataKey] = user
}

// GetCurrentUserFromContext : 从 context 中获取用户模型
func GetCurrentUserFromContext(c *gin.Context) (*userModel.User, error) {
err := errors.New("没有获取到用户数据")
userDataFromContext := c.Keys[config.AppConfig.ContextCurrentUserDataKey]
if userDataFromContext == nil {
return nil, err
}

user, ok := userDataFromContext.(*userModel.User)
if !ok {
return nil, err
}

return user, nil
}

// GetUserFromContextOrDataBase : 从 context 或者从数据库中获取用户模型
func GetUserFromContextOrDataBase(c *gin.Context, id int) (*userModel.User, error) {
// 当前用户存在并且就是想要获取的那个用户
currentUser, err := GetCurrentUserFromContext(c)
if currentUser != nil && err == nil {
if int(currentUser.ID) == id {
return currentUser, nil
}
}

// 获取的是其他指定 id 的用户
otherUser, err := userModel.Get(id)
if err != nil {
return nil, err
}

return otherUser, nil
}
97 changes: 97 additions & 0 deletions app/auth/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package auth

import (
"errors"
userModel "gin_weibo/app/models/user"
"gin_weibo/config"
"gin_weibo/pkg/session"
"gin_weibo/pkg/utils"
"net/url"
"strconv"

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

const (
rememberFormKey = "remember" // 表单提交时的 form name
rememberCookieName = "remember_me" // 存在 cookie 中的 key name
rememberMaxAge = 88888888 // 过期时间
)

// Login 登录
func Login(c *gin.Context, u *userModel.User) {
session.SetSession(c, config.AppConfig.AuthSessionKey, u.GetIDstring())
// 记住我
setRememberTokenInCookie(c, u)
}

// Logout 登出
func Logout(c *gin.Context) {
session.DeleteSession(c, config.AppConfig.AuthSessionKey)
delRememberToken(c)
}

// -------------- private --------------
// getCurrentUserFromSession : 从 session 中获取用户
func getCurrentUserFromSession(c *gin.Context) (*userModel.User, error) {
// 从 cookie 中获取 remember me token (如有则自动登录)
rememberMeToken := getRememberTokenFromCookie(c)
if rememberMeToken != "" {
if user, err := userModel.GetByRememberToken(rememberMeToken); err == nil {
Login(c, user)
return user, nil
}
delRememberToken(c)
}

// 从 session 中获取用户 id
idStr := session.GetSession(c, config.AppConfig.AuthSessionKey)
if idStr == "" {
return nil, errors.New("没有获取到 session")
}

id, err := strconv.Atoi(idStr)
if err != nil {
return nil, err
}

user, err := userModel.Get(id)
if err != nil {
return nil, err
}

return user, nil
}

// -------------- 记住我功能 utils --------------
func setRememberTokenInCookie(c *gin.Context, u *userModel.User) {
// 记住我 (如果 登录的 PostForm 中有着 remember="on" 说明开启记住我功能)
rememberMe := c.PostForm(rememberFormKey) == "on"
if !rememberMe {
return
}

// 更新用户的 RememberToken
newToken := string(utils.RandomCreateBytes(10))
u.RememberToken = newToken
if err := u.Update(false); err != nil {
return
}

// 写入 cookie
c.SetCookie(rememberCookieName, u.RememberToken, rememberMaxAge, "/", "", false, true)
}

func getRememberTokenFromCookie(c *gin.Context) string {
if cookie, err := c.Request.Cookie(rememberCookieName); err == nil {
if v, err := url.QueryUnescape(cookie.Value); err == nil {
return v
}
}

return ""
}

func delRememberToken(c *gin.Context) {
c.SetCookie(rememberCookieName, "", -1, "/", "", false, true)
}
2 changes: 2 additions & 0 deletions app/controllers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- `resource.go`: 中定义 `create``store``index``show``edit``update``destroy` 系列 controller
- `xx__.go`: 中定义只在当前 `controller package` 中使用的工具方法
64 changes: 64 additions & 0 deletions app/controllers/followers/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package followers

import (
"gin_weibo/app/controllers"
followerModel "gin_weibo/app/models/follower"
userModel "gin_weibo/app/models/user"
"gin_weibo/app/policies"
"gin_weibo/pkg/flash"
"gin_weibo/routes/named"

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

// Store 关注用户
func Store(c *gin.Context, currentUser *userModel.User) {
id, err := controllers.GetIntParam(c, "id")
if err != nil {
controllers.Render404(c)
return
}

if ok := policies.UserPolicyFollow(c, currentUser, id); !ok {
return
}

isFollowing := false
if id != int(currentUser.ID) {
isFollowing = followerModel.IsFollowing(int(currentUser.ID), id)
}

if !isFollowing {
if err := followerModel.DoFollow(currentUser.ID, uint(id)); err != nil {
flash.NewDangerFlash(c, "关注失败: "+err.Error())
}
}

controllers.Redirect(c, named.G("users.show", id)+"?page=1", false)
}

// Destroy 取消关注用户
func Destroy(c *gin.Context, currentUser *userModel.User) {
id, err := controllers.GetIntParam(c, "id")
if err != nil {
controllers.Render404(c)
return
}

if ok := policies.UserPolicyFollow(c, currentUser, id); !ok {
return
}

isFollowing := false
if id != int(currentUser.ID) {
isFollowing = followerModel.IsFollowing(int(currentUser.ID), id)
}

if isFollowing {
if err := followerModel.DoUnFollow(currentUser.ID, uint(id)); err != nil {
flash.NewDangerFlash(c, "取消关注失败: "+err.Error())
}
}

controllers.Redirect(c, named.G("users.show", id)+"?page=1", false)
}
18 changes: 18 additions & 0 deletions app/controllers/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package controllers

import (
"strconv"

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

// GetIntParam : 从 path params 中获取 int 参数
// http://a.com/xx/1 => 获取到 int 1
func GetIntParam(c *gin.Context, key string) (int, error) {
i, err := strconv.Atoi(c.Param(key))
if err != nil {
return 0, err
}

return i, nil
}
Loading

0 comments on commit 892a549

Please sign in to comment.