08-Follower
这章将告诉你如何实现类似于Twitter和其他社交网络的“粉丝”功能。
在本章中,我将更多地使用应用的数据库。 我希望应用的用户能够轻松便捷地关注其他用户。
本章的GitHub链接为: Source, Diff, Zip

完善用户模型

首先我们列下要实现的用户操作:
    关注 - Follow
    取消关注 - UnFollow
    关注自己 - FollowSelf
    follower数量 - FollowersCount
    following数量 - FollowingCount
    关注的Posts - FollowingPosts 这个将被用在页面美化那章中的登陆后的IndexView显示
    判断是否被关注 - IsFollowedByUser
    (*)创建Post - CreatePost 这个是我们为了方便创建数据加入的,和粉丝操作关系不大
Tip: 这里 Followers 可以通过 Gorm 的 Association("Followers")来实现, Following 好像不支持(不确定,看了几遍文档没有找到),不过这里我们一顿骚操作,自己来实现一下嘛
model/user.go
1
...
2
// Follow func
3
// follow someone usr_id other.id follow_id u.id
4
func (u *User) Follow(username string) error {
5
other, err := GetUserByUsername(username)
6
if err != nil {
7
return err
8
}
9
return db.Model(other).Association("Followers").Append(u).Error
10
}
11
12
// Unfollow func
13
func (u *User) Unfollow(username string) error {
14
other, err := GetUserByUsername(username)
15
if err != nil {
16
return err
17
}
18
return db.Model(other).Association("Followers").Delete(u).Error
19
}
20
21
// FollowSelf func
22
func (u *User) FollowSelf() error {
23
return db.Model(u).Association("Followers").Append(u).Error
24
}
25
26
// FollowersCount func
27
func (u *User) FollowersCount() int {
28
return db.Model(u).Association("Followers").Count()
29
}
30
31
// FollowingIDs func
32
func (u *User) FollowingIDs() []int {
33
var ids []int
34
rows, err := db.Table("follower").Where("follower_id = ?", u.ID).Select("user_id, follower_id").Rows()
35
if err != nil {
36
log.Println("Counting Following error:", err)
37
return ids
38
}
39
defer rows.Close()
40
for rows.Next() {
41
var id, followerID int
42
rows.Scan(&id, &followerID)
43
ids = append(ids, id)
44
}
45
return ids
46
}
47
48
// FollowingCount func
49
func (u *User) FollowingCount() int {
50
ids := u.FollowingIDs()
51
return len(ids)
52
}
53
54
// FollowingPosts func
55
func (u *User) FollowingPosts() (*[]Post, error) {
56
var posts []Post
57
ids := u.FollowingIDs()
58
if err := db.Preload("User").Order("timestamp desc").Where("user_id in (?)", ids).Find(&posts).Error; err != nil {
59
return nil, err
60
}
61
return &posts, nil
62
}
63
64
// IsFollowedByUser func
65
func (u *User) IsFollowedByUser(username string) bool {
66
user, _ := GetUserByUsername(username)
67
ids := user.FollowingIDs()
68
for _, id := range ids {
69
if u.ID == id {
70
return true
71
}
72
}
73
return false
74
}
75
76
// CreatePost func
77
func (u *User) CreatePost(body string) error {
78
post := Post{Body: body, UserID: u.ID}
79
return db.Create(&post).Error
80
}
81
...
Copied!
然后在 AddUser 的时候加入 FollowSelf, 即自己关注自己,方便在显示 Profile 页的时候,展示 FollowingPosts 将自己的 Post 也列进去
model/user.go
1
// AddUser func
2
func AddUser(username, password, email string) error {
3
user := User{Username: username, Email: email}
4
user.SetPassword(password)
5
user.SetAvatar(email)
6
if err := db.Create(&user).Error; err != nil {
7
return err
8
}
9
return user.FollowSelf()
10
}
Copied!
由于我们一开始的建立用户,没有加入 Follower 功能,我们重新初始化一遍数据
cmd/init_db\main.go
1
package main
2
3
import (
4
"log"
5
6
"github.com/bonfy/go-mega-code/model"
7
_ "github.com/jinzhu/gorm/dialects/mysql"
8
)
9
10
func main() {
11
log.Println("DB Init ...")
12
db := model.ConnectToDB()
13
defer db.Close()
14
model.SetDB(db)
15
16
db.DropTableIfExists(model.User{}, model.Post{}, "follower")
17
db.CreateTable(model.User{}, model.Post{})
18
19
model.AddUser("bonfy", "abc123", "[email protected]")
20
model.AddUser("rene", "abc123", "[email protected]")
21
22
u1, _ := model.GetUserByUsername("bonfy")
23
u1.CreatePost("Beautiful day in Portland!")
24
model.UpdateAboutMe(u1.Username, `I'm the author of Go-Mega Tutorial you are reading now!`)
25
26
u2, _ := model.GetUserByUsername("rene")
27
u2.CreatePost("The Avengers movie was so cool!")
28
u2.CreatePost("Sun shine is beautiful")
29
30
u1.Follow(u2.Username)
31
}
Copied!
执行
1
$ go run cmd/db_init/main.go
Copied!
然后查看 follower 表,里面就有数据了
08-01
说明: 如上图 user_id 表示用户, follower_id 表示关注者, bonfy(id:1) 关注者 只有自己, 而 rene(id:2) 关注者 有两位, 这也是 u1.Follow(u2.Username) 的执行结果
本小节 Diff

完善页面显示

增加 Follow and Unfollow 操作

controller/home.go
1
...
2
3
func (h home) registerRoutes() {
4
r := mux.NewRouter()
5
r.HandleFunc("/logout", middleAuth(logoutHandler))
6
r.HandleFunc("/login", loginHandler)
7
r.HandleFunc("/register", registerHandler)
8
r.HandleFunc("/user/{username}", middleAuth(profileHandler))
9
r.HandleFunc("/follow/{username}", middleAuth(followHandler))
10
r.HandleFunc("/unfollow/{username}", middleAuth(unFollowHandler))
11
r.HandleFunc("/profile_edit", middleAuth(profileEditHandler))
12
r.HandleFunc("/", middleAuth(indexHandler))
13
14
http.Handle("/", r)
15
}
16
17
...
18
19
func followHandler(w http.ResponseWriter, r *http.Request) {
20
vars := mux.Vars(r)
21
pUser := vars["username"]
22
sUser, _ := getSessionUser(r)
23
24
err := vm.Follow(sUser, pUser)
25
if err != nil {
26
log.Println("Follow error:", err)
27
w.Write([]byte("Error in Follow"))
28
return
29
}
30
http.Redirect(w, r, fmt.Sprintf("/user/%s", pUser), http.StatusSeeOther)
31
}
32
33
func unFollowHandler(w http.ResponseWriter, r *http.Request) {
34
vars := mux.Vars(r)
35
pUser := vars["username"]
36
sUser, _ := getSessionUser(r)
37
38
err := vm.UnFollow(sUser, pUser)
39
if err != nil {
40
log.Println("UnFollow error:", err)
41
w.Write([]byte("Error in UnFollow"))
42
return
43
}
44
http.Redirect(w, r, fmt.Sprintf("/user/%s", pUser), http.StatusSeeOther)
45
}
Copied!
FollowUnfollow之后通过 Redirect 回到原来的页面

完善Profile页面

vm/profile.go
1
package vm
2
3
import "github.com/bonfy/go-mega-code/model"
4
5
// ProfileViewModel struct
6
type ProfileViewModel struct {
7
BaseViewModel
8
Posts []model.Post
9
Editable bool
10
IsFollow bool
11
FollowersCount int
12
FollowingCount int
13
ProfileUser model.User
14
}
15
16
// ProfileViewModelOp struct
17
type ProfileViewModelOp struct{}
18
19
// GetVM func
20
func (ProfileViewModelOp) GetVM(sUser, pUser string) (ProfileViewModel, error) {
21
v := ProfileViewModel{}
22
v.SetTitle("Profile")
23
u, err := model.GetUserByUsername(pUser)
24
if err != nil {
25
return v, err
26
}
27
posts, _ := model.GetPostsByUserID(u.ID)
28
v.ProfileUser = *u
29
v.Editable = (sUser == pUser)
30
31
if !v.Editable {
32
v.IsFollow = u.IsFollowedByUser(sUser)
33
}
34
v.FollowersCount = u.FollowersCount()
35
v.FollowingCount = u.FollowingCount()
36
37
v.Posts = *posts
38
v.SetCurrentUser(sUser)
39
return v, nil
40
}
41
42
// Follow func : A follow B
43
func Follow(a, b string) error {
44
u, err := model.GetUserByUsername(a)
45
if err != nil {
46
return err
47
}
48
return u.Follow(b)
49
}
50
51
// UnFollow func : A unfollow B
52
func UnFollow(a, b string) error {
53
u, err := model.GetUserByUsername(a)
54
if err != nil {
55
return err
56
}
57
return u.Unfollow(b)
58
}
Copied!
templates/content/profile.html
1
...
2
3
{{if .ProfileUser.LastSeen}}
4
<p>Last seen on: {{ .ProfileUser.LastSeen }}</p>
5
{{end}}
6
7
<p>{{ .FollowersCount }} followers, {{ .FollowingCount }} following.</p>
8
9
{{if .Editable}}
10
<p><a href="/profile_edit">Edit your profile</a></p>
11
{{else}}
12
{{if .IsFollow}}
13
<p><a href="/unfollow/{{.ProfileUser.Username}}">Unfollow</a></p>
14
{{else}}
15
<p><a href="/follow/{{.ProfileUser.Username}}">Follow</a></p>
16
{{end}}
17
{{end}}
18
...
Copied!
运行
1
$ go run main.go
Copied!
08-02
说明: 这个是通过 rene 登陆之后访问 http://127.0.0.1:8888/user/bonfy 查看 bonfy 的profile, 由于 rene 没有 follow bonfy,可以点击 Follow 按钮,实现Follow操作
本小节 Diff

Links