|
|
|
// Copyright 2022 ROC. All rights reserved.
|
|
|
|
// Use of this source code is governed by a MIT style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package broker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/gofrs/uuid"
|
|
|
|
"github.com/rocboss/paopao-ce/internal/conf"
|
|
|
|
"github.com/rocboss/paopao-ce/internal/core"
|
|
|
|
"github.com/rocboss/paopao-ce/pkg/convert"
|
|
|
|
"github.com/rocboss/paopao-ce/pkg/errcode"
|
|
|
|
"github.com/rocboss/paopao-ce/pkg/utils"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
const _MaxCaptchaTimes = 2
|
|
|
|
|
|
|
|
type PhoneCaptchaReq struct {
|
|
|
|
Phone string `json:"phone" form:"phone" binding:"required"`
|
|
|
|
ImgCaptcha string `json:"img_captcha" form:"img_captcha" binding:"required"`
|
|
|
|
ImgCaptchaID string `json:"img_captcha_id" form:"img_captcha_id" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type UserPhoneBindReq struct {
|
|
|
|
Phone string `json:"phone" form:"phone" binding:"required"`
|
|
|
|
Captcha string `json:"captcha" form:"captcha" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type AuthRequest struct {
|
|
|
|
Username string `json:"username" form:"username" binding:"required"`
|
|
|
|
Password string `json:"password" form:"password" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type RegisterRequest struct {
|
|
|
|
Username string `json:"username" form:"username" binding:"required"`
|
|
|
|
Password string `json:"password" form:"password" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChangePasswordReq struct {
|
|
|
|
Password string `json:"password" form:"password" binding:"required"`
|
|
|
|
OldPassword string `json:"old_password" form:"old_password" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChangeNicknameReq struct {
|
|
|
|
Nickname string `json:"nickname" form:"nickname" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChangeAvatarReq struct {
|
|
|
|
Avatar string `json:"avatar" form:"avatar" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ChangeUserStatusReq struct {
|
|
|
|
ID int64 `json:"id" form:"id" binding:"required"`
|
|
|
|
Status int `json:"status" form:"status" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type RequestingFriendReq struct {
|
|
|
|
UserId int64 `json:"user_id" binding:"required"`
|
|
|
|
Greetings string `json:"greetings" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type AddFriendReq struct {
|
|
|
|
UserId int64 `json:"user_id" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type RejectFriendReq struct {
|
|
|
|
UserId int64 `json:"user_id" binding:"required"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type DeleteFriendReq struct {
|
|
|
|
UserId int64 `json:"user_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type UserProfileResp struct {
|
|
|
|
ID int64 `json:"id"`
|
|
|
|
Nickname string `json:"nickname"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Status int `json:"status"`
|
|
|
|
Avatar string `json:"avatar"`
|
|
|
|
IsAdmin bool `json:"is_admin"`
|
|
|
|
IsFriend bool `json:"is_friend"`
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
_LoginErrKey = "PaoPaoUserLoginErr"
|
|
|
|
_MaxLoginErrTimes = 10
|
|
|
|
)
|
|
|
|
|
|
|
|
// DoLogin 用户认证
|
|
|
|
func DoLogin(ctx *gin.Context, param *AuthRequest) (*core.User, error) {
|
|
|
|
user, err := ds.GetUserByUsername(param.Username)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errcode.UnauthorizedAuthNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
if user.Model != nil && user.ID > 0 {
|
|
|
|
if errTimes, err := conf.Redis.Get(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID)).Result(); err == nil {
|
|
|
|
if convert.StrTo(errTimes).MustInt() >= _MaxLoginErrTimes {
|
|
|
|
return nil, errcode.TooManyLoginError
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 对比密码是否正确
|
|
|
|
if ValidPassword(user.Password, param.Password, user.Salt) {
|
|
|
|
|
|
|
|
if user.Status == core.UserStatusClosed {
|
|
|
|
return nil, errcode.UserHasBeenBanned
|
|
|
|
}
|
|
|
|
|
|
|
|
// 清空登录计数
|
|
|
|
conf.Redis.Del(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID))
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 登录错误计数
|
|
|
|
_, err = conf.Redis.Incr(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID)).Result()
|
|
|
|
if err == nil {
|
|
|
|
conf.Redis.Expire(ctx, fmt.Sprintf("%s:%d", _LoginErrKey, user.ID), time.Hour).Result()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errcode.UnauthorizedAuthFailed
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errcode.UnauthorizedAuthNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidPassword 检查密码是否一致
|
|
|
|
func ValidPassword(dbPassword, password, salt string) bool {
|
|
|
|
return strings.Compare(dbPassword, utils.EncodeMD5(utils.EncodeMD5(password)+salt)) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckStatus 检测用户权限
|
|
|
|
func CheckStatus(user *core.User) bool {
|
|
|
|
return user.Status == core.UserStatusNormal
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidUsername 验证用户
|
|
|
|
func ValidUsername(username string) error {
|
|
|
|
// 检测用户是否合规
|
|
|
|
if utf8.RuneCountInString(username) < 3 || utf8.RuneCountInString(username) > 12 {
|
|
|
|
return errcode.UsernameLengthLimit
|
|
|
|
}
|
|
|
|
|
|
|
|
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(username) {
|
|
|
|
return errcode.UsernameCharLimit
|
|
|
|
}
|
|
|
|
|
|
|
|
// 重复检查
|
|
|
|
user, _ := ds.GetUserByUsername(username)
|
|
|
|
|
|
|
|
if user.Model != nil && user.ID > 0 {
|
|
|
|
return errcode.UsernameHasExisted
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckPassword 密码检查
|
|
|
|
func CheckPassword(password string) error {
|
|
|
|
// 检测用户是否合规
|
|
|
|
if utf8.RuneCountInString(password) < 6 || utf8.RuneCountInString(password) > 16 {
|
|
|
|
return errcode.PasswordLengthLimit
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckPhoneCaptcha 验证手机验证码
|
|
|
|
func CheckPhoneCaptcha(phone, captcha string) *errcode.Error {
|
|
|
|
// 如果禁止phone verify 则允许通过任意验证码
|
|
|
|
if DisablePhoneVerify {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := ds.GetLatestPhoneCaptcha(phone)
|
|
|
|
if err != nil {
|
|
|
|
return errcode.ErrorPhoneCaptcha
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Captcha != captcha {
|
|
|
|
return errcode.ErrorPhoneCaptcha
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.ExpiredOn < time.Now().Unix() {
|
|
|
|
return errcode.ErrorPhoneCaptcha
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.UseTimes >= _MaxCaptchaTimes {
|
|
|
|
return errcode.MaxPhoneCaptchaUseTimes
|
|
|
|
}
|
|
|
|
|
|
|
|
// 更新检测次数
|
|
|
|
ds.UsePhoneCaptcha(c)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckPhoneExist 检测手机号是否存在
|
|
|
|
func CheckPhoneExist(uid int64, phone string) bool {
|
|
|
|
u, err := ds.GetUserByPhone(phone)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if u.Model == nil || u.ID == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if u.ID == uid {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// EncryptPasswordAndSalt 密码加密&生成salt
|
|
|
|
func EncryptPasswordAndSalt(password string) (string, string) {
|
|
|
|
salt := uuid.Must(uuid.NewV4()).String()[:8]
|
|
|
|
password = utils.EncodeMD5(utils.EncodeMD5(password) + salt)
|
|
|
|
|
|
|
|
return password, salt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register 用户注册
|
|
|
|
func Register(username, password string) (*core.User, error) {
|
|
|
|
password, salt := EncryptPasswordAndSalt(password)
|
|
|
|
|
|
|
|
user := &core.User{
|
|
|
|
Nickname: username,
|
|
|
|
Username: username,
|
|
|
|
Password: password,
|
|
|
|
Avatar: GetRandomAvatar(),
|
|
|
|
Salt: salt,
|
|
|
|
Status: core.UserStatusNormal,
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := ds.CreateUser(user)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func RequestingFriend(user *core.User, param *RequestingFriendReq) error {
|
|
|
|
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
|
|
|
return errcode.NotExistFriendId
|
|
|
|
}
|
|
|
|
return ds.RequestingFriend(user.ID, param.UserId, param.Greetings)
|
|
|
|
}
|
|
|
|
|
|
|
|
func AddFriend(user *core.User, param *AddFriendReq) error {
|
|
|
|
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
|
|
|
return errcode.NotExistFriendId
|
|
|
|
}
|
|
|
|
return ds.AddFriend(user.ID, param.UserId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RejectFriend(user *core.User, param *RejectFriendReq) error {
|
|
|
|
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
|
|
|
return errcode.NotExistFriendId
|
|
|
|
}
|
|
|
|
return ds.RejectFriend(user.ID, param.UserId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteFriend(user *core.User, param *DeleteFriendReq) error {
|
|
|
|
if _, err := ds.GetUserByID(param.UserId); err != nil {
|
|
|
|
return errcode.NotExistFriendId
|
|
|
|
}
|
|
|
|
return ds.DeleteFriend(user.ID, param.UserId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetContacts(user *core.User, offset int, limit int) (*core.ContactList, error) {
|
|
|
|
return ds.GetContacts(user.ID, offset, limit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserInfo 获取用户信息
|
|
|
|
func GetUserInfo(param *AuthRequest) (*core.User, error) {
|
|
|
|
user, err := ds.GetUserByUsername(param.Username)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if user.Model != nil && user.ID > 0 {
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errcode.UnauthorizedAuthNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetUserByID(id int64) (*core.User, error) {
|
|
|
|
user, err := ds.GetUserByID(id)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if user.Model != nil && user.ID > 0 {
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errcode.NoExistUsername
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetUserByUsername(user *core.User, username string) (*UserProfileResp, error) {
|
|
|
|
other, err := ds.GetUserByUsername(username)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var resp *UserProfileResp
|
|
|
|
if other.Model != nil && other.ID > 0 {
|
|
|
|
resp = &UserProfileResp{
|
|
|
|
ID: other.ID,
|
|
|
|
Nickname: other.Nickname,
|
|
|
|
Username: other.Username,
|
|
|
|
Status: other.Status,
|
|
|
|
Avatar: other.Avatar,
|
|
|
|
IsAdmin: other.IsAdmin,
|
|
|
|
IsFriend: !(user == nil || user.ID == other.ID),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, errcode.NoExistUsername
|
|
|
|
}
|
|
|
|
|
|
|
|
if user != nil && user.ID != other.ID {
|
|
|
|
resp.IsFriend = ds.IsFriend(user.ID, other.ID)
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateUserInfo 更新用户信息
|
|
|
|
func UpdateUserInfo(user *core.User) *errcode.Error {
|
|
|
|
if err := ds.UpdateUser(user); err != nil {
|
|
|
|
return errcode.ServerError
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ChangeUserAvatar(user *core.User, avatar string) (err *errcode.Error) {
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
deleteOssObjects([]string{avatar})
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := ds.CheckAttachment(avatar); err != nil {
|
|
|
|
return errcode.InvalidParams
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := oss.PersistObject(oss.ObjectKey(avatar)); err != nil {
|
|
|
|
logrus.Errorf("service.ChangeUserAvatar persist object failed: %s", err)
|
|
|
|
return errcode.ServerError
|
|
|
|
}
|
|
|
|
|
|
|
|
user.Avatar = avatar
|
|
|
|
err = UpdateUserInfo(user)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserCollections 获取用户收藏列表
|
|
|
|
func GetUserCollections(userID int64, offset, limit int) ([]*core.PostFormated, int64, error) {
|
|
|
|
collections, err := ds.GetUserPostCollections(userID, offset, limit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
totalRows, err := ds.GetUserPostCollectionCount(userID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
var posts []*core.Post
|
|
|
|
for _, collection := range collections {
|
|
|
|
posts = append(posts, collection.Post)
|
|
|
|
}
|
|
|
|
postsFormated, err := ds.MergePosts(posts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return postsFormated, totalRows, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserStars 获取用户点赞列表
|
|
|
|
func GetUserStars(userID int64, offset, limit int) ([]*core.PostFormated, int64, error) {
|
|
|
|
stars, err := ds.GetUserPostStars(userID, offset, limit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
totalRows, err := ds.GetUserPostStarCount(userID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var posts []*core.Post
|
|
|
|
for _, star := range stars {
|
|
|
|
posts = append(posts, star.Post)
|
|
|
|
}
|
|
|
|
postsFormated, err := ds.MergePosts(posts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return postsFormated, totalRows, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserWalletBills 获取用户账单列表
|
|
|
|
func GetUserWalletBills(userID int64, offset, limit int) ([]*core.WalletStatement, int64, error) {
|
|
|
|
bills, err := ds.GetUserWalletBills(userID, offset, limit)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
totalRows, err := ds.GetUserWalletBillCount(userID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return bills, totalRows, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendPhoneCaptcha 发送短信验证码
|
|
|
|
func SendPhoneCaptcha(ctx *gin.Context, phone string) error {
|
|
|
|
|
|
|
|
err := ds.SendPhoneCaptcha(phone)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 写入计数缓存
|
|
|
|
conf.Redis.Incr(ctx, "PaoPaoSmsCaptcha:"+phone).Result()
|
|
|
|
|
|
|
|
currentTime := time.Now()
|
|
|
|
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
|
|
|
|
|
|
|
|
conf.Redis.Expire(ctx, "PaoPaoSmsCaptcha:"+phone, endTime.Sub(currentTime))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSuggestUsers 根据关键词获取用户推荐
|
|
|
|
func GetSuggestUsers(keyword string) ([]string, error) {
|
|
|
|
users, err := ds.GetUsersByKeyword(keyword)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
usernames := []string{}
|
|
|
|
for _, user := range users {
|
|
|
|
usernames = append(usernames, user.Username)
|
|
|
|
}
|
|
|
|
|
|
|
|
return usernames, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSuggestTags 根据关键词获取标签推荐
|
|
|
|
func GetSuggestTags(keyword string) ([]string, error) {
|
|
|
|
tags, err := ds.GetTagsByKeyword(keyword)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ts := []string{}
|
|
|
|
for _, t := range tags {
|
|
|
|
ts = append(ts, t.Tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsFriend(userId, friendId int64) bool {
|
|
|
|
return ds.IsFriend(userId, friendId)
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkPermision 检查是否拥有者或管理员
|
|
|
|
func checkPermision(user *core.User, targetUserId int64) *errcode.Error {
|
|
|
|
if user == nil || (user.ID != targetUserId && !user.IsAdmin) {
|
|
|
|
return errcode.NoPermission
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|