- 08-Follower
- 完善用户模型
- 完善页面显示
- 增加 Follow and Unfollow 操作
- 完善Profile页面
- Links
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
...// Follow func// follow someone usr_id other.id follow_id u.idfunc (u *User) Follow(username string) error {other, err := GetUserByUsername(username)if err != nil {return err}return db.Model(other).Association("Followers").Append(u).Error}// Unfollow funcfunc (u *User) Unfollow(username string) error {other, err := GetUserByUsername(username)if err != nil {return err}return db.Model(other).Association("Followers").Delete(u).Error}// FollowSelf funcfunc (u *User) FollowSelf() error {return db.Model(u).Association("Followers").Append(u).Error}// FollowersCount funcfunc (u *User) FollowersCount() int {return db.Model(u).Association("Followers").Count()}// FollowingIDs funcfunc (u *User) FollowingIDs() []int {var ids []introws, err := db.Table("follower").Where("follower_id = ?", u.ID).Select("user_id, follower_id").Rows()if err != nil {log.Println("Counting Following error:", err)return ids}defer rows.Close()for rows.Next() {var id, followerID introws.Scan(&id, &followerID)ids = append(ids, id)}return ids}// FollowingCount funcfunc (u *User) FollowingCount() int {ids := u.FollowingIDs()return len(ids)}// FollowingPosts funcfunc (u *User) FollowingPosts() (*[]Post, error) {var posts []Postids := u.FollowingIDs()if err := db.Preload("User").Order("timestamp desc").Where("user_id in (?)", ids).Find(&posts).Error; err != nil {return nil, err}return &posts, nil}// IsFollowedByUser funcfunc (u *User) IsFollowedByUser(username string) bool {user, _ := GetUserByUsername(username)ids := user.FollowingIDs()for _, id := range ids {if u.ID == id {return true}}return false}// CreatePost funcfunc (u *User) CreatePost(body string) error {post := Post{Body: body, UserID: u.ID}return db.Create(&post).Error}...
然后在 AddUser 的时候加入 FollowSelf, 即自己关注自己,方便在显示 Profile 页的时候,展示 FollowingPosts 将自己的 Post 也列进去
model/user.go
// AddUser funcfunc AddUser(username, password, email string) error {user := User{Username: username, Email: email}user.SetPassword(password)user.SetAvatar(email)if err := db.Create(&user).Error; err != nil {return err}return user.FollowSelf()}
由于我们一开始的建立用户,没有加入 Follower 功能,我们重新初始化一遍数据
cmd/init_db\main.go
package mainimport ("log""github.com/bonfy/go-mega-code/model"_ "github.com/jinzhu/gorm/dialects/mysql")func main() {log.Println("DB Init ...")db := model.ConnectToDB()defer db.Close()model.SetDB(db)db.DropTableIfExists(model.User{}, model.Post{}, "follower")db.CreateTable(model.User{}, model.Post{})model.AddUser("bonfy", "abc123", "i@bonfy.im")model.AddUser("rene", "abc123", "rene@test.com")u1, _ := model.GetUserByUsername("bonfy")u1.CreatePost("Beautiful day in Portland!")model.UpdateAboutMe(u1.Username, `I'm the author of Go-Mega Tutorial you are reading now!`)u2, _ := model.GetUserByUsername("rene")u2.CreatePost("The Avengers movie was so cool!")u2.CreatePost("Sun shine is beautiful")u1.Follow(u2.Username)}
执行
$ go run cmd/db_init/main.go
然后查看 follower 表,里面就有数据了

说明: 如上图 user_id 表示用户, follower_id 表示关注者, bonfy(id:1) 关注者 只有自己, 而 rene(id:2) 关注者 有两位, 这也是 u1.Follow(u2.Username) 的执行结果
本小节 Diff
完善页面显示
增加 Follow and Unfollow 操作
controller/home.go
...func (h home) registerRoutes() {r := mux.NewRouter()r.HandleFunc("/logout", middleAuth(logoutHandler))r.HandleFunc("/login", loginHandler)r.HandleFunc("/register", registerHandler)r.HandleFunc("/user/{username}", middleAuth(profileHandler))r.HandleFunc("/follow/{username}", middleAuth(followHandler))r.HandleFunc("/unfollow/{username}", middleAuth(unFollowHandler))r.HandleFunc("/profile_edit", middleAuth(profileEditHandler))r.HandleFunc("/", middleAuth(indexHandler))http.Handle("/", r)}...func followHandler(w http.ResponseWriter, r *http.Request) {vars := mux.Vars(r)pUser := vars["username"]sUser, _ := getSessionUser(r)err := vm.Follow(sUser, pUser)if err != nil {log.Println("Follow error:", err)w.Write([]byte("Error in Follow"))return}http.Redirect(w, r, fmt.Sprintf("/user/%s", pUser), http.StatusSeeOther)}func unFollowHandler(w http.ResponseWriter, r *http.Request) {vars := mux.Vars(r)pUser := vars["username"]sUser, _ := getSessionUser(r)err := vm.UnFollow(sUser, pUser)if err != nil {log.Println("UnFollow error:", err)w.Write([]byte("Error in UnFollow"))return}http.Redirect(w, r, fmt.Sprintf("/user/%s", pUser), http.StatusSeeOther)}
Follow 和 Unfollow之后通过 Redirect 回到原来的页面
完善Profile页面
vm/profile.go
package vmimport "github.com/bonfy/go-mega-code/model"// ProfileViewModel structtype ProfileViewModel struct {BaseViewModelPosts []model.PostEditable boolIsFollow boolFollowersCount intFollowingCount intProfileUser model.User}// ProfileViewModelOp structtype ProfileViewModelOp struct{}// GetVM funcfunc (ProfileViewModelOp) GetVM(sUser, pUser string) (ProfileViewModel, error) {v := ProfileViewModel{}v.SetTitle("Profile")u, err := model.GetUserByUsername(pUser)if err != nil {return v, err}posts, _ := model.GetPostsByUserID(u.ID)v.ProfileUser = *uv.Editable = (sUser == pUser)if !v.Editable {v.IsFollow = u.IsFollowedByUser(sUser)}v.FollowersCount = u.FollowersCount()v.FollowingCount = u.FollowingCount()v.Posts = *postsv.SetCurrentUser(sUser)return v, nil}// Follow func : A follow Bfunc Follow(a, b string) error {u, err := model.GetUserByUsername(a)if err != nil {return err}return u.Follow(b)}// UnFollow func : A unfollow Bfunc UnFollow(a, b string) error {u, err := model.GetUserByUsername(a)if err != nil {return err}return u.Unfollow(b)}
templates/content/profile.html
...{{if .ProfileUser.LastSeen}}<p>Last seen on: {{ .ProfileUser.LastSeen }}</p>{{end}}<p>{{ .FollowersCount }} followers, {{ .FollowingCount }} following.</p>{{if .Editable}}<p><a href="/profile_edit">Edit your profile</a></p>{{else}}{{if .IsFollow}}<p><a href="/unfollow/{{.ProfileUser.Username}}">Unfollow</a></p>{{else}}<p><a href="/follow/{{.ProfileUser.Username}}">Follow</a></p>{{end}}{{end}}...
运行
$ go run main.go

说明: 这个是通过 rene 登陆之后访问
http://127.0.0.1:8888/user/bonfy查看 bonfy 的profile, 由于 rene 没有 follow bonfy,可以点击Follow按钮,实现Follow操作本小节 Diff
Links
- 目录
- 上一节: 07-Profile-Page-And-Avatar
- 下一节: 09-Pagination
