• 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

    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. // Unfollow func
    12. func (u *User) Unfollow(username string) error {
    13. other, err := GetUserByUsername(username)
    14. if err != nil {
    15. return err
    16. }
    17. return db.Model(other).Association("Followers").Delete(u).Error
    18. }
    19. // FollowSelf func
    20. func (u *User) FollowSelf() error {
    21. return db.Model(u).Association("Followers").Append(u).Error
    22. }
    23. // FollowersCount func
    24. func (u *User) FollowersCount() int {
    25. return db.Model(u).Association("Followers").Count()
    26. }
    27. // FollowingIDs func
    28. func (u *User) FollowingIDs() []int {
    29. var ids []int
    30. rows, err := db.Table("follower").Where("follower_id = ?", u.ID).Select("user_id, follower_id").Rows()
    31. if err != nil {
    32. log.Println("Counting Following error:", err)
    33. return ids
    34. }
    35. defer rows.Close()
    36. for rows.Next() {
    37. var id, followerID int
    38. rows.Scan(&id, &followerID)
    39. ids = append(ids, id)
    40. }
    41. return ids
    42. }
    43. // FollowingCount func
    44. func (u *User) FollowingCount() int {
    45. ids := u.FollowingIDs()
    46. return len(ids)
    47. }
    48. // FollowingPosts func
    49. func (u *User) FollowingPosts() (*[]Post, error) {
    50. var posts []Post
    51. ids := u.FollowingIDs()
    52. if err := db.Preload("User").Order("timestamp desc").Where("user_id in (?)", ids).Find(&posts).Error; err != nil {
    53. return nil, err
    54. }
    55. return &posts, nil
    56. }
    57. // IsFollowedByUser func
    58. func (u *User) IsFollowedByUser(username string) bool {
    59. user, _ := GetUserByUsername(username)
    60. ids := user.FollowingIDs()
    61. for _, id := range ids {
    62. if u.ID == id {
    63. return true
    64. }
    65. }
    66. return false
    67. }
    68. // CreatePost func
    69. func (u *User) CreatePost(body string) error {
    70. post := Post{Body: body, UserID: u.ID}
    71. return db.Create(&post).Error
    72. }
    73. ...

    然后在 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. }

    由于我们一开始的建立用户,没有加入 Follower 功能,我们重新初始化一遍数据

    cmd/init_db\main.go

    1. package main
    2. import (
    3. "log"
    4. "github.com/bonfy/go-mega-code/model"
    5. _ "github.com/jinzhu/gorm/dialects/mysql"
    6. )
    7. func main() {
    8. log.Println("DB Init ...")
    9. db := model.ConnectToDB()
    10. defer db.Close()
    11. model.SetDB(db)
    12. db.DropTableIfExists(model.User{}, model.Post{}, "follower")
    13. db.CreateTable(model.User{}, model.Post{})
    14. model.AddUser("bonfy", "abc123", "i@bonfy.im")
    15. model.AddUser("rene", "abc123", "rene@test.com")
    16. u1, _ := model.GetUserByUsername("bonfy")
    17. u1.CreatePost("Beautiful day in Portland!")
    18. model.UpdateAboutMe(u1.Username, `I'm the author of Go-Mega Tutorial you are reading now!`)
    19. u2, _ := model.GetUserByUsername("rene")
    20. u2.CreatePost("The Avengers movie was so cool!")
    21. u2.CreatePost("Sun shine is beautiful")
    22. u1.Follow(u2.Username)
    23. }

    执行

    1. $ go run cmd/db_init/main.go

    然后查看 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. func (h home) registerRoutes() {
    3. r := mux.NewRouter()
    4. r.HandleFunc("/logout", middleAuth(logoutHandler))
    5. r.HandleFunc("/login", loginHandler)
    6. r.HandleFunc("/register", registerHandler)
    7. r.HandleFunc("/user/{username}", middleAuth(profileHandler))
    8. r.HandleFunc("/follow/{username}", middleAuth(followHandler))
    9. r.HandleFunc("/unfollow/{username}", middleAuth(unFollowHandler))
    10. r.HandleFunc("/profile_edit", middleAuth(profileEditHandler))
    11. r.HandleFunc("/", middleAuth(indexHandler))
    12. http.Handle("/", r)
    13. }
    14. ...
    15. func followHandler(w http.ResponseWriter, r *http.Request) {
    16. vars := mux.Vars(r)
    17. pUser := vars["username"]
    18. sUser, _ := getSessionUser(r)
    19. err := vm.Follow(sUser, pUser)
    20. if err != nil {
    21. log.Println("Follow error:", err)
    22. w.Write([]byte("Error in Follow"))
    23. return
    24. }
    25. http.Redirect(w, r, fmt.Sprintf("/user/%s", pUser), http.StatusSeeOther)
    26. }
    27. func unFollowHandler(w http.ResponseWriter, r *http.Request) {
    28. vars := mux.Vars(r)
    29. pUser := vars["username"]
    30. sUser, _ := getSessionUser(r)
    31. err := vm.UnFollow(sUser, pUser)
    32. if err != nil {
    33. log.Println("UnFollow error:", err)
    34. w.Write([]byte("Error in UnFollow"))
    35. return
    36. }
    37. http.Redirect(w, r, fmt.Sprintf("/user/%s", pUser), http.StatusSeeOther)
    38. }

    FollowUnfollow之后通过 Redirect 回到原来的页面

    完善Profile页面

    vm/profile.go

    1. package vm
    2. import "github.com/bonfy/go-mega-code/model"
    3. // ProfileViewModel struct
    4. type ProfileViewModel struct {
    5. BaseViewModel
    6. Posts []model.Post
    7. Editable bool
    8. IsFollow bool
    9. FollowersCount int
    10. FollowingCount int
    11. ProfileUser model.User
    12. }
    13. // ProfileViewModelOp struct
    14. type ProfileViewModelOp struct{}
    15. // GetVM func
    16. func (ProfileViewModelOp) GetVM(sUser, pUser string) (ProfileViewModel, error) {
    17. v := ProfileViewModel{}
    18. v.SetTitle("Profile")
    19. u, err := model.GetUserByUsername(pUser)
    20. if err != nil {
    21. return v, err
    22. }
    23. posts, _ := model.GetPostsByUserID(u.ID)
    24. v.ProfileUser = *u
    25. v.Editable = (sUser == pUser)
    26. if !v.Editable {
    27. v.IsFollow = u.IsFollowedByUser(sUser)
    28. }
    29. v.FollowersCount = u.FollowersCount()
    30. v.FollowingCount = u.FollowingCount()
    31. v.Posts = *posts
    32. v.SetCurrentUser(sUser)
    33. return v, nil
    34. }
    35. // Follow func : A follow B
    36. func Follow(a, b string) error {
    37. u, err := model.GetUserByUsername(a)
    38. if err != nil {
    39. return err
    40. }
    41. return u.Follow(b)
    42. }
    43. // UnFollow func : A unfollow B
    44. func UnFollow(a, b string) error {
    45. u, err := model.GetUserByUsername(a)
    46. if err != nil {
    47. return err
    48. }
    49. return u.Unfollow(b)
    50. }

    templates/content/profile.html

    1. ...
    2. {{if .ProfileUser.LastSeen}}
    3. <p>Last seen on: {{ .ProfileUser.LastSeen }}</p>
    4. {{end}}
    5. <p>{{ .FollowersCount }} followers, {{ .FollowingCount }} following.</p>
    6. {{if .Editable}}
    7. <p><a href="/profile_edit">Edit your profile</a></p>
    8. {{else}}
    9. {{if .IsFollow}}
    10. <p><a href="/unfollow/{{.ProfileUser.Username}}">Unfollow</a></p>
    11. {{else}}
    12. <p><a href="/follow/{{.ProfileUser.Username}}">Follow</a></p>
    13. {{end}}
    14. {{end}}
    15. ...

    运行

    1. $ go run main.go

    08-02

    说明: 这个是通过 rene 登陆之后访问 http://127.0.0.1:8888/user/bonfy 查看 bonfy 的profile, 由于 rene 没有 follow bonfy,可以点击 Follow 按钮,实现Follow操作

    本小节 Diff

    • 目录
    • 上一节: 07-Profile-Page-And-Avatar
    • 下一节: 09-Pagination