mirror of https://github.com/rocboss/paopao-ce
parent
8cb0b153ea
commit
d6cd619898
@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
__debug_bin
|
||||
config.yaml
|
||||
*.log
|
||||
paopao-api
|
@ -0,0 +1,27 @@
|
||||
# build app
|
||||
FROM golang AS build-env
|
||||
|
||||
ADD . /paopao-api
|
||||
|
||||
WORKDIR /paopao-api
|
||||
|
||||
RUN CGO_ENABLED=0 go build .
|
||||
|
||||
# safe image
|
||||
FROM alpine
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN apk update && apk add --no-cache ca-certificates && update-ca-certificates
|
||||
|
||||
COPY --from=build-env /paopao-api/paopao-api /usr/bin/paopao-api
|
||||
COPY --from=build-env /paopao-api/comic.ttf /comic.ttf
|
||||
COPY --from=build-env /paopao-api/qqwry.dat /qqwry.dat
|
||||
COPY --from=build-env /paopao-api/configs /configs
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["paopao-api"]
|
||||
|
||||
# HEALTHCHECK
|
||||
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD ps -ef | grep paopao-api || exit 1
|
Binary file not shown.
@ -0,0 +1,58 @@
|
||||
App: # APP基础设置项
|
||||
BarkToken:
|
||||
AttachmentIncomeRate: 0.8
|
||||
MaxCommentCount: 10
|
||||
DefaultContextTimeout: 60
|
||||
DefaultPageSize: 10
|
||||
MaxPageSize: 100
|
||||
SmsJuheKey:
|
||||
SmsJuheTplID:
|
||||
SmsJuheTplVal: "#code#=%d&#m#=%d"
|
||||
AlipayAppID:
|
||||
AlipayPrivateKey:
|
||||
Server: # 服务设置
|
||||
RunMode: debug
|
||||
HttpIp: 0.0.0.0
|
||||
HttpPort: 8008
|
||||
ReadTimeout: 60
|
||||
WriteTimeout: 60
|
||||
Log: # 日志
|
||||
LogType: zinc # 可选file或zinc
|
||||
LogFileSavePath: storage/logs
|
||||
LogFileName: app
|
||||
LogFileExt: .log
|
||||
LogZincHost: http://127.0.0.1:4080/es/_bulk
|
||||
LogZincIndex: paopao-log
|
||||
LogZincUser: admin
|
||||
LogZincPassword: admin
|
||||
JWT: # 鉴权加密
|
||||
Secret: 18a6413dc4fe394c66345ebe501b2f26
|
||||
Issuer: paopao-api
|
||||
Expire: 86400
|
||||
Search: # 搜索配置
|
||||
ZincHost: http://127.0.0.1:4080
|
||||
ZincIndex: paopao-data
|
||||
ZincUser: admin
|
||||
ZincPassword: admin
|
||||
Storage: # 阿里云OSS存储配置
|
||||
AliossAccessKeyID:
|
||||
AliossAccessKeySecret:
|
||||
AliossEndpoint:
|
||||
AliossBucket:
|
||||
AliossDomain:
|
||||
Database: # 数据库
|
||||
DBType: mysql
|
||||
Username: root # 填写你的数据库账号
|
||||
Password: root # 填写你的数据库密码
|
||||
Host: 127.0.0.1:3306
|
||||
DBName: paopao
|
||||
TablePrefix: p_
|
||||
Charset: utf8mb4
|
||||
ParseTime: True
|
||||
LogLevel: 2
|
||||
MaxIdleConns: 10
|
||||
MaxOpenConns: 30
|
||||
Redis:
|
||||
Host: 127.0.0.1:6379
|
||||
Password:
|
||||
DB:
|
@ -0,0 +1,11 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis/v8"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
DBEngine *gorm.DB
|
||||
Redis *redis.Client
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/rocboss/paopao-api/pkg/setting"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
ServerSetting *setting.ServerSettingS
|
||||
AppSetting *setting.AppSettingS
|
||||
DatabaseSetting *setting.DatabaseSettingS
|
||||
RedisSetting *setting.RedisSettingS
|
||||
SearchSetting *setting.SearchSettingS
|
||||
AliossSetting *setting.AliossSettingS
|
||||
JWTSetting *setting.JWTSettingS
|
||||
LoggerSetting *setting.LoggerSettingS
|
||||
Logger *logrus.Logger
|
||||
Mutex *sync.Mutex
|
||||
)
|
@ -0,0 +1,37 @@
|
||||
module github.com/rocboss/paopao-api
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/ethereum/go-ethereum v1.10.16
|
||||
github.com/fbsobreira/gotron-sdk v0.0.0-20211102183839-58a64f4da5f4
|
||||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/go-playground/validator/v10 v10.10.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.4
|
||||
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/btree v1.0.1
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/smartwalle/alipay/v3 v3.1.7 // indirect
|
||||
github.com/spf13/viper v1.10.1
|
||||
github.com/ugorji/go v1.2.7 // indirect
|
||||
github.com/yinheli/mahonia v0.0.0-20131226213531-0eef680515cc // indirect
|
||||
github.com/yinheli/qqwry v0.0.0-20160229183603-f50680010f4a // indirect
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/resty.v1 v1.12.0
|
||||
gorm.io/driver/mysql v1.3.2
|
||||
gorm.io/gorm v1.23.2
|
||||
gorm.io/plugin/dbresolver v1.1.0
|
||||
gorm.io/plugin/soft_delete v1.1.0
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/logger"
|
||||
"github.com/rocboss/paopao-api/pkg/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := setupSetting()
|
||||
if err != nil {
|
||||
log.Fatalf("init.setupSetting err: %v", err)
|
||||
}
|
||||
err = setupLogger()
|
||||
if err != nil {
|
||||
log.Fatalf("init.setupLogger err: %v", err)
|
||||
}
|
||||
err = setupDBEngine()
|
||||
if err != nil {
|
||||
log.Fatalf("init.setupDBEngine err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupSetting() error {
|
||||
setting, err := setting.NewSetting()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setting.ReadSection("Server", &global.ServerSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("App", &global.AppSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("Log", &global.LoggerSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("Database", &global.DatabaseSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("Search", &global.SearchSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("Redis", &global.RedisSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("JWT", &global.JWTSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setting.ReadSection("Storage", &global.AliossSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.JWTSetting.Expire *= time.Second
|
||||
global.ServerSetting.ReadTimeout *= time.Second
|
||||
global.ServerSetting.WriteTimeout *= time.Second
|
||||
global.Mutex = &sync.Mutex{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupLogger() error {
|
||||
logger, err := logger.New(global.LoggerSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.Logger = logger
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupDBEngine() error {
|
||||
var err error
|
||||
global.DBEngine, err = model.NewDBEngine(global.DatabaseSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
global.Redis = redis.NewClient(&redis.Options{
|
||||
Addr: global.RedisSetting.Host,
|
||||
Password: global.RedisSetting.Password,
|
||||
DB: global.RedisSetting.DB,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package dao
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (d *Dao) CreateAttachment(attachment *model.Attachment) (*model.Attachment, error) {
|
||||
return attachment.Create(d.engine)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package dao
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (d *Dao) GetComments(conditions *model.ConditionsT, offset, limit int) ([]*model.Comment, error) {
|
||||
return (&model.Comment{}).List(d.engine, conditions, offset, limit)
|
||||
}
|
||||
|
||||
func (d *Dao) GetCommentByID(id int64) (*model.Comment, error) {
|
||||
comment := &model.Comment{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
return comment.Get(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) DeleteComment(comment *model.Comment) error {
|
||||
return comment.Delete(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetCommentCount(conditions *model.ConditionsT) (int64, error) {
|
||||
return (&model.Comment{}).Count(d.engine, conditions)
|
||||
}
|
||||
|
||||
func (d *Dao) CreateComment(comment *model.Comment) (*model.Comment, error) {
|
||||
return comment.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) CreateCommentReply(reply *model.CommentReply) (*model.CommentReply, error) {
|
||||
return reply.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetCommentReplyByID(id int64) (*model.CommentReply, error) {
|
||||
reply := &model.CommentReply{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
return reply.Get(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) DeleteCommentReply(reply *model.CommentReply) error {
|
||||
return reply.Delete(d.engine)
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package dao
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (d *Dao) GetCommentContentsByIDs(ids []int64) ([]*model.CommentContent, error) {
|
||||
commentContent := &model.CommentContent{}
|
||||
return commentContent.List(d.engine, &model.ConditionsT{
|
||||
"comment_id IN ?": ids,
|
||||
}, 0, 0)
|
||||
}
|
||||
func (d *Dao) GetCommentRepliesByID(ids []int64) ([]*model.CommentReplyFormated, error) {
|
||||
CommentReply := &model.CommentReply{}
|
||||
replies, err := CommentReply.List(d.engine, &model.ConditionsT{
|
||||
"comment_id IN ?": ids,
|
||||
}, 0, 0)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIds := []int64{}
|
||||
for _, reply := range replies {
|
||||
userIds = append(userIds, reply.UserID, reply.AtUserID)
|
||||
}
|
||||
|
||||
users, err := d.GetUsersByIDs(userIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repliesFormated := []*model.CommentReplyFormated{}
|
||||
for _, reply := range replies {
|
||||
replyFormated := reply.Format()
|
||||
for _, user := range users {
|
||||
if reply.UserID == user.ID {
|
||||
replyFormated.User = user.Format()
|
||||
}
|
||||
if reply.AtUserID == user.ID {
|
||||
replyFormated.AtUser = user.Format()
|
||||
}
|
||||
}
|
||||
|
||||
repliesFormated = append(repliesFormated, replyFormated)
|
||||
}
|
||||
|
||||
return repliesFormated, nil
|
||||
}
|
||||
|
||||
func (d *Dao) CreateCommentContent(content *model.CommentContent) (*model.CommentContent, error) {
|
||||
return content.Create(d.engine)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-api/pkg/zinc"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Dao struct {
|
||||
engine *gorm.DB
|
||||
zinc *zinc.ZincClient
|
||||
}
|
||||
|
||||
func New(engine *gorm.DB, zinc *zinc.ZincClient) *Dao {
|
||||
return &Dao{
|
||||
engine: engine,
|
||||
zinc: zinc,
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package dao
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (d *Dao) CreateMessage(msg *model.Message) (*model.Message, error) {
|
||||
return msg.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUnreadCount(userID int64) (int64, error) {
|
||||
return (&model.Message{}).Count(d.engine, &model.ConditionsT{
|
||||
"receiver_user_id": userID,
|
||||
"is_read": model.MSG_UNREAD,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Dao) GetMessageByID(id int64) (*model.Message, error) {
|
||||
return (&model.Message{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}).Get(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) ReadMessage(message *model.Message) error {
|
||||
message.IsRead = 1
|
||||
return message.Update(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetMessages(conditions *model.ConditionsT, offset, limit int) ([]*model.MessageFormated, error) {
|
||||
messages, err := (&model.Message{}).List(d.engine, conditions, offset, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mfs := []*model.MessageFormated{}
|
||||
for _, message := range messages {
|
||||
mf := message.Format()
|
||||
mfs = append(mfs, mf)
|
||||
}
|
||||
|
||||
return mfs, nil
|
||||
}
|
||||
|
||||
func (d *Dao) GetMessageCount(conditions *model.ConditionsT) (int64, error) {
|
||||
return (&model.Message{}).Count(d.engine, conditions)
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
)
|
||||
|
||||
func (d *Dao) CreatePost(post *model.Post) (*model.Post, error) {
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
return post.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) DeletePost(post *model.Post) error {
|
||||
return post.Delete(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) LockPost(post *model.Post) error {
|
||||
post.IsLock = 1 - post.IsLock
|
||||
return post.Update(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetPostByID(id int64) (*model.Post, error) {
|
||||
post := &model.Post{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
return post.Get(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetPosts(conditions *model.ConditionsT, offset, limit int) ([]*model.Post, error) {
|
||||
return (&model.Post{}).List(d.engine, conditions, offset, limit)
|
||||
}
|
||||
|
||||
func (d *Dao) GetPostCount(conditions *model.ConditionsT) (int64, error) {
|
||||
return (&model.Post{}).Count(d.engine, conditions)
|
||||
}
|
||||
|
||||
func (d *Dao) UpdatePost(post *model.Post) error {
|
||||
return post.Update(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserPostStar(postID, userID int64) (*model.PostStar, error) {
|
||||
star := &model.PostStar{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return star.Get(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserPostStars(userID int64, offset, limit int) ([]*model.PostStar, error) {
|
||||
star := &model.PostStar{
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return star.List(d.engine, &model.ConditionsT{
|
||||
"ORDER": "id DESC",
|
||||
}, offset, limit)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserPostStarCount(userID int64) (int64, error) {
|
||||
star := &model.PostStar{
|
||||
UserID: userID,
|
||||
}
|
||||
return star.Count(d.engine, &model.ConditionsT{})
|
||||
}
|
||||
|
||||
func (d *Dao) CreatePostStar(postID, userID int64) (*model.PostStar, error) {
|
||||
star := &model.PostStar{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return star.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) DeletePostStar(p *model.PostStar) error {
|
||||
return p.Delete(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserPostCollection(postID, userID int64) (*model.PostCollection, error) {
|
||||
star := &model.PostCollection{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return star.Get(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserPostCollections(userID int64, offset, limit int) ([]*model.PostCollection, error) {
|
||||
collection := &model.PostCollection{
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return collection.List(d.engine, &model.ConditionsT{
|
||||
"ORDER": "id DESC",
|
||||
}, offset, limit)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserPostCollectionCount(userID int64) (int64, error) {
|
||||
collection := &model.PostCollection{
|
||||
UserID: userID,
|
||||
}
|
||||
return collection.Count(d.engine, &model.ConditionsT{})
|
||||
}
|
||||
func (d *Dao) GetUserWalletBills(userID int64, offset, limit int) ([]*model.WalletStatement, error) {
|
||||
statement := &model.WalletStatement{
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return statement.List(d.engine, &model.ConditionsT{
|
||||
"ORDER": "id DESC",
|
||||
}, offset, limit)
|
||||
}
|
||||
|
||||
func (d *Dao) GetUserWalletBillCount(userID int64) (int64, error) {
|
||||
statement := &model.WalletStatement{
|
||||
UserID: userID,
|
||||
}
|
||||
return statement.Count(d.engine, &model.ConditionsT{})
|
||||
}
|
||||
|
||||
func (d *Dao) CreatePostCollection(postID, userID int64) (*model.PostCollection, error) {
|
||||
collection := &model.PostCollection{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return collection.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) DeletePostCollection(p *model.PostCollection) error {
|
||||
return p.Delete(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetPostAttatchmentBill(postID, userID int64) (*model.PostAttachmentBill, error) {
|
||||
bill := &model.PostAttachmentBill{
|
||||
PostID: postID,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
return bill.Get(d.engine)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package dao
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (d *Dao) CreatePostContent(content *model.PostContent) (*model.PostContent, error) {
|
||||
return content.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetPostContentsByIDs(ids []int64) ([]*model.PostContent, error) {
|
||||
return (&model.PostContent{}).List(d.engine, &model.ConditionsT{
|
||||
"post_id IN ?": ids,
|
||||
"ORDER": "sort ASC",
|
||||
}, 0, 0)
|
||||
}
|
||||
|
||||
func (d *Dao) GetPostContentByID(id int64) (*model.PostContent, error) {
|
||||
return (&model.PostContent{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}).Get(d.engine)
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-api/pkg/zinc"
|
||||
)
|
||||
|
||||
type SearchType string
|
||||
|
||||
const SearchTypeDefault SearchType = "search"
|
||||
const SearchTypeTag SearchType = "tag"
|
||||
|
||||
type QueryT struct {
|
||||
Query string
|
||||
Type SearchType
|
||||
}
|
||||
|
||||
func (d *Dao) CreateSearchIndex(indexName string) {
|
||||
// 不存在则创建索引
|
||||
d.zinc.CreateIndex(indexName, &zinc.ZincIndexProperty{
|
||||
"id": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Store: true,
|
||||
Sortable: true,
|
||||
},
|
||||
"user_id": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Store: true,
|
||||
},
|
||||
"comment_count": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"collection_count": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"upvote_count": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"is_top": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"is_essence": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"content": &zinc.ZincIndexPropertyT{
|
||||
Type: "text",
|
||||
Index: true,
|
||||
Store: true,
|
||||
Aggregatable: true,
|
||||
Highlightable: true,
|
||||
Analyzer: "gse_search",
|
||||
SearchAnalyzer: "gse_standard",
|
||||
},
|
||||
"tags": &zinc.ZincIndexPropertyT{
|
||||
Type: "keyword",
|
||||
Index: true,
|
||||
Store: true,
|
||||
},
|
||||
"ip_loc": &zinc.ZincIndexPropertyT{
|
||||
Type: "keyword",
|
||||
Index: true,
|
||||
Store: true,
|
||||
},
|
||||
"latest_replied_on": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"attachment_price": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"created_on": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
"modified_on": &zinc.ZincIndexPropertyT{
|
||||
Type: "numeric",
|
||||
Index: true,
|
||||
Sortable: true,
|
||||
Store: true,
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (d *Dao) BulkPushDoc(data []map[string]interface{}) (bool, error) {
|
||||
return d.zinc.BulkPushDoc(data)
|
||||
}
|
||||
|
||||
func (d *Dao) DelDoc(indexName, id string) error {
|
||||
return d.zinc.DelDoc(indexName, id)
|
||||
}
|
||||
|
||||
func (d *Dao) QueryAll(q *QueryT, indexName string, offset, limit int) (*zinc.QueryResultT, error) {
|
||||
// 普通搜索
|
||||
if q.Type == SearchTypeDefault && q.Query != "" {
|
||||
return d.QuerySearch(indexName, q.Query, offset, limit)
|
||||
}
|
||||
// Tag分类
|
||||
if q.Type == SearchTypeTag && q.Query != "" {
|
||||
return d.QueryTagSearch(indexName, q.Query, offset, limit)
|
||||
}
|
||||
|
||||
queryMap := map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"match_all": map[string]string{},
|
||||
},
|
||||
"sort": []string{"-is_top", "-latest_replied_on"},
|
||||
"from": offset,
|
||||
"size": limit,
|
||||
}
|
||||
rsp, err := d.zinc.EsQuery(indexName, queryMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (d *Dao) QuerySearch(indexName, query string, offset, limit int) (*zinc.QueryResultT, error) {
|
||||
rsp, err := d.zinc.EsQuery(indexName, map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"match_phrase": map[string]interface{}{
|
||||
"content": query,
|
||||
},
|
||||
},
|
||||
"from": offset,
|
||||
"size": limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (d *Dao) QueryTagSearch(indexName, query string, offset, limit int) (*zinc.QueryResultT, error) {
|
||||
rsp, err := d.zinc.ApiQuery(indexName, map[string]interface{}{
|
||||
"search_type": "querystring",
|
||||
"query": map[string]interface{}{
|
||||
"term": "tags." + query + ":1",
|
||||
},
|
||||
"sort": []string{"-is_top", "-latest_replied_on"},
|
||||
"from": offset,
|
||||
"max_results": limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rsp, err
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package dao
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (d *Dao) CreateTag(tag *model.Tag) (*model.Tag, error) {
|
||||
t, err := tag.Get(d.engine)
|
||||
if err != nil {
|
||||
tag.QuoteNum = 1
|
||||
return tag.Create(d.engine)
|
||||
}
|
||||
|
||||
// 更新
|
||||
t.QuoteNum++
|
||||
err = t.Update(d.engine)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (d *Dao) DeleteTag(tag *model.Tag) error {
|
||||
tag, err := tag.Get(d.engine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tag.QuoteNum--
|
||||
return tag.Update(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) GetTags(conditions *model.ConditionsT, offset, limit int) ([]*model.Tag, error) {
|
||||
return (&model.Tag{}).List(d.engine, conditions, offset, limit)
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"gopkg.in/resty.v1"
|
||||
)
|
||||
|
||||
type JuhePhoneCaptchaRsp struct {
|
||||
ErrorCode int `json:"error_code"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// 根据用户ID获取用户
|
||||
func (d *Dao) GetUserByID(id int64) (*model.User, error) {
|
||||
user := &model.User{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
|
||||
return user.Get(d.engine)
|
||||
}
|
||||
|
||||
// 根据用户名获取用户
|
||||
func (d *Dao) GetUserByUsername(username string) (*model.User, error) {
|
||||
user := &model.User{
|
||||
Username: username,
|
||||
}
|
||||
|
||||
return user.Get(d.engine)
|
||||
}
|
||||
|
||||
// 根据手机号获取用户
|
||||
func (d *Dao) GetUserByPhone(phone string) (*model.User, error) {
|
||||
user := &model.User{
|
||||
Phone: phone,
|
||||
}
|
||||
|
||||
return user.Get(d.engine)
|
||||
}
|
||||
|
||||
// 根据IDs获取用户列表
|
||||
func (d *Dao) GetUsersByIDs(ids []int64) ([]*model.User, error) {
|
||||
user := &model.User{}
|
||||
|
||||
return user.List(d.engine, &model.ConditionsT{
|
||||
"id IN ?": ids,
|
||||
}, 0, 0)
|
||||
}
|
||||
|
||||
// 根据关键词模糊获取用户列表
|
||||
func (d *Dao) GetUsersByKeyword(keyword string) ([]*model.User, error) {
|
||||
user := &model.User{}
|
||||
|
||||
if strings.Trim(keyword, "") == "" {
|
||||
return user.List(d.engine, &model.ConditionsT{
|
||||
"ORDER": "id ASC",
|
||||
}, 0, 6)
|
||||
} else {
|
||||
|
||||
return user.List(d.engine, &model.ConditionsT{
|
||||
"username LIKE ?": strings.Trim(keyword, "") + "%",
|
||||
}, 0, 6)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据关键词模糊获取用户列表
|
||||
func (d *Dao) GetTagsByKeyword(keyword string) ([]*model.Tag, error) {
|
||||
tag := &model.Tag{}
|
||||
|
||||
if strings.Trim(keyword, "") == "" {
|
||||
return tag.List(d.engine, &model.ConditionsT{
|
||||
"ORDER": "quote_num DESC",
|
||||
}, 0, 6)
|
||||
} else {
|
||||
|
||||
return tag.List(d.engine, &model.ConditionsT{
|
||||
"tag LIKE ?": "%" + strings.Trim(keyword, "") + "%",
|
||||
"ORDER": "quote_num DESC",
|
||||
}, 0, 6)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
func (d *Dao) CreateUser(user *model.User) (*model.User, error) {
|
||||
return user.Create(d.engine)
|
||||
}
|
||||
|
||||
// 更新用户
|
||||
func (d *Dao) UpdateUser(user *model.User) error {
|
||||
return user.Update(d.engine)
|
||||
}
|
||||
|
||||
// 获取最新短信验证码
|
||||
func (d *Dao) GetLatestPhoneCaptcha(phone string) (*model.Captcha, error) {
|
||||
return (&model.Captcha{
|
||||
Phone: phone,
|
||||
}).Get(d.engine)
|
||||
}
|
||||
|
||||
// 更新短信验证码
|
||||
func (d *Dao) UsePhoneCaptcha(captcha *model.Captcha) error {
|
||||
captcha.UseTimes++
|
||||
return captcha.Update(d.engine)
|
||||
}
|
||||
|
||||
// 发送短信验证码
|
||||
func (d *Dao) SendPhoneCaptcha(phone string) error {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
captcha := rand.Intn(900000) + 100000
|
||||
m := 5
|
||||
|
||||
gateway := "https://v.juhe.cn/sms/send"
|
||||
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().
|
||||
SetFormData(map[string]string{
|
||||
"mobile": phone,
|
||||
"tpl_id": global.AppSetting.SmsJuheTplID,
|
||||
"tpl_value": fmt.Sprintf(global.AppSetting.SmsJuheTplVal, captcha, m),
|
||||
"key": global.AppSetting.SmsJuheKey,
|
||||
}).Post(gateway)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return errors.New(resp.Status())
|
||||
}
|
||||
|
||||
result := &JuhePhoneCaptchaRsp{}
|
||||
err = json.Unmarshal(resp.Body(), result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.ErrorCode != 0 {
|
||||
return errors.New(result.Reason)
|
||||
}
|
||||
|
||||
// 写入表
|
||||
captchaModel := &model.Captcha{
|
||||
Phone: phone,
|
||||
Captcha: strconv.Itoa(captcha),
|
||||
ExpiredOn: time.Now().Add(time.Minute * time.Duration(m)).Unix(),
|
||||
}
|
||||
captchaModel.Create(d.engine)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (d *Dao) GetRechargeByID(id int64) (*model.WalletRecharge, error) {
|
||||
recharge := &model.WalletRecharge{
|
||||
Model: &model.Model{
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
|
||||
return recharge.Get(d.engine)
|
||||
}
|
||||
func (d *Dao) CreateRecharge(userId, amount int64) (*model.WalletRecharge, error) {
|
||||
recharge := &model.WalletRecharge{
|
||||
UserID: userId,
|
||||
Amount: amount,
|
||||
}
|
||||
|
||||
return recharge.Create(d.engine)
|
||||
}
|
||||
|
||||
func (d *Dao) HandleRechargeSuccess(recharge *model.WalletRecharge, tradeNo string) error {
|
||||
user, _ := (&model.User{
|
||||
Model: &model.Model{
|
||||
ID: recharge.UserID,
|
||||
},
|
||||
}).Get(d.engine)
|
||||
|
||||
return d.engine.Transaction(func(tx *gorm.DB) error {
|
||||
// 扣除金额
|
||||
if err := tx.Model(user).Update("balance", gorm.Expr("balance + ?", recharge.Amount)).Error; err != nil {
|
||||
// 返回任何错误都会回滚事务
|
||||
return err
|
||||
}
|
||||
|
||||
// 新增账单
|
||||
if err := tx.Create(&model.WalletStatement{
|
||||
UserID: user.ID,
|
||||
ChangeAmount: recharge.Amount,
|
||||
BalanceSnapshot: user.Balance + recharge.Amount,
|
||||
Reason: "用户充值",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 标记为已付款
|
||||
if err := tx.Model(recharge).Updates(map[string]interface{}{
|
||||
"trade_no": tradeNo,
|
||||
"trade_status": "TRADE_SUCCESS",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回 nil 提交事务
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Dao) HandlePostAttachmentBought(post *model.Post, user *model.User) error {
|
||||
return d.engine.Transaction(func(tx *gorm.DB) error {
|
||||
// 扣除金额
|
||||
if err := tx.Model(user).Update("balance", gorm.Expr("balance - ?", post.AttachmentPrice)).Error; err != nil {
|
||||
// 返回任何错误都会回滚事务
|
||||
return err
|
||||
}
|
||||
|
||||
// 新增账单
|
||||
if err := tx.Create(&model.WalletStatement{
|
||||
PostID: post.ID,
|
||||
UserID: user.ID,
|
||||
ChangeAmount: -post.AttachmentPrice,
|
||||
BalanceSnapshot: user.Balance - post.AttachmentPrice,
|
||||
Reason: "购买附件支出",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 新增附件购买记录
|
||||
if err := tx.Create(&model.PostAttachmentBill{
|
||||
PostID: post.ID,
|
||||
UserID: user.ID,
|
||||
PaidAmount: post.AttachmentPrice,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 对附件主新增账单
|
||||
income := int64(float64(post.AttachmentPrice) * global.AppSetting.AttachmentIncomeRate)
|
||||
if income > 0 {
|
||||
master := &model.User{
|
||||
Model: &model.Model{
|
||||
ID: post.UserID,
|
||||
},
|
||||
}
|
||||
master, _ = master.Get(d.engine)
|
||||
|
||||
if err := tx.Model(master).Update("balance", gorm.Expr("balance + ?", income)).Error; err != nil {
|
||||
// 返回任何错误都会回滚事务
|
||||
return err
|
||||
}
|
||||
|
||||
// 新增账单
|
||||
if err := tx.Create(&model.WalletStatement{
|
||||
PostID: post.ID,
|
||||
UserID: master.ID,
|
||||
ChangeAmount: income,
|
||||
BalanceSnapshot: master.Balance + income,
|
||||
Reason: "出售附件收入",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 返回 nil 提交事务
|
||||
return nil
|
||||
})
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
func Priv() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if user, exist := c.Get("USER"); exist {
|
||||
if userModel, ok := user.(*model.User); ok {
|
||||
if userModel.Status == model.UserStatusNormal {
|
||||
|
||||
if userModel.Phone == "" {
|
||||
response := app.NewResponse(c)
|
||||
response.ToErrorResponse(errcode.AccountNoPhoneBind)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response := app.NewResponse(c)
|
||||
response.ToErrorResponse(errcode.UserHasBeenBanned)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type AttachmentType int
|
||||
|
||||
const (
|
||||
ATTACHMENT_TYPE_IMAGE AttachmentType = iota + 1
|
||||
ATTACHMENT_TYPE_VIDEO
|
||||
ATTACHMENT_TYPE_OTHER
|
||||
)
|
||||
|
||||
type Attachment struct {
|
||||
*Model
|
||||
UserID int64 `json:"user_id"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
ImgWidth int `json:"img_width"`
|
||||
ImgHeight int `json:"img_height"`
|
||||
Type AttachmentType `json:"type"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (a *Attachment) Create(db *gorm.DB) (*Attachment, error) {
|
||||
err := db.Create(&a).Error
|
||||
|
||||
return a, err
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Captcha struct {
|
||||
*Model
|
||||
Phone string `json:"phone"`
|
||||
Captcha string `json:"captcha"`
|
||||
UseTimes int `json:"use_times"`
|
||||
ExpiredOn int64 `json:"expired_on"`
|
||||
}
|
||||
|
||||
func (c *Captcha) Create(db *gorm.DB) (*Captcha, error) {
|
||||
err := db.Create(&c).Error
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *Captcha) Update(db *gorm.DB) error {
|
||||
return db.Model(&Captcha{}).Where("id = ? AND is_del = ?", c.Model.ID, 0).Save(c).Error
|
||||
}
|
||||
|
||||
func (c *Captcha) Get(db *gorm.DB) (*Captcha, error) {
|
||||
var captcha Captcha
|
||||
if c.Model != nil && c.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", c.ID, 0)
|
||||
}
|
||||
if c.Phone != "" {
|
||||
db = db.Where("phone = ?", c.Phone)
|
||||
}
|
||||
|
||||
err := db.Last(&captcha).Error
|
||||
if err != nil {
|
||||
return &captcha, err
|
||||
}
|
||||
|
||||
return &captcha, nil
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Comment struct {
|
||||
*Model
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
IP string `json:"ip"`
|
||||
IPLoc string `json:"ip_loc"`
|
||||
}
|
||||
|
||||
type CommentFormated struct {
|
||||
ID int64 `json:"id"`
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
User *UserFormated `json:"user"`
|
||||
Contents []*CommentContent `json:"contents"`
|
||||
Replies []*CommentReplyFormated `json:"replies"`
|
||||
IPLoc string `json:"ip_loc"`
|
||||
CreatedOn int64 `json:"created_on"`
|
||||
ModifiedOn int64 `json:"modified_on"`
|
||||
}
|
||||
|
||||
func (c *Comment) Format() *CommentFormated {
|
||||
if c.Model == nil {
|
||||
return &CommentFormated{}
|
||||
}
|
||||
return &CommentFormated{
|
||||
ID: c.Model.ID,
|
||||
PostID: c.PostID,
|
||||
UserID: c.UserID,
|
||||
User: &UserFormated{},
|
||||
Contents: []*CommentContent{},
|
||||
Replies: []*CommentReplyFormated{},
|
||||
IPLoc: c.IPLoc,
|
||||
CreatedOn: c.CreatedOn,
|
||||
ModifiedOn: c.ModifiedOn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Comment) Get(db *gorm.DB) (*Comment, error) {
|
||||
var comment Comment
|
||||
if c.Model != nil && c.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", c.ID, 0)
|
||||
} else {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
err := db.First(&comment).Error
|
||||
if err != nil {
|
||||
return &comment, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (c *Comment) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Comment, error) {
|
||||
var comments []*Comment
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if c.PostID > 0 {
|
||||
db = db.Where("id = ?", c.PostID)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&comments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (c *Comment) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
|
||||
var count int64
|
||||
if c.PostID > 0 {
|
||||
db = db.Where("post_id = ?", c.PostID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k != "ORDER" {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
if err := db.Model(c).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (c *Comment) Create(db *gorm.DB) (*Comment, error) {
|
||||
err := db.Create(&c).Error
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *Comment) Delete(db *gorm.DB) error {
|
||||
return db.Model(&Comment{}).Where("id = ? AND is_del = ?", c.Model.ID, 0).Updates(map[string]interface{}{
|
||||
"deleted_on": time.Now().Unix(),
|
||||
"is_del": 1,
|
||||
}).Error
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type CommentContent struct {
|
||||
*Model
|
||||
CommentID int64 `json:"comment_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
Type PostContentT `json:"type"`
|
||||
Sort int64 `json:"sort"`
|
||||
}
|
||||
|
||||
func (c *CommentContent) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*CommentContent, error) {
|
||||
var comments []*CommentContent
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if c.CommentID > 0 {
|
||||
db = db.Where("id = ?", c.CommentID)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&comments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (c *CommentContent) Create(db *gorm.DB) (*CommentContent, error) {
|
||||
err := db.Create(&c).Error
|
||||
|
||||
return c, err
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CommentReply struct {
|
||||
*Model
|
||||
CommentID int64 `json:"comment_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
AtUserID int64 `json:"at_user_id"`
|
||||
Content string `json:"content"`
|
||||
IP string `json:"ip"`
|
||||
IPLoc string `json:"ip_loc"`
|
||||
}
|
||||
|
||||
type CommentReplyFormated struct {
|
||||
ID int64 `json:"id"`
|
||||
CommentID int64 `json:"comment_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
User *UserFormated `json:"user"`
|
||||
AtUserID int64 `json:"at_user_id"`
|
||||
AtUser *UserFormated `json:"at_user"`
|
||||
Content string `json:"content"`
|
||||
IPLoc string `json:"ip_loc"`
|
||||
CreatedOn int64 `json:"created_on"`
|
||||
ModifiedOn int64 `json:"modified_on"`
|
||||
}
|
||||
|
||||
func (c *CommentReply) Format() *CommentReplyFormated {
|
||||
if c.Model == nil {
|
||||
return &CommentReplyFormated{}
|
||||
}
|
||||
|
||||
return &CommentReplyFormated{
|
||||
ID: c.ID,
|
||||
CommentID: c.CommentID,
|
||||
UserID: c.UserID,
|
||||
User: &UserFormated{},
|
||||
AtUserID: c.AtUserID,
|
||||
AtUser: &UserFormated{},
|
||||
Content: c.Content,
|
||||
IPLoc: c.IPLoc,
|
||||
CreatedOn: c.CreatedOn,
|
||||
ModifiedOn: c.ModifiedOn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommentReply) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*CommentReply, error) {
|
||||
var comments []*CommentReply
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if c.CommentID > 0 {
|
||||
db = db.Where("id = ?", c.CommentID)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&comments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (c *CommentReply) Create(db *gorm.DB) (*CommentReply, error) {
|
||||
err := db.Create(&c).Error
|
||||
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *CommentReply) Get(db *gorm.DB) (*CommentReply, error) {
|
||||
var reply CommentReply
|
||||
if c.Model != nil && c.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", c.ID, 0)
|
||||
} else {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
err := db.First(&reply).Error
|
||||
if err != nil {
|
||||
return &reply, err
|
||||
}
|
||||
|
||||
return &reply, nil
|
||||
}
|
||||
|
||||
func (c *CommentReply) Delete(db *gorm.DB) error {
|
||||
return db.Model(&CommentReply{}).Where("id = ? AND is_del = ?", c.Model.ID, 0).Updates(map[string]interface{}{
|
||||
"deleted_on": time.Now().Unix(),
|
||||
"is_del": 1,
|
||||
}).Error
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type MessageT int8
|
||||
|
||||
const (
|
||||
MESSAGE_POST MessageT = iota + 1
|
||||
MESSAGE_COMMENT
|
||||
MESSAGE_REPLY
|
||||
MESSAGE_WHISPER
|
||||
)
|
||||
const MESSAGE_SYSTEM MessageT = 99
|
||||
|
||||
const MSG_UNREAD = 0
|
||||
const MSG_READED = 1
|
||||
|
||||
type Message struct {
|
||||
*Model
|
||||
SenderUserID int64 `json:"sender_user_id"`
|
||||
ReceiverUserID int64 `json:"receiver_user_id"`
|
||||
Type MessageT `json:"type"`
|
||||
Breif string `json:"breif"`
|
||||
Content string `json:"content"`
|
||||
PostID int64 `json:"post_id"`
|
||||
CommentID int64 `json:"comment_id"`
|
||||
ReplyID int64 `json:"reply_id"`
|
||||
IsRead int8 `json:"is_read"`
|
||||
}
|
||||
|
||||
type MessageFormated struct {
|
||||
ID int64 `json:"id"`
|
||||
SenderUserID int64 `json:"sender_user_id"`
|
||||
SenderUser *UserFormated `json:"sender_user"`
|
||||
ReceiverUserID int64 `json:"receiver_user_id"`
|
||||
Type MessageT `json:"type"`
|
||||
Breif string `json:"breif"`
|
||||
Content string `json:"content"`
|
||||
PostID int64 `json:"post_id"`
|
||||
Post *PostFormated `json:"post"`
|
||||
CommentID int64 `json:"comment_id"`
|
||||
Comment *Comment `json:"comment"`
|
||||
ReplyID int64 `json:"reply_id"`
|
||||
Reply *CommentReply `json:"reply"`
|
||||
IsRead int8 `json:"is_read"`
|
||||
CreatedOn int64 `json:"created_on"`
|
||||
ModifiedOn int64 `json:"modified_on"`
|
||||
}
|
||||
|
||||
func (m *Message) Format() *MessageFormated {
|
||||
if m.Model == nil || m.Model.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
mf := &MessageFormated{
|
||||
ID: m.ID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
SenderUser: &UserFormated{},
|
||||
ReceiverUserID: m.ReceiverUserID,
|
||||
Type: m.Type,
|
||||
Breif: m.Breif,
|
||||
Content: m.Content,
|
||||
PostID: m.PostID,
|
||||
Post: &PostFormated{},
|
||||
CommentID: m.CommentID,
|
||||
Comment: &Comment{},
|
||||
ReplyID: m.ReplyID,
|
||||
Reply: &CommentReply{},
|
||||
IsRead: m.IsRead,
|
||||
CreatedOn: m.CreatedOn,
|
||||
ModifiedOn: m.ModifiedOn,
|
||||
}
|
||||
|
||||
return mf
|
||||
}
|
||||
|
||||
func (m *Message) Create(db *gorm.DB) (*Message, error) {
|
||||
err := db.Create(&m).Error
|
||||
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (m *Message) Update(db *gorm.DB) error {
|
||||
return db.Model(&Message{}).Where("id = ? AND is_del = ?", m.Model.ID, 0).Save(m).Error
|
||||
}
|
||||
|
||||
func (m *Message) Get(db *gorm.DB) (*Message, error) {
|
||||
var message Message
|
||||
if m.Model != nil && m.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", m.ID, 0)
|
||||
}
|
||||
if m.ReceiverUserID > 0 {
|
||||
db = db.Where("receiver_user_id = ?", m.ReceiverUserID)
|
||||
}
|
||||
|
||||
err := db.First(&message).Error
|
||||
if err != nil {
|
||||
return &message, err
|
||||
}
|
||||
|
||||
return &message, nil
|
||||
}
|
||||
|
||||
func (c *Message) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Message, error) {
|
||||
var messages []*Message
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&messages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (m *Message) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
|
||||
var count int64
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k != "ORDER" {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
if err := db.Model(m).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Post struct {
|
||||
*Model
|
||||
UserID int64 `json:"user_id"`
|
||||
CommentCount int64 `json:"comment_count"`
|
||||
CollectionCount int64 `json:"collection_count"`
|
||||
UpvoteCount int64 `json:"upvote_count"`
|
||||
IsTop int `json:"is_top"`
|
||||
IsEssence int `json:"is_essence"`
|
||||
IsLock int `json:"is_lock"`
|
||||
LatestRepliedOn int64 `json:"latest_replied_on"`
|
||||
Tags string `json:"tags"`
|
||||
AttachmentPrice int64 `json:"attachment_price"`
|
||||
IP string `json:"ip"`
|
||||
IPLoc string `json:"ip_loc"`
|
||||
}
|
||||
|
||||
type PostFormated struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
User *UserFormated `json:"user"`
|
||||
Contents []*PostContentFormated `json:"contents"`
|
||||
CommentCount int64 `json:"comment_count"`
|
||||
CollectionCount int64 `json:"collection_count"`
|
||||
UpvoteCount int64 `json:"upvote_count"`
|
||||
IsTop int `json:"is_top"`
|
||||
IsEssence int `json:"is_essence"`
|
||||
IsLock int `json:"is_lock"`
|
||||
LatestRepliedOn int64 `json:"latest_replied_on"`
|
||||
CreatedOn int64 `json:"created_on"`
|
||||
ModifiedOn int64 `json:"modified_on"`
|
||||
Tags map[string]int8 `json:"tags"`
|
||||
AttachmentPrice int64 `json:"attachment_price"`
|
||||
IPLoc string `json:"ip_loc"`
|
||||
}
|
||||
|
||||
func (p *Post) Format() *PostFormated {
|
||||
if p.Model != nil {
|
||||
tagsMap := map[string]int8{}
|
||||
for _, tag := range strings.Split(p.Tags, ",") {
|
||||
tagsMap[tag] = 1
|
||||
}
|
||||
return &PostFormated{
|
||||
ID: p.ID,
|
||||
UserID: p.UserID,
|
||||
User: &UserFormated{},
|
||||
Contents: []*PostContentFormated{},
|
||||
CommentCount: p.CommentCount,
|
||||
CollectionCount: p.CollectionCount,
|
||||
UpvoteCount: p.UpvoteCount,
|
||||
IsTop: p.IsTop,
|
||||
IsEssence: p.IsEssence,
|
||||
IsLock: p.IsLock,
|
||||
LatestRepliedOn: p.LatestRepliedOn,
|
||||
CreatedOn: p.CreatedOn,
|
||||
ModifiedOn: p.ModifiedOn,
|
||||
AttachmentPrice: p.AttachmentPrice,
|
||||
Tags: tagsMap,
|
||||
IPLoc: p.IPLoc,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Post) Create(db *gorm.DB) (*Post, error) {
|
||||
err := db.Create(&p).Error
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (s *Post) Delete(db *gorm.DB) error {
|
||||
return db.Model(&Post{}).Where("id = ? AND is_del = ?", s.Model.ID, 0).Updates(map[string]interface{}{
|
||||
"deleted_on": time.Now().Unix(),
|
||||
"is_del": 1,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (p *Post) Get(db *gorm.DB) (*Post, error) {
|
||||
var post Post
|
||||
if p.Model != nil && p.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", p.ID, 0)
|
||||
} else {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
err := db.First(&post).Error
|
||||
if err != nil {
|
||||
return &post, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (p *Post) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Post, error) {
|
||||
var posts []*Post
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&posts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (p *Post) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
|
||||
var count int64
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k != "ORDER" {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
if err := db.Model(p).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (p *Post) Update(db *gorm.DB) error {
|
||||
return db.Model(&Post{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Save(p).Error
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type PostAttachmentBill struct {
|
||||
*Model
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
PaidAmount int64 `json:"paid_amount"`
|
||||
}
|
||||
|
||||
func (p *PostAttachmentBill) Get(db *gorm.DB) (*PostAttachmentBill, error) {
|
||||
var pas PostAttachmentBill
|
||||
if p.Model != nil && p.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", p.ID, 0)
|
||||
}
|
||||
if p.PostID > 0 {
|
||||
db = db.Where("post_id = ?", p.PostID)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
|
||||
err := db.First(&pas).Error
|
||||
if err != nil {
|
||||
return &pas, err
|
||||
}
|
||||
|
||||
return &pas, nil
|
||||
}
|
||||
|
||||
func (p *PostAttachmentBill) Create(db *gorm.DB) (*PostAttachmentBill, error) {
|
||||
err := db.Create(&p).Error
|
||||
|
||||
return p, err
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PostCollection struct {
|
||||
*Model
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
func (p *PostCollection) Get(db *gorm.DB) (*PostCollection, error) {
|
||||
var star PostCollection
|
||||
if p.Model != nil && p.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", p.ID, 0)
|
||||
}
|
||||
if p.PostID > 0 {
|
||||
db = db.Where("post_id = ?", p.PostID)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
|
||||
err := db.First(&star).Error
|
||||
if err != nil {
|
||||
return &star, err
|
||||
}
|
||||
|
||||
return &star, nil
|
||||
}
|
||||
|
||||
func (p *PostCollection) Create(db *gorm.DB) (*PostCollection, error) {
|
||||
err := db.Create(&p).Error
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (p *PostCollection) Delete(db *gorm.DB) error {
|
||||
return db.Model(&PostCollection{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{
|
||||
"deleted_on": time.Now().Unix(),
|
||||
"is_del": 1,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (p *PostCollection) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*PostCollection, error) {
|
||||
var collections []*PostCollection
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&collections).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections, nil
|
||||
}
|
||||
|
||||
func (p *PostCollection) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
|
||||
var count int64
|
||||
if p.PostID > 0 {
|
||||
db = db.Where("post_id = ?", p.PostID)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k != "ORDER" {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
if err := db.Model(p).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PostStar struct {
|
||||
*Model
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
}
|
||||
|
||||
func (p *PostStar) Get(db *gorm.DB) (*PostStar, error) {
|
||||
var star PostStar
|
||||
if p.Model != nil && p.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", p.ID, 0)
|
||||
}
|
||||
if p.PostID > 0 {
|
||||
db = db.Where("post_id = ?", p.PostID)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
|
||||
err := db.First(&star).Error
|
||||
if err != nil {
|
||||
return &star, err
|
||||
}
|
||||
|
||||
return &star, nil
|
||||
}
|
||||
|
||||
func (p *PostStar) Create(db *gorm.DB) (*PostStar, error) {
|
||||
err := db.Create(&p).Error
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (p *PostStar) Delete(db *gorm.DB) error {
|
||||
return db.Model(&PostStar{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{
|
||||
"deleted_on": time.Now().Unix(),
|
||||
"is_del": 1,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (p *PostStar) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*PostStar, error) {
|
||||
var stars []*PostStar
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&stars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stars, nil
|
||||
}
|
||||
|
||||
func (p *PostStar) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
|
||||
var count int64
|
||||
if p.PostID > 0 {
|
||||
db = db.Where("post_id = ?", p.PostID)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k != "ORDER" {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
if err := db.Model(p).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Tag struct {
|
||||
*Model
|
||||
UserID int64 `json:"user_id"`
|
||||
Tag string `json:"tag"`
|
||||
QuoteNum int64 `json:"quote_num"`
|
||||
}
|
||||
type TagFormated struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
User *UserFormated `json:"user"`
|
||||
Tag string `json:"tag"`
|
||||
QuoteNum int64 `json:"quote_num"`
|
||||
}
|
||||
|
||||
func (t *Tag) Format() *TagFormated {
|
||||
if t.Model == nil {
|
||||
return &TagFormated{}
|
||||
}
|
||||
|
||||
return &TagFormated{
|
||||
ID: t.ID,
|
||||
UserID: t.UserID,
|
||||
User: &UserFormated{},
|
||||
Tag: t.Tag,
|
||||
QuoteNum: t.QuoteNum,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tag) Get(db *gorm.DB) (*Tag, error) {
|
||||
var tag Tag
|
||||
if t.Model != nil && t.Model.ID > 0 {
|
||||
db = db.Where("id= ? AND is_del = ?", t.Model.ID, 0)
|
||||
} else {
|
||||
db = db.Where("tag = ? AND is_del = ?", t.Tag, 0)
|
||||
}
|
||||
|
||||
err := db.First(&tag).Error
|
||||
if err != nil {
|
||||
return &tag, err
|
||||
}
|
||||
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
func (t *Tag) Create(db *gorm.DB) (*Tag, error) {
|
||||
err := db.Create(&t).Error
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (t *Tag) Update(db *gorm.DB) error {
|
||||
return db.Model(&Tag{}).Where("id = ? AND is_del = ?", t.Model.ID, 0).Save(t).Error
|
||||
}
|
||||
|
||||
func (t *Tag) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Tag, error) {
|
||||
var tags []*Tag
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if t.UserID > 0 {
|
||||
db = db.Where("user_id = ?", t.UserID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&tags).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
const (
|
||||
UserStatusNormal int = iota + 1
|
||||
UserStatusClosed
|
||||
)
|
||||
|
||||
type User struct {
|
||||
*Model
|
||||
Nickname string `json:"nickname"`
|
||||
Username string `json:"username"`
|
||||
Phone string `json:"phone"`
|
||||
Password string `json:"password"`
|
||||
Salt string `json:"salt"`
|
||||
Status int `json:"status"`
|
||||
Avatar string `json:"avatar"`
|
||||
Balance int64 `json:"balance"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
}
|
||||
|
||||
type UserFormated 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"`
|
||||
}
|
||||
|
||||
func (u *User) Format() *UserFormated {
|
||||
if u.Model != nil {
|
||||
return &UserFormated{
|
||||
ID: u.ID,
|
||||
Nickname: u.Nickname,
|
||||
Username: u.Username,
|
||||
Status: u.Status,
|
||||
Avatar: u.Avatar,
|
||||
IsAdmin: u.IsAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) Get(db *gorm.DB) (*User, error) {
|
||||
var user User
|
||||
if u.Model != nil && u.Model.ID > 0 {
|
||||
db = db.Where("id= ? AND is_del = ?", u.Model.ID, 0)
|
||||
} else if u.Phone != "" {
|
||||
db = db.Where("phone = ? AND is_del = ?", u.Phone, 0)
|
||||
} else {
|
||||
db = db.Where("username = ? AND is_del = ?", u.Username, 0)
|
||||
}
|
||||
|
||||
err := db.First(&user).Error
|
||||
if err != nil {
|
||||
return &user, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (u *User) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*User, error) {
|
||||
var users []*User
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *User) Create(db *gorm.DB) (*User, error) {
|
||||
err := db.Create(&u).Error
|
||||
|
||||
return u, err
|
||||
}
|
||||
|
||||
func (u *User) Update(db *gorm.DB) error {
|
||||
return db.Model(&User{}).Where("id = ? AND is_del = ?", u.Model.ID, 0).Save(u).Error
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type WalletRecharge struct {
|
||||
*Model
|
||||
UserID int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
TradeStatus string `json:"trade_status"`
|
||||
}
|
||||
|
||||
func (p *WalletRecharge) Get(db *gorm.DB) (*WalletRecharge, error) {
|
||||
var pas WalletRecharge
|
||||
if p.Model != nil && p.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", p.ID, 0)
|
||||
}
|
||||
if p.UserID > 0 {
|
||||
db = db.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
|
||||
err := db.First(&pas).Error
|
||||
if err != nil {
|
||||
return &pas, err
|
||||
}
|
||||
|
||||
return &pas, nil
|
||||
}
|
||||
|
||||
func (p *WalletRecharge) Create(db *gorm.DB) (*WalletRecharge, error) {
|
||||
err := db.Create(&p).Error
|
||||
|
||||
return p, err
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type WalletStatement struct {
|
||||
*Model
|
||||
UserID int64 `json:"user_id"`
|
||||
ChangeAmount int64 `json:"change_amount"`
|
||||
BalanceSnapshot int64 `json:"balance_snapshot"`
|
||||
Reason string `json:"reason"`
|
||||
PostID int64 `json:"post_id"`
|
||||
}
|
||||
|
||||
func (w *WalletStatement) Get(db *gorm.DB) (*WalletStatement, error) {
|
||||
var ws WalletStatement
|
||||
if w.Model != nil && w.ID > 0 {
|
||||
db = db.Where("id = ? AND is_del = ?", w.ID, 0)
|
||||
}
|
||||
if w.PostID > 0 {
|
||||
db = db.Where("post_id = ?", w.PostID)
|
||||
}
|
||||
if w.UserID > 0 {
|
||||
db = db.Where("user_id = ?", w.UserID)
|
||||
}
|
||||
|
||||
err := db.First(&ws).Error
|
||||
if err != nil {
|
||||
return &ws, err
|
||||
}
|
||||
|
||||
return &ws, nil
|
||||
}
|
||||
|
||||
func (w *WalletStatement) Create(db *gorm.DB) (*WalletStatement, error) {
|
||||
err := db.Create(&w).Error
|
||||
|
||||
return w, err
|
||||
}
|
||||
|
||||
func (w *WalletStatement) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*WalletStatement, error) {
|
||||
var records []*WalletStatement
|
||||
var err error
|
||||
if offset >= 0 && limit > 0 {
|
||||
db = db.Offset(offset).Limit(limit)
|
||||
}
|
||||
if w.UserID > 0 {
|
||||
db = db.Where("user_id = ?", w.UserID)
|
||||
}
|
||||
|
||||
for k, v := range *conditions {
|
||||
if k == "ORDER" {
|
||||
db = db.Order(v)
|
||||
} else {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.Where("is_del = ?", 0).Find(&records).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (w *WalletStatement) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
|
||||
var count int64
|
||||
if w.PostID > 0 {
|
||||
db = db.Where("post_id = ?", w.PostID)
|
||||
}
|
||||
if w.UserID > 0 {
|
||||
db = db.Where("user_id = ?", w.UserID)
|
||||
}
|
||||
for k, v := range *conditions {
|
||||
if k != "ORDER" {
|
||||
db = db.Where(k, v)
|
||||
}
|
||||
}
|
||||
if err := db.Model(w).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
@ -0,0 +1,299 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"image"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/internal/service"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
func GeneratePath(s string) string {
|
||||
n := len(s)
|
||||
if n <= 2 {
|
||||
return s
|
||||
}
|
||||
|
||||
return GeneratePath(s[:n-2]) + "/" + s[n-2:]
|
||||
}
|
||||
|
||||
func GetFileExt(s string) (string, error) {
|
||||
switch s {
|
||||
case "image/png":
|
||||
return ".png", nil
|
||||
case "image/jpg":
|
||||
return ".jpg", nil
|
||||
case "image/jpeg":
|
||||
return ".jpeg", nil
|
||||
case "image/gif":
|
||||
return ".gif", nil
|
||||
case "video/mp4":
|
||||
return ".mp4", nil
|
||||
case "video/quicktime":
|
||||
return ".mov", nil
|
||||
case "application/zip":
|
||||
return ".zip", nil
|
||||
default:
|
||||
return "", errcode.FileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型")
|
||||
}
|
||||
}
|
||||
func GetImageSize(img image.Rectangle) (int, int) {
|
||||
b := img.Bounds()
|
||||
width := b.Max.X
|
||||
height := b.Max.Y
|
||||
return width, height
|
||||
}
|
||||
|
||||
func UploadAttachment(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
svc := service.New(c)
|
||||
|
||||
uploadType := c.Request.FormValue("type")
|
||||
file, fileHeader, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
global.Logger.Errorf("api.UploadAttachment err: %v", err)
|
||||
response.ToErrorResponse(errcode.FileUploadFailed)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if uploadType != "public/video" &&
|
||||
uploadType != "public/image" &&
|
||||
uploadType != "public/avatar" &&
|
||||
uploadType != "attachment" {
|
||||
response.ToErrorResponse(errcode.InvalidParams)
|
||||
return
|
||||
}
|
||||
|
||||
if fileHeader.Size > 1024*1024*100 {
|
||||
response.ToErrorResponse(errcode.FileInvalidSize.WithDetails("最大允许100MB"))
|
||||
return
|
||||
}
|
||||
|
||||
fileExt, err := GetFileExt(fileHeader.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
global.Logger.Errorf("GetFileExt err: %v", err)
|
||||
response.ToErrorResponse(err.(*errcode.Error))
|
||||
return
|
||||
}
|
||||
|
||||
fileReader, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
global.Logger.Errorf("Attachment file read err: %v", err)
|
||||
response.ToErrorResponse(errcode.FileUploadFailed)
|
||||
return
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
// 生成随机路径
|
||||
randomPath := uuid.Must(uuid.NewV4()).String()
|
||||
ossSavePath := uploadType + "/" + GeneratePath(randomPath[:8]) + "/" + randomPath[9:] + fileExt
|
||||
|
||||
client, err := oss.New(global.AliossSetting.AliossEndpoint, global.AliossSetting.AliossAccessKeyID, global.AliossSetting.AliossAccessKeySecret)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("oss.New err: %v", err)
|
||||
response.ToErrorResponse(errcode.FileUploadFailed)
|
||||
return
|
||||
}
|
||||
|
||||
bucket, err := client.Bucket("paopao-assets")
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.Bucket err: %v", err)
|
||||
response.ToErrorResponse(errcode.FileUploadFailed)
|
||||
return
|
||||
}
|
||||
|
||||
err = bucket.PutObject(ossSavePath, fileReader)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("bucket.PutObject err: %v", err)
|
||||
response.ToErrorResponse(errcode.FileUploadFailed)
|
||||
return
|
||||
}
|
||||
|
||||
// 构造附件Model
|
||||
attachment := &model.Attachment{
|
||||
FileSize: fileHeader.Size,
|
||||
Content: "https://" + global.AliossSetting.AliossDomain + "/" + ossSavePath,
|
||||
}
|
||||
|
||||
if userID, exists := c.Get("UID"); exists {
|
||||
attachment.UserID = userID.(int64)
|
||||
}
|
||||
|
||||
if uploadType == "public/image" || uploadType == "public/avatar" {
|
||||
attachment.Type = model.ATTACHMENT_TYPE_IMAGE
|
||||
|
||||
src, err := imaging.Decode(file)
|
||||
if err == nil {
|
||||
attachment.ImgWidth, attachment.ImgHeight = GetImageSize(src.Bounds())
|
||||
}
|
||||
}
|
||||
if uploadType == "public/video" {
|
||||
attachment.Type = model.ATTACHMENT_TYPE_VIDEO
|
||||
}
|
||||
if uploadType == "attachment" {
|
||||
attachment.Type = model.ATTACHMENT_TYPE_OTHER
|
||||
}
|
||||
|
||||
attachment, err = svc.CreateAttachment(attachment)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.CreateAttachment err: %v", err)
|
||||
response.ToErrorResponse(errcode.FileUploadFailed)
|
||||
}
|
||||
|
||||
response.ToResponse(attachment)
|
||||
}
|
||||
|
||||
func DownloadAttachmentPrecheck(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
svc := service.New(c)
|
||||
|
||||
contentID := convert.StrTo(c.Query("id")).MustInt64()
|
||||
// 加载content
|
||||
content, err := svc.GetPostContentByID(contentID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostContentByID err: %v", err)
|
||||
response.ToErrorResponse(errcode.InvalidDownloadReq)
|
||||
}
|
||||
user, _ := c.Get("USER")
|
||||
if content.Type == model.CONTENT_TYPE_CHARGE_ATTACHMENT {
|
||||
// 加载post
|
||||
post, err := svc.GetPost(content.PostID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPost err: %v", err)
|
||||
response.ToResponse(gin.H{
|
||||
"paid": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 发布者或管理员免费下载
|
||||
if post.UserID == user.(*model.User).ID || user.(*model.User).IsAdmin {
|
||||
response.ToResponse(gin.H{
|
||||
"paid": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检测是否有购买记录
|
||||
response.ToResponse(gin.H{
|
||||
"paid": svc.CheckPostAttachmentIsPaid(post.ID, user.(*model.User).ID),
|
||||
})
|
||||
return
|
||||
}
|
||||
response.ToResponse(gin.H{
|
||||
"paid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func DownloadAttachment(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
svc := service.New(c)
|
||||
|
||||
contentID := convert.StrTo(c.Query("id")).MustInt64()
|
||||
|
||||
// 加载content
|
||||
content, err := svc.GetPostContentByID(contentID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostContentByID err: %v", err)
|
||||
response.ToErrorResponse(errcode.InvalidDownloadReq)
|
||||
}
|
||||
|
||||
// 收费附件
|
||||
if content.Type == model.CONTENT_TYPE_CHARGE_ATTACHMENT {
|
||||
user, _ := c.Get("USER")
|
||||
|
||||
// 加载post
|
||||
post, err := svc.GetPost(content.PostID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPost err: %v", err)
|
||||
response.ToResponse(gin.H{
|
||||
"paid": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
paidFlag := false
|
||||
|
||||
// 发布者或管理员免费下载
|
||||
if post.UserID == user.(*model.User).ID || user.(*model.User).IsAdmin {
|
||||
paidFlag = true
|
||||
}
|
||||
|
||||
// 检测是否有购买记录
|
||||
if svc.CheckPostAttachmentIsPaid(post.ID, user.(*model.User).ID) {
|
||||
paidFlag = true
|
||||
}
|
||||
|
||||
if !paidFlag {
|
||||
// 未购买,则尝试购买
|
||||
err := svc.BuyPostAttachment(&model.Post{
|
||||
Model: &model.Model{
|
||||
ID: post.ID,
|
||||
},
|
||||
UserID: post.UserID,
|
||||
AttachmentPrice: post.AttachmentPrice,
|
||||
}, user.(*model.User))
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.BuyPostAttachment err: %v", err)
|
||||
if err == errcode.InsuffientDownloadMoney {
|
||||
|
||||
response.ToErrorResponse(errcode.InsuffientDownloadMoney)
|
||||
} else {
|
||||
|
||||
response.ToErrorResponse(errcode.DownloadExecFail)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始构造下载地址
|
||||
client, err := oss.New(global.AliossSetting.AliossEndpoint, global.AliossSetting.AliossAccessKeyID, global.AliossSetting.AliossAccessKeySecret)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("oss.New err: %v", err)
|
||||
response.ToErrorResponse(errcode.DownloadReqError)
|
||||
return
|
||||
}
|
||||
|
||||
bucket, err := client.Bucket("paopao-assets")
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.Bucket err: %v", err)
|
||||
response.ToErrorResponse(errcode.DownloadReqError)
|
||||
return
|
||||
}
|
||||
|
||||
// 签名
|
||||
objectKey := strings.Replace(content.Content, "https://"+global.AliossSetting.AliossDomain+"/", "", -1)
|
||||
signedURL, err := bucket.SignURL(objectKey, oss.HTTPGet, 60)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.SignURL err: %v", err)
|
||||
response.ToErrorResponse(errcode.DownloadReqError)
|
||||
return
|
||||
}
|
||||
ur, err := url.Parse(signedURL)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("url.Parse err: %v", err)
|
||||
response.ToErrorResponse(errcode.DownloadReqError)
|
||||
return
|
||||
}
|
||||
epath, err := url.PathUnescape(ur.Path)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("url.PathUnescape err: %v", err)
|
||||
response.ToErrorResponse(errcode.DownloadReqError)
|
||||
return
|
||||
}
|
||||
|
||||
ur.Path = epath
|
||||
ur.RawPath = epath
|
||||
response.ToResponse(ur.String())
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/internal/service"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
func GetPostComments(c *gin.Context) {
|
||||
|
||||
postID := convert.StrTo(c.Query("id")).MustInt64()
|
||||
response := app.NewResponse(c)
|
||||
|
||||
svc := service.New(c)
|
||||
contents, totalRows, err := svc.GetPostComments(postID, "id ASC", 0, 0)
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostComments err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCommentsFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponseList(contents, totalRows)
|
||||
}
|
||||
|
||||
func CreatePostComment(c *gin.Context) {
|
||||
param := service.CommentCreationReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
comment, err := svc.CreatePostComment(userID.(int64), param)
|
||||
|
||||
if err != nil {
|
||||
if err == errcode.MaxCommentCount {
|
||||
response.ToErrorResponse(errcode.MaxCommentCount)
|
||||
} else {
|
||||
global.Logger.Errorf("svc.CreatePostComment err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.CreateCommentFailed)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(comment)
|
||||
}
|
||||
|
||||
func DeletePostComment(c *gin.Context) {
|
||||
param := service.CommentDelReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
user, _ := c.Get("USER")
|
||||
svc := service.New(c)
|
||||
|
||||
comment, err := svc.GetPostComment(param.ID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostComment err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCommentFailed)
|
||||
return
|
||||
}
|
||||
|
||||
if user.(*model.User).ID != comment.UserID && !user.(*model.User).IsAdmin {
|
||||
response.ToErrorResponse(errcode.NoPermission)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
err = svc.DeletePostComment(comment)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.DeletePostComment err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.DeleteCommentFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
func CreatePostCommentReply(c *gin.Context) {
|
||||
param := service.CommentReplyCreationReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
user, _ := c.Get("USER")
|
||||
svc := service.New(c)
|
||||
|
||||
comment, err := svc.CreatePostCommentReply(param.CommentID, param.Content, user.(*model.User).ID, param.AtUserID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.CreatePostCommentReply err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.CreateReplyFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(comment)
|
||||
}
|
||||
|
||||
func DeletePostCommentReply(c *gin.Context) {
|
||||
param := service.ReplyDelReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user, _ := c.Get("USER")
|
||||
svc := service.New(c)
|
||||
|
||||
reply, err := svc.GetPostCommentReply(param.ID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostCommentReply err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetReplyFailed)
|
||||
return
|
||||
}
|
||||
|
||||
if user.(*model.User).ID != reply.UserID && !user.(*model.User).IsAdmin {
|
||||
response.ToErrorResponse(errcode.NoPermission)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
err = svc.DeletePostCommentReply(reply)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.DeletePostCommentReply err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.DeleteCommentFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"time"
|
||||
|
||||
"github.com/afocus/captcha"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/internal/service"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
"github.com/rocboss/paopao-api/pkg/util"
|
||||
)
|
||||
|
||||
const MAX_PHONE_CAPTCHA = 10
|
||||
|
||||
func Version(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
response.ToResponse(gin.H{
|
||||
"version": "PaoPao Service v1.0",
|
||||
})
|
||||
}
|
||||
|
||||
func SyncSearchIndex(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
svc := service.New(c)
|
||||
|
||||
user, _ := c.Get("USER")
|
||||
|
||||
if user.(*model.User).IsAdmin {
|
||||
go svc.PushPostsToSearch()
|
||||
}
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
func GetCaptcha(c *gin.Context) {
|
||||
cap := captcha.New()
|
||||
|
||||
if err := cap.SetFont("comic.ttf"); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
cap.SetSize(160, 64)
|
||||
cap.SetDisturbance(captcha.MEDIUM)
|
||||
cap.SetFrontColor(color.RGBA{0, 0, 0, 255})
|
||||
cap.SetBkgColor(color.RGBA{218, 240, 228, 255})
|
||||
img, password := cap.Create(6, captcha.NUM)
|
||||
emptyBuff := bytes.NewBuffer(nil)
|
||||
_ = png.Encode(emptyBuff, img)
|
||||
|
||||
key := util.EncodeMD5(uuid.Must(uuid.NewV4()).String())
|
||||
|
||||
// 五分钟有效期
|
||||
global.Redis.SetEX(c, "PaoPaoCaptcha:"+key, password, time.Minute*5)
|
||||
|
||||
response := app.NewResponse(c)
|
||||
response.ToResponse(gin.H{
|
||||
"id": key,
|
||||
"b64s": "data:image/png;base64," + base64.StdEncoding.EncodeToString(emptyBuff.Bytes()),
|
||||
})
|
||||
}
|
||||
|
||||
func PostCaptcha(c *gin.Context) {
|
||||
param := service.PhoneCaptchaReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
svc := service.New(c)
|
||||
|
||||
// 验证图片验证码
|
||||
if res, err := global.Redis.Get(c.Request.Context(), "PaoPaoCaptcha:"+param.ImgCaptchaID).Result(); err != nil || res != param.ImgCaptcha {
|
||||
response.ToErrorResponse(errcode.ErrorCaptchaPassword)
|
||||
return
|
||||
}
|
||||
global.Redis.Del(c.Request.Context(), "PaoPaoCaptcha:"+param.ImgCaptchaID).Result()
|
||||
|
||||
// 今日频次限制
|
||||
if res, _ := global.Redis.Get(c.Request.Context(), "PaoPaoSmsCaptcha:"+param.Phone).Result(); convert.StrTo(res).MustInt() >= MAX_PHONE_CAPTCHA {
|
||||
response.ToErrorResponse(errcode.TooManyPhoneCaptchaSend)
|
||||
return
|
||||
}
|
||||
|
||||
err := svc.SendPhoneCaptcha(param.Phone)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("app.SendPhoneCaptcha errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.GetPhoneCaptchaError)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/internal/service"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
func GetUnreadMsgCount(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
user := &model.User{}
|
||||
if u, exists := c.Get("USER"); exists {
|
||||
user = u.(*model.User)
|
||||
}
|
||||
svc := service.New(c)
|
||||
|
||||
count, _ := svc.GetUnreadCount(user.ID)
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
|
||||
func GetMessages(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
messages, totalRows, err := svc.GetMessages(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetMessages err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetMessagesFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponseList(messages, totalRows)
|
||||
}
|
||||
|
||||
func ReadMessage(c *gin.Context) {
|
||||
param := service.ReadMessageReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
err := svc.ReadMessage(param.ID, userID.(int64))
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.ReadMessage err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.ReadMessageFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/dao"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/internal/service"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
func GetPostList(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
q := &dao.QueryT{
|
||||
Query: c.Query("query"),
|
||||
Type: "search",
|
||||
}
|
||||
if c.Query("type") == "tag" {
|
||||
q.Type = "tag"
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
if q.Query == "" && q.Type == "search" {
|
||||
// 直接读库
|
||||
posts, err := svc.GetPostList(&service.PostListReq{
|
||||
Conditions: &model.ConditionsT{
|
||||
"ORDER": "is_top DESC, latest_replied_on DESC",
|
||||
},
|
||||
Offset: (app.GetPage(c) - 1) * app.GetPageSize(c),
|
||||
Limit: app.GetPageSize(c),
|
||||
})
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostList err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostsFailed)
|
||||
return
|
||||
}
|
||||
totalRows, _ := svc.GetPostCount(&model.ConditionsT{
|
||||
"ORDER": "latest_replied_on DESC",
|
||||
})
|
||||
|
||||
response.ToResponseList(posts, totalRows)
|
||||
} else {
|
||||
posts, totalRows, err := svc.GetPostListFromSearch(q, (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostListFromSearch err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostsFailed)
|
||||
return
|
||||
}
|
||||
response.ToResponseList(posts, totalRows)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPost(c *gin.Context) {
|
||||
postID := convert.StrTo(c.Query("id")).MustInt64()
|
||||
response := app.NewResponse(c)
|
||||
|
||||
svc := service.New(c)
|
||||
postFormated, err := svc.GetPost(postID)
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPost err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(postFormated)
|
||||
}
|
||||
|
||||
func CreatePost(c *gin.Context) {
|
||||
param := service.PostCreationReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
post, err := svc.CreatePost(userID.(int64), param)
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.CreatePost err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.CreatePostFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(post)
|
||||
}
|
||||
|
||||
func DeletePost(c *gin.Context) {
|
||||
param := service.PostDelReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user, _ := c.Get("USER")
|
||||
svc := service.New(c)
|
||||
|
||||
// 获取Post
|
||||
postFormated, err := svc.GetPost(param.ID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPost err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostFailed)
|
||||
return
|
||||
}
|
||||
|
||||
if postFormated.UserID != user.(*model.User).ID && !user.(*model.User).IsAdmin {
|
||||
response.ToErrorResponse(errcode.NoPermission)
|
||||
return
|
||||
}
|
||||
|
||||
err = svc.DeletePost(param.ID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.DeletePost err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.DeletePostFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
func GetPostStar(c *gin.Context) {
|
||||
postID := convert.StrTo(c.Query("id")).MustInt64()
|
||||
response := app.NewResponse(c)
|
||||
|
||||
svc := service.New(c)
|
||||
userID, _ := c.Get("UID")
|
||||
|
||||
_, err := svc.GetPostStar(postID, userID.(int64))
|
||||
if err != nil {
|
||||
response.ToResponse(gin.H{
|
||||
"status": false,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"status": true,
|
||||
})
|
||||
}
|
||||
|
||||
func PostStar(c *gin.Context) {
|
||||
param := service.PostStarReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
userID, _ := c.Get("UID")
|
||||
|
||||
status := false
|
||||
star, err := svc.GetPostStar(param.ID, userID.(int64))
|
||||
if err != nil {
|
||||
// 创建Star
|
||||
svc.CreatePostStar(param.ID, userID.(int64))
|
||||
status = true
|
||||
} else {
|
||||
// 取消Star
|
||||
svc.DeletePostStar(star)
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
|
||||
func GetPostCollection(c *gin.Context) {
|
||||
postID := convert.StrTo(c.Query("id")).MustInt64()
|
||||
response := app.NewResponse(c)
|
||||
|
||||
svc := service.New(c)
|
||||
userID, _ := c.Get("UID")
|
||||
|
||||
_, err := svc.GetPostCollection(postID, userID.(int64))
|
||||
if err != nil {
|
||||
response.ToResponse(gin.H{
|
||||
"status": false,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"status": true,
|
||||
})
|
||||
}
|
||||
|
||||
func PostCollection(c *gin.Context) {
|
||||
param := service.PostCollectionReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
userID, _ := c.Get("UID")
|
||||
|
||||
status := false
|
||||
collection, err := svc.GetPostCollection(param.ID, userID.(int64))
|
||||
if err != nil {
|
||||
// 创建collection
|
||||
svc.CreatePostCollection(param.ID, userID.(int64))
|
||||
status = true
|
||||
} else {
|
||||
// 取消Star
|
||||
svc.DeletePostCollection(collection)
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
|
||||
func LockPost(c *gin.Context) {
|
||||
param := service.PostLockReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user, _ := c.Get("USER")
|
||||
svc := service.New(c)
|
||||
|
||||
// 获取Post
|
||||
postFormated, err := svc.GetPost(param.ID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPost err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostFailed)
|
||||
return
|
||||
}
|
||||
|
||||
if postFormated.UserID != user.(*model.User).ID && !user.(*model.User).IsAdmin {
|
||||
response.ToErrorResponse(errcode.NoPermission)
|
||||
return
|
||||
}
|
||||
err = svc.LockPost(param.ID)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.LockPost err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.LockPostFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"lock_status": 1 - postFormated.IsLock,
|
||||
})
|
||||
}
|
||||
|
||||
func GetPostTags(c *gin.Context) {
|
||||
param := service.PostTagsReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
|
||||
tags, err := svc.GetPostTags(¶m)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostTags err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostTagsFailed)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
response.ToResponse(tags)
|
||||
}
|
@ -0,0 +1,547 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/internal/service"
|
||||
"github.com/rocboss/paopao-api/pkg/app"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
// 用户登录
|
||||
func Login(c *gin.Context) {
|
||||
param := service.AuthRequest{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
user, err := svc.DoLogin(¶m)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.DoLogin err: %v", err)
|
||||
response.ToErrorResponse(err.(*errcode.Error))
|
||||
return
|
||||
}
|
||||
|
||||
token, err := app.GenerateToken(user)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("app.GenerateToken err: %v", err)
|
||||
response.ToErrorResponse(errcode.UnauthorizedTokenGenerate)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"token": token,
|
||||
})
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
func Register(c *gin.Context) {
|
||||
|
||||
param := service.RegisterRequest{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
|
||||
// 用户名检查
|
||||
err := svc.ValidUsername(param.Username)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.Register err: %v", err)
|
||||
response.ToErrorResponse(err.(*errcode.Error))
|
||||
return
|
||||
}
|
||||
|
||||
// 密码检查
|
||||
err = svc.CheckPassword(param.Password)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.Register err: %v", err)
|
||||
response.ToErrorResponse(err.(*errcode.Error))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := svc.Register(
|
||||
param.Username,
|
||||
param.Password,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.Register err: %v", err)
|
||||
response.ToErrorResponse(errcode.UserRegisterFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户基本信息
|
||||
func GetUserInfo(c *gin.Context) {
|
||||
param := service.AuthRequest{}
|
||||
response := app.NewResponse(c)
|
||||
svc := service.New(c)
|
||||
|
||||
if username, exists := c.Get("USERNAME"); exists {
|
||||
param.Username = username.(string)
|
||||
}
|
||||
|
||||
user, err := svc.GetUserInfo(¶m)
|
||||
|
||||
if err != nil {
|
||||
response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
|
||||
return
|
||||
}
|
||||
phone := ""
|
||||
if user.Phone != "" && len(user.Phone) == 11 {
|
||||
phone = user.Phone[0:3] + "****" + user.Phone[7:]
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"id": user.ID,
|
||||
"nickname": user.Nickname,
|
||||
"username": user.Username,
|
||||
"status": user.Status,
|
||||
"avatar": user.Avatar,
|
||||
"balance": user.Balance,
|
||||
"phone": phone,
|
||||
"is_admin": user.IsAdmin,
|
||||
})
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
func ChangeUserPassword(c *gin.Context) {
|
||||
param := service.ChangePasswordReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
if u, exists := c.Get("USER"); exists {
|
||||
user = u.(*model.User)
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
|
||||
// 密码检查
|
||||
err := svc.CheckPassword(param.Password)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.Register err: %v", err)
|
||||
response.ToErrorResponse(err.(*errcode.Error))
|
||||
return
|
||||
}
|
||||
|
||||
// 旧密码校验
|
||||
if !svc.ValidPassword(user.Password, param.OldPassword, user.Salt) {
|
||||
response.ToErrorResponse(errcode.ErrorOldPassword)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新入库
|
||||
user.Password, user.Salt = svc.EncryptPasswordAndSalt(param.Password)
|
||||
svc.UpdateUserInfo(user)
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
// 修改昵称
|
||||
func ChangeNickname(c *gin.Context) {
|
||||
param := service.ChangeNicknameReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
if u, exists := c.Get("USER"); exists {
|
||||
user = u.(*model.User)
|
||||
}
|
||||
svc := service.New(c)
|
||||
|
||||
if utf8.RuneCountInString(param.Nickname) < 2 || utf8.RuneCountInString(param.Nickname) > 12 {
|
||||
response.ToErrorResponse(errcode.NicknameLengthLimit)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行绑定
|
||||
user.Nickname = param.Nickname
|
||||
svc.UpdateUserInfo(user)
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
// 修改头像
|
||||
func ChangeAvatar(c *gin.Context) {
|
||||
param := service.ChangeAvatarReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
if u, exists := c.Get("USER"); exists {
|
||||
user = u.(*model.User)
|
||||
}
|
||||
svc := service.New(c)
|
||||
|
||||
if strings.Index(param.Avatar, "https://"+global.AliossSetting.AliossDomain) != 0 {
|
||||
response.ToErrorResponse(errcode.InvalidParams)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行绑定
|
||||
user.Avatar = param.Avatar
|
||||
svc.UpdateUserInfo(user)
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
// 用户绑定手机号
|
||||
func BindUserPhone(c *gin.Context) {
|
||||
param := service.UserPhoneBindReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
user := &model.User{}
|
||||
if u, exists := c.Get("USER"); exists {
|
||||
user = u.(*model.User)
|
||||
}
|
||||
svc := service.New(c)
|
||||
|
||||
// 手机重复性检查
|
||||
if svc.CheckPhoneExist(user.ID, param.Phone) {
|
||||
response.ToErrorResponse(errcode.ExistedUserPhone)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证短信验证码
|
||||
err := svc.CheckPhoneCaptcha(param.Phone, param.Captcha)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.CheckPhoneCaptcha err: %v\n", err)
|
||||
response.ToErrorResponse(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 执行绑定
|
||||
user.Phone = param.Phone
|
||||
svc.UpdateUserInfo(user)
|
||||
|
||||
response.ToResponse(nil)
|
||||
}
|
||||
|
||||
func GetUserProfile(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
username := c.Query("username")
|
||||
|
||||
svc := service.New(c)
|
||||
user, err := svc.GetUserByUsername(username)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetUserByUsername err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.NoExistUsername)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"id": user.ID,
|
||||
"nickname": user.Nickname,
|
||||
"username": user.Username,
|
||||
"status": user.Status,
|
||||
"avatar": user.Avatar,
|
||||
"is_admin": user.IsAdmin,
|
||||
})
|
||||
}
|
||||
|
||||
func GetUserPosts(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
username := c.Query("username")
|
||||
|
||||
svc := service.New(c)
|
||||
user, err := svc.GetUserByUsername(username)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetUserByUsername err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.NoExistUsername)
|
||||
return
|
||||
}
|
||||
|
||||
conditions := &model.ConditionsT{
|
||||
"user_id": user.ID,
|
||||
"ORDER": "latest_replied_on DESC",
|
||||
}
|
||||
|
||||
posts, err := svc.GetPostList(&service.PostListReq{
|
||||
Conditions: conditions,
|
||||
Offset: (app.GetPage(c) - 1) * app.GetPageSize(c),
|
||||
Limit: app.GetPageSize(c),
|
||||
})
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetPostList err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetPostsFailed)
|
||||
return
|
||||
}
|
||||
totalRows, _ := svc.GetPostCount(conditions)
|
||||
|
||||
response.ToResponseList(posts, totalRows)
|
||||
}
|
||||
|
||||
func GetUserCollections(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
posts, totalRows, err := svc.GetUserCollections(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetUserCollections err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCollectionsFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponseList(posts, totalRows)
|
||||
}
|
||||
|
||||
func GetUserStars(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
posts, totalRows, err := svc.GetUserStars(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetUserStars err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCollectionsFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponseList(posts, totalRows)
|
||||
}
|
||||
|
||||
func GetSuggestUsers(c *gin.Context) {
|
||||
keyword := c.Query("k")
|
||||
response := app.NewResponse(c)
|
||||
|
||||
svc := service.New(c)
|
||||
usernames, err := svc.GetSuggestUsers(keyword)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetSuggestUsers err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCollectionsFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(usernames)
|
||||
}
|
||||
|
||||
func GetSuggestTags(c *gin.Context) {
|
||||
keyword := c.Query("k")
|
||||
response := app.NewResponse(c)
|
||||
|
||||
svc := service.New(c)
|
||||
tags, err := svc.GetSuggestTags(keyword)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetSuggestTags err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCollectionsFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(tags)
|
||||
}
|
||||
|
||||
func GetUserRechargeLink(c *gin.Context) {
|
||||
param := service.RechargeReq{}
|
||||
response := app.NewResponse(c)
|
||||
valid, errs := app.BindAndValid(c, ¶m)
|
||||
if !valid {
|
||||
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
|
||||
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
|
||||
return
|
||||
}
|
||||
|
||||
// 下单
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
recharge, err := svc.CreateRecharge(userID.(int64), param.Amount)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.CreateRecharge err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
client, err := alipay.New(global.AppSetting.AlipayAppID, global.AppSetting.AlipayPrivateKey, true)
|
||||
// 将 key 的验证调整到初始化阶段
|
||||
if err != nil {
|
||||
global.Logger.Errorf("alipay.New err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
err = client.LoadAppPublicCertFromFile("configs/alipayAppCertPublicKey.crt") // 加载应用公钥证书
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.LoadAppPublicCertFromFile err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
err = client.LoadAliPayRootCertFromFile("configs/alipayRootCert.crt") // 加载支付宝根证书
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.LoadAliPayRootCertFromFile err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
err = client.LoadAliPayPublicCertFromFile("configs/alipayCertPublicKey_RSA2.crt") // 加载支付宝公钥证书
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.LoadAliPayPublicCertFromFile err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
p := alipay.TradePreCreate{}
|
||||
p.OutTradeNo = fmt.Sprintf("%d", recharge.ID)
|
||||
p.Subject = "PaoPao用户钱包充值"
|
||||
p.TotalAmount = fmt.Sprintf("%.2f", float64(recharge.Amount)/100.0)
|
||||
p.NotifyURL = "https://" + c.Request.Host + "/alipay/notify"
|
||||
|
||||
rsp, err := client.TradePreCreate(p)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.TradePreCreate err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
if rsp.Content.Code != alipay.CodeSuccess {
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"id": recharge.ID,
|
||||
"pay": rsp.Content.QRCode,
|
||||
})
|
||||
}
|
||||
|
||||
func GetUserRechargeResult(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
id := c.Query("id")
|
||||
userID, _ := c.Get("UID")
|
||||
|
||||
svc := service.New(c)
|
||||
recharge, err := svc.GetRechargeByID(convert.StrTo(id).MustInt64())
|
||||
if err != nil {
|
||||
response.ToErrorResponse(errcode.GetRechargeFailed)
|
||||
return
|
||||
}
|
||||
|
||||
if recharge.UserID != userID.(int64) {
|
||||
response.ToErrorResponse(errcode.GetRechargeFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponse(gin.H{
|
||||
"id": recharge.ID,
|
||||
"status": recharge.TradeStatus,
|
||||
})
|
||||
}
|
||||
|
||||
func AlipayNotify(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
c.Request.ParseForm()
|
||||
|
||||
aliClient, err := alipay.New(global.AppSetting.AlipayAppID, global.AppSetting.AlipayPrivateKey, true)
|
||||
// 将 key 的验证调整到初始化阶段
|
||||
if err != nil {
|
||||
global.Logger.Errorf("alipay.New err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
err = aliClient.LoadAppPublicCertFromFile("configs/alipayAppCertPublicKey.crt") // 加载应用公钥证书
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.LoadAppPublicCertFromFile err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
err = aliClient.LoadAliPayRootCertFromFile("configs/alipayRootCert.crt") // 加载支付宝根证书
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.LoadAliPayRootCertFromFile err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
err = aliClient.LoadAliPayPublicCertFromFile("configs/alipayCertPublicKey_RSA2.crt") // 加载支付宝公钥证书
|
||||
if err != nil {
|
||||
global.Logger.Errorf("client.LoadAliPayPublicCertFromFile err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeReqFail)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = aliClient.GetTradeNotification(c.Request)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("aliClient.GetTradeNotification err: %v\n", err)
|
||||
global.Logger.Infoln(c.Request.Form)
|
||||
response.ToErrorResponse(errcode.RechargeNotifyError)
|
||||
return
|
||||
}
|
||||
|
||||
svc := service.New(c)
|
||||
id := c.Request.Form.Get("out_trade_no")
|
||||
tradeNo := c.Request.Form.Get("trade_no")
|
||||
tradeStatus := c.Request.Form.Get("trade_status")
|
||||
if tradeStatus == "TRADE_SUCCESS" {
|
||||
// 交易支付成功
|
||||
err = svc.FinishRecharge(convert.StrTo(id).MustInt64(), tradeNo)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.FinishRecharge err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.RechargeNotifyError)
|
||||
return
|
||||
}
|
||||
}
|
||||
response.Ctx.String(http.StatusOK, "success")
|
||||
}
|
||||
|
||||
func GetUserWalletBills(c *gin.Context) {
|
||||
response := app.NewResponse(c)
|
||||
|
||||
userID, _ := c.Get("UID")
|
||||
svc := service.New(c)
|
||||
bills, totalRows, err := svc.GetUserWalletBills(userID.(int64), (app.GetPage(c)-1)*app.GetPageSize(c), app.GetPageSize(c))
|
||||
|
||||
if err != nil {
|
||||
global.Logger.Errorf("svc.GetUserWalletBills err: %v\n", err)
|
||||
response.ToErrorResponse(errcode.GetCollectionsFailed)
|
||||
return
|
||||
}
|
||||
|
||||
response.ToResponseList(bills, totalRows)
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/internal/middleware"
|
||||
"github.com/rocboss/paopao-api/internal/routers/api"
|
||||
)
|
||||
|
||||
func NewRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.HandleMethodNotAllowed = true
|
||||
r.Use(gin.Logger())
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
// 跨域配置
|
||||
corsConfig := cors.DefaultConfig()
|
||||
corsConfig.AllowAllOrigins = true
|
||||
corsConfig.AddAllowHeaders("Authorization")
|
||||
r.Use(cors.New(corsConfig))
|
||||
|
||||
// 获取version
|
||||
r.GET("/", api.Version)
|
||||
|
||||
// 用户登录
|
||||
r.POST("/auth/login", api.Login)
|
||||
|
||||
// 用户注册
|
||||
r.POST("/auth/register", api.Register)
|
||||
|
||||
// 获取验证码
|
||||
r.GET("/captcha", api.GetCaptcha)
|
||||
|
||||
// 发送验证码
|
||||
r.POST("/captcha", api.PostCaptcha)
|
||||
|
||||
// 支付宝回调
|
||||
r.POST("/alipay/notify", api.AlipayNotify)
|
||||
|
||||
// 无鉴权路由组
|
||||
noAuthApi := r.Group("/")
|
||||
{
|
||||
// 获取广场流
|
||||
noAuthApi.GET("/posts", api.GetPostList)
|
||||
|
||||
// 获取动态详情
|
||||
noAuthApi.GET("/post", api.GetPost)
|
||||
|
||||
// 获取动态评论
|
||||
noAuthApi.GET("/post/comments", api.GetPostComments)
|
||||
|
||||
// 获取话题列表
|
||||
noAuthApi.GET("/tags", api.GetPostTags)
|
||||
|
||||
// 获取用户基本信息
|
||||
noAuthApi.GET("/user/profile", api.GetUserProfile)
|
||||
|
||||
// 获取用户动态列表
|
||||
noAuthApi.GET("/user/posts", api.GetUserPosts)
|
||||
}
|
||||
|
||||
// 鉴权路由组
|
||||
authApi := r.Group("/").Use(middleware.JWT())
|
||||
privApi := r.Group("/").Use(middleware.JWT()).Use(middleware.Priv())
|
||||
{
|
||||
// 同步索引
|
||||
authApi.GET("/sync/index", api.SyncSearchIndex)
|
||||
|
||||
// 获取当前用户信息
|
||||
authApi.GET("/user/info", api.GetUserInfo)
|
||||
|
||||
// 获取当前用户未读消息数量
|
||||
authApi.GET("/user/msgcount/unread", api.GetUnreadMsgCount)
|
||||
|
||||
// 获取消息列表
|
||||
authApi.GET("/user/messages", api.GetMessages)
|
||||
|
||||
// 标记消息已读
|
||||
authApi.POST("/user/message/read", api.ReadMessage)
|
||||
|
||||
// 获取用户收藏列表
|
||||
authApi.GET("/user/collections", api.GetUserCollections)
|
||||
|
||||
// 获取用户点赞列表
|
||||
authApi.GET("/user/stars", api.GetUserStars)
|
||||
|
||||
// 绑定用户手机号
|
||||
authApi.POST("/user/phone", api.BindUserPhone)
|
||||
|
||||
// 修改密码
|
||||
authApi.POST("/user/password", api.ChangeUserPassword)
|
||||
|
||||
// 修改昵称
|
||||
authApi.POST("/user/nickname", api.ChangeNickname)
|
||||
|
||||
// 修改头像
|
||||
authApi.POST("/user/avatar", api.ChangeAvatar)
|
||||
|
||||
// 检索用户
|
||||
authApi.GET("/suggest/users", api.GetSuggestUsers)
|
||||
|
||||
// 检索标签
|
||||
authApi.GET("/suggest/tags", api.GetSuggestTags)
|
||||
|
||||
// 用户充值
|
||||
authApi.POST("/user/recharge", api.GetUserRechargeLink)
|
||||
|
||||
// 用户充值
|
||||
authApi.GET("/user/recharge", api.GetUserRechargeResult)
|
||||
|
||||
// 获取用户账单
|
||||
authApi.GET("/user/wallet/bills", api.GetUserWalletBills)
|
||||
|
||||
// 上传资源
|
||||
privApi.POST("/attachment", api.UploadAttachment)
|
||||
|
||||
// 下载资源预检
|
||||
privApi.GET("/attachment/precheck", api.DownloadAttachmentPrecheck)
|
||||
|
||||
// 下载资源
|
||||
privApi.GET("/attachment", api.DownloadAttachment)
|
||||
|
||||
// 发布动态
|
||||
privApi.POST("/post", api.CreatePost)
|
||||
|
||||
// 删除动态
|
||||
privApi.DELETE("/post", api.DeletePost)
|
||||
|
||||
// 获取动态点赞状态
|
||||
authApi.GET("/post/star", api.GetPostStar)
|
||||
|
||||
// 动态点赞操作
|
||||
privApi.POST("/post/star", api.PostStar)
|
||||
|
||||
// 获取动态收藏状态
|
||||
authApi.GET("/post/collection", api.GetPostCollection)
|
||||
|
||||
// 动态收藏操作
|
||||
privApi.POST("/post/collection", api.PostCollection)
|
||||
|
||||
// 锁定动态
|
||||
privApi.POST("/post/lock", api.LockPost)
|
||||
|
||||
// 发布动态评论
|
||||
privApi.POST("/post/comment", api.CreatePostComment)
|
||||
|
||||
// 删除动态评论
|
||||
privApi.DELETE("/post/comment", api.DeletePostComment)
|
||||
|
||||
// 发布评论回复
|
||||
privApi.POST("/post/comment/reply", api.CreatePostCommentReply)
|
||||
|
||||
// 删除评论回复
|
||||
privApi.DELETE("/post/comment/reply", api.DeletePostCommentReply)
|
||||
|
||||
}
|
||||
// 默认404
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"code": 404,
|
||||
"msg": "Not Found",
|
||||
})
|
||||
})
|
||||
// 默认405
|
||||
r.NoMethod(func(c *gin.Context) {
|
||||
c.JSON(http.StatusMethodNotAllowed, gin.H{
|
||||
"code": 405,
|
||||
"msg": "Method Not Allowed",
|
||||
})
|
||||
})
|
||||
return r
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package service
|
||||
|
||||
import "github.com/rocboss/paopao-api/internal/model"
|
||||
|
||||
func (svc *Service) CreateAttachment(attachment *model.Attachment) (*model.Attachment, error) {
|
||||
return svc.dao.CreateAttachment(attachment)
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultAvatars = []string{
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/zoe.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/william.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/walter.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/thomas.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/taylor.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/sophia.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/sam.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/ryan.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/ruby.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/quinn.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/paul.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/owen.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/olivia.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/norman.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/nora.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/natalie.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/naomi.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/miley.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/mike.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/lucas.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/kylie.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/julia.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/joshua.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/john.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/jane.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/jackson.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/ivy.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/isaac.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/henry.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/harry.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/harold.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/hanna.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/grace.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/george.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/freddy.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/frank.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/finn.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/emma.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/emily.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/edward.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/clara.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/claire.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/chloe.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/audrey.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/arthur.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/anna.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/andy.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/alfred.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/alexa.png",
|
||||
"https://paopao-assets.oss-cn-shanghai.aliyuncs.com/public/avatar/default/abigail.png",
|
||||
}
|
||||
|
||||
func (s *Service) GetRandomAvatar() string {
|
||||
rand.Seed(time.Now().UnixMicro())
|
||||
return defaultAvatars[rand.Intn(len(defaultAvatars))]
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
"github.com/rocboss/paopao-api/pkg/util"
|
||||
)
|
||||
|
||||
type CommentCreationReq struct {
|
||||
PostID int64 `json:"post_id" binding:"required"`
|
||||
Contents []*PostContentItem `json:"contents" binding:"required"`
|
||||
Users []string `json:"users" binding:"required"`
|
||||
}
|
||||
type CommentReplyCreationReq struct {
|
||||
CommentID int64 `json:"comment_id" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
AtUserID int64 `json:"at_user_id"`
|
||||
}
|
||||
|
||||
type CommentDelReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
type ReplyDelReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostComments(postID int64, sort string, offset, limit int) ([]*model.CommentFormated, int64, error) {
|
||||
conditions := &model.ConditionsT{
|
||||
"post_id": postID,
|
||||
"ORDER": sort,
|
||||
}
|
||||
comments, err := svc.dao.GetComments(conditions, offset, limit)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
userIDs := []int64{}
|
||||
commentIDs := []int64{}
|
||||
for _, comment := range comments {
|
||||
userIDs = append(userIDs, comment.UserID)
|
||||
commentIDs = append(commentIDs, comment.ID)
|
||||
}
|
||||
|
||||
users, err := svc.dao.GetUsersByIDs(userIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
contents, err := svc.dao.GetCommentContentsByIDs(commentIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
replies, err := svc.dao.GetCommentRepliesByID(commentIDs)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
commentsFormated := []*model.CommentFormated{}
|
||||
for _, comment := range comments {
|
||||
commentFormated := comment.Format()
|
||||
for _, content := range contents {
|
||||
if content.CommentID == comment.ID {
|
||||
commentFormated.Contents = append(commentFormated.Contents, content)
|
||||
}
|
||||
}
|
||||
for _, reply := range replies {
|
||||
if reply.CommentID == commentFormated.ID {
|
||||
commentFormated.Replies = append(commentFormated.Replies, reply)
|
||||
}
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.ID == comment.UserID {
|
||||
commentFormated.User = user.Format()
|
||||
}
|
||||
}
|
||||
|
||||
commentsFormated = append(commentsFormated, commentFormated)
|
||||
}
|
||||
|
||||
// 获取总量
|
||||
totalRows, _ := svc.dao.GetCommentCount(conditions)
|
||||
|
||||
return commentsFormated, totalRows, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CreatePostComment(userID int64, param CommentCreationReq) (*model.Comment, error) {
|
||||
// 加载Post
|
||||
post, err := svc.dao.GetPostByID(param.PostID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if post.CommentCount >= global.AppSetting.MaxCommentCount {
|
||||
return nil, errcode.MaxCommentCount
|
||||
}
|
||||
ip := svc.ctx.ClientIP()
|
||||
comment := &model.Comment{
|
||||
PostID: post.ID,
|
||||
UserID: userID,
|
||||
IP: ip,
|
||||
IPLoc: util.GetIPLoc(ip),
|
||||
}
|
||||
comment, err = svc.dao.CreateComment(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range param.Contents {
|
||||
// 检查附件是否是本站资源
|
||||
if item.Type == model.CONTENT_TYPE_IMAGE || item.Type == model.CONTENT_TYPE_VIDEO || item.Type == model.CONTENT_TYPE_ATTACHMENT {
|
||||
if strings.Index(item.Content, "https://"+global.AliossSetting.AliossDomain) != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
postContent := &model.CommentContent{
|
||||
CommentID: comment.ID,
|
||||
UserID: userID,
|
||||
Content: item.Content,
|
||||
Type: item.Type,
|
||||
Sort: item.Sort,
|
||||
}
|
||||
svc.dao.CreateCommentContent(postContent)
|
||||
}
|
||||
|
||||
// 更新Post回复数
|
||||
post.CommentCount++
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
// 创建用户消息提醒
|
||||
postMaster, err := svc.dao.GetUserByID(post.UserID)
|
||||
if err == nil && postMaster.ID != userID {
|
||||
go svc.dao.CreateMessage(&model.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: postMaster.ID,
|
||||
Type: model.MESSAGE_COMMENT,
|
||||
Breif: "在泡泡中评论了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
})
|
||||
}
|
||||
for _, u := range param.Users {
|
||||
user, err := svc.dao.GetUserByUsername(u)
|
||||
if err != nil || user.ID == userID || user.ID == postMaster.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建消息提醒
|
||||
go svc.dao.CreateMessage(&model.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: user.ID,
|
||||
Type: model.MESSAGE_COMMENT,
|
||||
Breif: "在泡泡评论中@了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostComment(id int64) (*model.Comment, error) {
|
||||
return svc.dao.GetCommentByID(id)
|
||||
}
|
||||
|
||||
func (svc *Service) DeletePostComment(comment *model.Comment) error {
|
||||
// 加载post
|
||||
post, err := svc.dao.GetPostByID(comment.PostID)
|
||||
if err == nil {
|
||||
// 更新post回复数
|
||||
post.CommentCount--
|
||||
svc.dao.UpdatePost(post)
|
||||
}
|
||||
|
||||
return svc.dao.DeleteComment(comment)
|
||||
}
|
||||
|
||||
func (svc *Service) CreatePostCommentReply(commentID int64, content string, userID, atUserID int64) (*model.CommentReply, error) {
|
||||
// 加载Comment
|
||||
comment, err := svc.dao.GetCommentByID(commentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加载comment的post
|
||||
post, err := svc.dao.GetPostByID(comment.PostID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if post.CommentCount >= global.AppSetting.MaxCommentCount {
|
||||
return nil, errcode.MaxCommentCount
|
||||
}
|
||||
|
||||
if userID == atUserID {
|
||||
atUserID = 0
|
||||
}
|
||||
|
||||
if atUserID > 0 {
|
||||
// 检测目前用户是否存在
|
||||
users, _ := svc.dao.GetUsersByIDs([]int64{atUserID})
|
||||
if len(users) == 0 {
|
||||
atUserID = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 创建评论
|
||||
ip := svc.ctx.ClientIP()
|
||||
reply := &model.CommentReply{
|
||||
CommentID: commentID,
|
||||
UserID: userID,
|
||||
Content: content,
|
||||
AtUserID: atUserID,
|
||||
IP: ip,
|
||||
IPLoc: util.GetIPLoc(ip),
|
||||
}
|
||||
|
||||
reply, err = svc.dao.CreateCommentReply(reply)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新Post回复数
|
||||
post.CommentCount++
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
// 创建用户消息提醒
|
||||
commentMaster, err := svc.dao.GetUserByID(comment.UserID)
|
||||
if err == nil && commentMaster.ID != userID {
|
||||
go svc.dao.CreateMessage(&model.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: commentMaster.ID,
|
||||
Type: model.MESSAGE_REPLY,
|
||||
Breif: "在泡泡评论下回复了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
ReplyID: reply.ID,
|
||||
})
|
||||
}
|
||||
postMaster, err := svc.dao.GetUserByID(post.UserID)
|
||||
if err == nil && postMaster.ID != userID && commentMaster.ID != postMaster.ID {
|
||||
go svc.dao.CreateMessage(&model.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: postMaster.ID,
|
||||
Type: model.MESSAGE_REPLY,
|
||||
Breif: "在泡泡评论下发布了新回复",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
ReplyID: reply.ID,
|
||||
})
|
||||
}
|
||||
if atUserID > 0 {
|
||||
user, err := svc.dao.GetUserByID(atUserID)
|
||||
if err == nil && user.ID != userID && commentMaster.ID != user.ID && postMaster.ID != user.ID {
|
||||
// 创建消息提醒
|
||||
go svc.dao.CreateMessage(&model.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: user.ID,
|
||||
Type: model.MESSAGE_REPLY,
|
||||
Breif: "在泡泡评论的回复中@了你",
|
||||
PostID: post.ID,
|
||||
CommentID: comment.ID,
|
||||
ReplyID: reply.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostCommentReply(id int64) (*model.CommentReply, error) {
|
||||
return svc.dao.GetCommentReplyByID(id)
|
||||
}
|
||||
|
||||
func (svc *Service) DeletePostCommentReply(reply *model.CommentReply) error {
|
||||
err := svc.dao.DeleteCommentReply(reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 加载Comment
|
||||
comment, err := svc.dao.GetCommentByID(reply.CommentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 加载comment的post
|
||||
post, err := svc.dao.GetPostByID(comment.PostID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新Post回复数
|
||||
post.CommentCount--
|
||||
post.LatestRepliedOn = time.Now().Unix()
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
type ReadMessageReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
func (svc *Service) CreateMessage(msg *model.Message) (*model.Message, error) {
|
||||
return svc.dao.CreateMessage(msg)
|
||||
}
|
||||
|
||||
func (svc *Service) GetUnreadCount(userID int64) (int64, error) {
|
||||
return svc.dao.GetUnreadCount(userID)
|
||||
}
|
||||
func (svc *Service) ReadMessage(id, userID int64) error {
|
||||
// 获取message
|
||||
message, err := svc.dao.GetMessageByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if message.ReceiverUserID != userID {
|
||||
return errcode.NoPermission
|
||||
}
|
||||
|
||||
// 已读消息
|
||||
return svc.dao.ReadMessage(message)
|
||||
}
|
||||
|
||||
func (svc *Service) GetMessages(userID int64, offset, limit int) ([]*model.MessageFormated, int64, error) {
|
||||
conditions := &model.ConditionsT{
|
||||
"receiver_user_id": userID,
|
||||
"ORDER": "id DESC",
|
||||
}
|
||||
messages, err := svc.dao.GetMessages(conditions, offset, limit)
|
||||
|
||||
for _, mf := range messages {
|
||||
|
||||
if mf.SenderUserID > 0 {
|
||||
user, err := svc.dao.GetUserByID(mf.SenderUserID)
|
||||
if err == nil {
|
||||
mf.SenderUser = user.Format()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if mf.PostID > 0 {
|
||||
post, err := svc.GetPost(mf.PostID)
|
||||
if err == nil {
|
||||
mf.Post = post
|
||||
if mf.CommentID > 0 {
|
||||
comment, err := svc.GetPostComment(mf.CommentID)
|
||||
|
||||
if err == nil {
|
||||
mf.Comment = comment
|
||||
|
||||
if mf.ReplyID > 0 {
|
||||
reply, err := svc.GetPostCommentReply(mf.ReplyID)
|
||||
if err == nil {
|
||||
mf.Reply = reply
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取总量
|
||||
totalRows, _ := svc.dao.GetMessageCount(conditions)
|
||||
|
||||
return messages, totalRows, nil
|
||||
}
|
@ -0,0 +1,594 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/dao"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/util"
|
||||
"github.com/rocboss/paopao-api/pkg/zinc"
|
||||
)
|
||||
|
||||
type TagType string
|
||||
|
||||
const TagTypeHot TagType = "hot"
|
||||
const TagTypeNew TagType = "new"
|
||||
|
||||
type PostListReq struct {
|
||||
Conditions *model.ConditionsT
|
||||
Offset int
|
||||
Limit int
|
||||
}
|
||||
type PostTagsReq struct {
|
||||
Type TagType `json:"type" form:"type" binding:"required"`
|
||||
Num int `json:"num" form:"num" binding:"required"`
|
||||
}
|
||||
|
||||
type PostCreationReq struct {
|
||||
Contents []*PostContentItem `json:"contents" binding:"required"`
|
||||
Tags []string `json:"tags" binding:"required"`
|
||||
Users []string `json:"users" binding:"required"`
|
||||
AttachmentPrice int64 `json:"attachment_price"`
|
||||
}
|
||||
|
||||
type PostDelReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
type PostLockReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
type PostStarReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
type PostCollectionReq struct {
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
}
|
||||
type PostContentItem struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
Type model.PostContentT `json:"type" binding:"required"`
|
||||
Sort int64 `json:"sort" binding:"required"`
|
||||
}
|
||||
|
||||
func (svc *Service) CreatePost(userID int64, param PostCreationReq) (*model.Post, error) {
|
||||
ip := svc.ctx.ClientIP()
|
||||
|
||||
post := &model.Post{
|
||||
UserID: userID,
|
||||
Tags: strings.Join(param.Tags, ","),
|
||||
IP: ip,
|
||||
IPLoc: util.GetIPLoc(ip),
|
||||
AttachmentPrice: param.AttachmentPrice,
|
||||
}
|
||||
post, err := svc.dao.CreatePost(post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建标签
|
||||
for _, t := range param.Tags {
|
||||
tag := &model.Tag{
|
||||
UserID: userID,
|
||||
Tag: t,
|
||||
}
|
||||
svc.dao.CreateTag(tag)
|
||||
}
|
||||
|
||||
for _, item := range param.Contents {
|
||||
|
||||
// 检查附件是否是本站资源
|
||||
if item.Type == model.CONTENT_TYPE_IMAGE || item.Type == model.CONTENT_TYPE_VIDEO || item.Type == model.CONTENT_TYPE_ATTACHMENT {
|
||||
if strings.Index(item.Content, "https://"+global.AliossSetting.AliossDomain) != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 检查链接是否合法
|
||||
if item.Type == model.CONTENT_TYPE_LINK {
|
||||
if strings.Index(item.Content, "http://") != 0 && strings.Index(item.Content, "https://") != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if item.Type == model.CONTENT_TYPE_ATTACHMENT && param.AttachmentPrice > 0 {
|
||||
item.Type = model.CONTENT_TYPE_CHARGE_ATTACHMENT
|
||||
}
|
||||
|
||||
postContent := &model.PostContent{
|
||||
PostID: post.ID,
|
||||
UserID: userID,
|
||||
Content: item.Content,
|
||||
Type: item.Type,
|
||||
Sort: item.Sort,
|
||||
}
|
||||
svc.dao.CreatePostContent(postContent)
|
||||
}
|
||||
|
||||
// 推送Search
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
// 创建用户消息提醒
|
||||
for _, u := range param.Users {
|
||||
user, err := svc.dao.GetUserByUsername(u)
|
||||
if err != nil || user.ID == userID {
|
||||
continue
|
||||
}
|
||||
|
||||
// 创建消息提醒
|
||||
go svc.dao.CreateMessage(&model.Message{
|
||||
SenderUserID: userID,
|
||||
ReceiverUserID: user.ID,
|
||||
Type: model.MESSAGE_POST,
|
||||
Breif: "在新发布的泡泡动态中@了你",
|
||||
PostID: post.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
func (svc *Service) DeletePost(id int64) error {
|
||||
post, _ := svc.dao.GetPostByID(id)
|
||||
|
||||
// tag删除
|
||||
tags := strings.Split(post.Tags, ",")
|
||||
for _, t := range tags {
|
||||
|
||||
tag := &model.Tag{
|
||||
Tag: t,
|
||||
}
|
||||
svc.dao.DeleteTag(tag)
|
||||
}
|
||||
|
||||
err := svc.dao.DeletePost(post)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除索引
|
||||
go svc.DeleteSearchPost(post)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) LockPost(id int64) error {
|
||||
post, _ := svc.dao.GetPostByID(id)
|
||||
|
||||
err := svc.dao.LockPost(post)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostStar(postID, userID int64) (*model.PostStar, error) {
|
||||
return svc.dao.GetUserPostStar(postID, userID)
|
||||
}
|
||||
|
||||
func (svc *Service) CreatePostStar(postID, userID int64) (*model.PostStar, error) {
|
||||
// 加载Post
|
||||
post, err := svc.dao.GetPostByID(postID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
star, err := svc.dao.CreatePostStar(postID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新Post点赞数
|
||||
post.UpvoteCount++
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
return star, nil
|
||||
}
|
||||
|
||||
func (svc *Service) DeletePostStar(star *model.PostStar) error {
|
||||
err := svc.dao.DeletePostStar(star)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 加载Post
|
||||
post, err := svc.dao.GetPostByID(star.PostID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新Post点赞数
|
||||
post.UpvoteCount--
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostCollection(postID, userID int64) (*model.PostCollection, error) {
|
||||
return svc.dao.GetUserPostCollection(postID, userID)
|
||||
}
|
||||
|
||||
func (svc *Service) CreatePostCollection(postID, userID int64) (*model.PostCollection, error) {
|
||||
// 加载Post
|
||||
post, err := svc.dao.GetPostByID(postID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collection, err := svc.dao.CreatePostCollection(postID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新Post点赞数
|
||||
post.CollectionCount++
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
func (svc *Service) DeletePostCollection(collection *model.PostCollection) error {
|
||||
err := svc.dao.DeletePostCollection(collection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 加载Post
|
||||
post, err := svc.dao.GetPostByID(collection.PostID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 更新Post点赞数
|
||||
post.CollectionCount--
|
||||
svc.dao.UpdatePost(post)
|
||||
|
||||
// 更新索引
|
||||
go svc.PushPostToSearch(post)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPost(id int64) (*model.PostFormated, error) {
|
||||
post, err := svc.dao.GetPostByID(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
postContents, err := svc.dao.GetPostContentsByIDs([]int64{post.ID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users, err := svc.dao.GetUsersByIDs([]int64{post.UserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 数据整合
|
||||
postFormated := post.Format()
|
||||
for _, user := range users {
|
||||
postFormated.User = user.Format()
|
||||
}
|
||||
for _, content := range postContents {
|
||||
if content.PostID == post.ID {
|
||||
postFormated.Contents = append(postFormated.Contents, content.Format())
|
||||
}
|
||||
}
|
||||
return postFormated, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostContentByID(id int64) (*model.PostContent, error) {
|
||||
return svc.dao.GetPostContentByID(id)
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostList(req *PostListReq) ([]*model.PostFormated, error) {
|
||||
posts, err := svc.dao.GetPosts(req.Conditions, req.Offset, req.Limit)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc.FormatPosts(posts)
|
||||
}
|
||||
|
||||
func (svc *Service) FormatPosts(posts []*model.Post) ([]*model.PostFormated, error) {
|
||||
postIds := []int64{}
|
||||
userIds := []int64{}
|
||||
for _, post := range posts {
|
||||
postIds = append(postIds, post.ID)
|
||||
userIds = append(userIds, post.UserID)
|
||||
}
|
||||
|
||||
postContents, err := svc.dao.GetPostContentsByIDs(postIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users, err := svc.dao.GetUsersByIDs(userIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 数据整合
|
||||
postsFormated := []*model.PostFormated{}
|
||||
for _, post := range posts {
|
||||
postFormated := post.Format()
|
||||
|
||||
for _, user := range users {
|
||||
if user.ID == postFormated.UserID {
|
||||
postFormated.User = user.Format()
|
||||
}
|
||||
}
|
||||
for _, content := range postContents {
|
||||
if content.PostID == post.ID {
|
||||
postFormated.Contents = append(postFormated.Contents, content.Format())
|
||||
}
|
||||
}
|
||||
|
||||
postsFormated = append(postsFormated, postFormated)
|
||||
}
|
||||
|
||||
return postsFormated, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostCount(conditions *model.ConditionsT) (int64, error) {
|
||||
return svc.dao.GetPostCount(conditions)
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostListFromSearch(q *dao.QueryT, offset, limit int) ([]*model.PostFormated, int64, error) {
|
||||
queryResult, err := svc.dao.QueryAll(q, global.SearchSetting.ZincIndex, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
posts, err := svc.FormatZincPost(queryResult)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return posts, queryResult.Hits.Total.Value, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostListFromSearchByQuery(query string, offset, limit int) ([]*model.PostFormated, int64, error) {
|
||||
queryResult, err := svc.dao.QuerySearch(global.SearchSetting.ZincIndex, query, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
posts, err := svc.FormatZincPost(queryResult)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return posts, queryResult.Hits.Total.Value, nil
|
||||
}
|
||||
|
||||
func (svc *Service) PushPostToSearch(post *model.Post) {
|
||||
indexName := global.SearchSetting.ZincIndex
|
||||
|
||||
postFormated := post.Format()
|
||||
postFormated.User = &model.UserFormated{
|
||||
ID: post.UserID,
|
||||
}
|
||||
contents, _ := svc.dao.GetPostContentsByIDs([]int64{post.ID})
|
||||
for _, content := range contents {
|
||||
postFormated.Contents = append(postFormated.Contents, content.Format())
|
||||
}
|
||||
|
||||
contentFormated := ""
|
||||
|
||||
for _, content := range postFormated.Contents {
|
||||
if content.Type == model.CONTENT_TYPE_TEXT || content.Type == model.CONTENT_TYPE_TITLE {
|
||||
contentFormated = contentFormated + content.Content + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
tagMaps := map[string]int8{}
|
||||
for _, tag := range strings.Split(post.Tags, ",") {
|
||||
tagMaps[tag] = 1
|
||||
}
|
||||
|
||||
data := []map[string]interface{}{}
|
||||
data = append(data, map[string]interface{}{
|
||||
"index": map[string]interface{}{
|
||||
"_index": indexName,
|
||||
"_id": fmt.Sprintf("%d", post.ID),
|
||||
},
|
||||
}, map[string]interface{}{
|
||||
"id": post.ID,
|
||||
"user_id": post.UserID,
|
||||
"comment_count": post.CommentCount,
|
||||
"collection_count": post.CollectionCount,
|
||||
"upvote_count": post.UpvoteCount,
|
||||
"is_top": post.IsTop,
|
||||
"is_essence": post.IsEssence,
|
||||
"content": contentFormated,
|
||||
"tags": tagMaps,
|
||||
"ip_loc": post.IPLoc,
|
||||
"latest_replied_on": post.LatestRepliedOn,
|
||||
"attachment_price": post.AttachmentPrice,
|
||||
"created_on": post.CreatedOn,
|
||||
"modified_on": post.ModifiedOn,
|
||||
})
|
||||
|
||||
svc.dao.BulkPushDoc(data)
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteSearchPost(post *model.Post) error {
|
||||
indexName := global.SearchSetting.ZincIndex
|
||||
|
||||
return svc.dao.DelDoc(indexName, fmt.Sprintf("%d", post.ID))
|
||||
}
|
||||
|
||||
func (svc *Service) PushPostsToSearch() {
|
||||
if ok, _ := global.Redis.SetNX(svc.ctx, "JOB_PUSH_TO_SEARCH", 1, time.Hour).Result(); ok {
|
||||
splitNum := 1000
|
||||
totalRows, _ := svc.GetPostCount(&model.ConditionsT{})
|
||||
|
||||
pages := math.Ceil(float64(totalRows) / float64(splitNum))
|
||||
nums := int(pages)
|
||||
|
||||
indexName := global.SearchSetting.ZincIndex
|
||||
// 创建索引
|
||||
svc.dao.CreateSearchIndex(indexName)
|
||||
|
||||
for i := 0; i < nums; i++ {
|
||||
data := []map[string]interface{}{}
|
||||
|
||||
posts, _ := svc.GetPostList(&PostListReq{
|
||||
Conditions: &model.ConditionsT{},
|
||||
Offset: i * splitNum,
|
||||
Limit: splitNum,
|
||||
})
|
||||
|
||||
for _, post := range posts {
|
||||
contentFormated := ""
|
||||
|
||||
for _, content := range post.Contents {
|
||||
if content.Type == model.CONTENT_TYPE_TEXT || content.Type == model.CONTENT_TYPE_TITLE {
|
||||
contentFormated = contentFormated + content.Content + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
data = append(data, map[string]interface{}{
|
||||
"index": map[string]interface{}{
|
||||
"_index": indexName,
|
||||
"_id": fmt.Sprintf("%d", post.ID),
|
||||
},
|
||||
}, map[string]interface{}{
|
||||
"id": post.ID,
|
||||
"user_id": post.User.ID,
|
||||
"comment_count": post.CommentCount,
|
||||
"collection_count": post.CollectionCount,
|
||||
"upvote_count": post.UpvoteCount,
|
||||
"is_top": post.IsTop,
|
||||
"is_essence": post.IsEssence,
|
||||
"content": contentFormated,
|
||||
"tags": post.Tags,
|
||||
"ip_loc": post.IPLoc,
|
||||
"latest_replied_on": post.LatestRepliedOn,
|
||||
"attachment_price": post.AttachmentPrice,
|
||||
"created_on": post.CreatedOn,
|
||||
"modified_on": post.ModifiedOn,
|
||||
})
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
svc.dao.BulkPushDoc(data)
|
||||
}
|
||||
}
|
||||
|
||||
global.Redis.Del(svc.ctx, "JOB_PUSH_TO_SEARCH")
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) FormatZincPost(queryResult *zinc.QueryResultT) ([]*model.PostFormated, error) {
|
||||
posts := []*model.PostFormated{}
|
||||
for _, hit := range queryResult.Hits.Hits {
|
||||
item := &model.PostFormated{}
|
||||
|
||||
raw, _ := json.Marshal(hit.Source)
|
||||
err := json.Unmarshal(raw, item)
|
||||
if err == nil {
|
||||
posts = append(posts, item)
|
||||
}
|
||||
}
|
||||
|
||||
postIds := []int64{}
|
||||
userIds := []int64{}
|
||||
for _, post := range posts {
|
||||
postIds = append(postIds, post.ID)
|
||||
userIds = append(userIds, post.UserID)
|
||||
}
|
||||
postContents, err := svc.dao.GetPostContentsByIDs(postIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users, err := svc.dao.GetUsersByIDs(userIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 数据整合
|
||||
for _, post := range posts {
|
||||
for _, user := range users {
|
||||
if user.ID == post.UserID {
|
||||
post.User = user.Format()
|
||||
}
|
||||
}
|
||||
if post.Contents == nil {
|
||||
post.Contents = []*model.PostContentFormated{}
|
||||
}
|
||||
for _, content := range postContents {
|
||||
if content.PostID == post.ID {
|
||||
post.Contents = append(post.Contents, content.Format())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetPostTags(param *PostTagsReq) ([]*model.TagFormated, error) {
|
||||
num := param.Num
|
||||
if num > global.AppSetting.MaxPageSize {
|
||||
num = global.AppSetting.MaxPageSize
|
||||
}
|
||||
|
||||
conditions := &model.ConditionsT{}
|
||||
if param.Type == TagTypeHot {
|
||||
// 热门标签
|
||||
conditions = &model.ConditionsT{
|
||||
"ORDER": "quote_num DESC",
|
||||
}
|
||||
}
|
||||
if param.Type == TagTypeNew {
|
||||
// 热门标签
|
||||
conditions = &model.ConditionsT{
|
||||
"ORDER": "id DESC",
|
||||
}
|
||||
}
|
||||
|
||||
tags, err := svc.dao.GetTags(conditions, 0, num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取创建者User IDs
|
||||
userIds := []int64{}
|
||||
for _, tag := range tags {
|
||||
userIds = append(userIds, tag.UserID)
|
||||
}
|
||||
|
||||
users, _ := svc.dao.GetUsersByIDs(userIds)
|
||||
|
||||
tagsFormated := []*model.TagFormated{}
|
||||
for _, tag := range tags {
|
||||
tagFormated := tag.Format()
|
||||
for _, user := range users {
|
||||
if user.ID == tagFormated.UserID {
|
||||
tagFormated.User = user.Format()
|
||||
}
|
||||
}
|
||||
tagsFormated = append(tagsFormated, tagFormated)
|
||||
}
|
||||
|
||||
return tagsFormated, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CheckPostAttachmentIsPaid(postID, userID int64) bool {
|
||||
bill, err := svc.dao.GetPostAttatchmentBill(postID, userID)
|
||||
|
||||
return err == nil && bill.Model != nil && bill.ID > 0
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/dao"
|
||||
"github.com/rocboss/paopao-api/pkg/zinc"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
ctx *gin.Context
|
||||
dao *dao.Dao
|
||||
}
|
||||
|
||||
func New(ctx *gin.Context) Service {
|
||||
svc := Service{ctx: ctx}
|
||||
svc.dao = dao.New(global.DBEngine, &zinc.ZincClient{
|
||||
ZincClientConfig: &zinc.ZincClientConfig{
|
||||
ZincHost: global.SearchSetting.ZincHost,
|
||||
ZincUser: global.SearchSetting.ZincUser,
|
||||
ZincPassword: global.SearchSetting.ZincPassword,
|
||||
},
|
||||
})
|
||||
|
||||
return svc
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/pkg/util"
|
||||
)
|
||||
|
||||
func (svc *Service) GetParamSign(param map[string]interface{}, secretKey string) string {
|
||||
signRaw := ""
|
||||
|
||||
rawStrs := []string{}
|
||||
for k, v := range param {
|
||||
if k != "sign" {
|
||||
rawStrs = append(rawStrs, k+"="+fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(rawStrs)
|
||||
for _, v := range rawStrs {
|
||||
signRaw += v
|
||||
}
|
||||
|
||||
if global.ServerSetting.RunMode == "debug" {
|
||||
global.Logger.Info(map[string]string{
|
||||
"signRaw": signRaw,
|
||||
"sysSign": util.EncodeMD5(signRaw + secretKey),
|
||||
})
|
||||
}
|
||||
|
||||
return util.EncodeMD5(signRaw + secretKey)
|
||||
}
|
@ -0,0 +1,364 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
"github.com/rocboss/paopao-api/pkg/util"
|
||||
)
|
||||
|
||||
const MAX_CAPTCHA_TIMES = 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"`
|
||||
}
|
||||
|
||||
const LOGIN_ERR_KEY = "PaoPaoUserLoginErr"
|
||||
const MAX_LOGIN_ERR_TIMES = 10
|
||||
|
||||
// 用户认证
|
||||
func (svc *Service) DoLogin(param *AuthRequest) (*model.User, error) {
|
||||
user, err := svc.dao.GetUserByUsername(param.Username)
|
||||
if err != nil {
|
||||
return nil, errcode.UnauthorizedAuthNotExist
|
||||
}
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
if errTimes, err := global.Redis.Get(svc.ctx, fmt.Sprintf("%s:%d", LOGIN_ERR_KEY, user.ID)).Result(); err == nil {
|
||||
if convert.StrTo(errTimes).MustInt() >= MAX_LOGIN_ERR_TIMES {
|
||||
return nil, errcode.TooManyLoginError
|
||||
}
|
||||
}
|
||||
|
||||
// 对比密码是否正确
|
||||
if svc.ValidPassword(user.Password, param.Password, user.Salt) {
|
||||
|
||||
if user.Status == model.UserStatusClosed {
|
||||
return nil, errcode.UserHasBeenBanned
|
||||
}
|
||||
|
||||
// 清空登录计数
|
||||
global.Redis.Del(svc.ctx, fmt.Sprintf("%s:%d", LOGIN_ERR_KEY, user.ID))
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// 登录错误计数
|
||||
_, err = global.Redis.Incr(svc.ctx, fmt.Sprintf("%s:%d", LOGIN_ERR_KEY, user.ID)).Result()
|
||||
if err == nil {
|
||||
global.Redis.Expire(svc.ctx, fmt.Sprintf("%s:%d", LOGIN_ERR_KEY, user.ID), time.Hour).Result()
|
||||
}
|
||||
|
||||
return nil, errcode.UnauthorizedAuthFailed
|
||||
}
|
||||
|
||||
return nil, errcode.UnauthorizedAuthNotExist
|
||||
}
|
||||
|
||||
// 检查密码是否一致
|
||||
func (svc *Service) ValidPassword(dbPassword, password, salt string) bool {
|
||||
return strings.Compare(dbPassword, util.EncodeMD5(util.EncodeMD5(password)+salt)) == 0
|
||||
}
|
||||
|
||||
// 检测用户权限
|
||||
func (svc *Service) CheckStatus(user *model.User) bool {
|
||||
return user.Status == model.UserStatusNormal
|
||||
}
|
||||
|
||||
// 验证用户
|
||||
func (svc *Service) 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, _ := svc.dao.GetUserByUsername(username)
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
return errcode.UsernameHasExisted
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 密码检查
|
||||
func (svc *Service) CheckPassword(password string) error {
|
||||
// 检测用户是否合规
|
||||
if utf8.RuneCountInString(password) < 6 || utf8.RuneCountInString(password) > 16 {
|
||||
return errcode.PasswordLengthLimit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证手机验证码
|
||||
func (svc *Service) CheckPhoneCaptcha(phone, captcha string) *errcode.Error {
|
||||
c, err := svc.dao.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 >= MAX_CAPTCHA_TIMES {
|
||||
return errcode.MaxPhoneCaptchaUseTimes
|
||||
}
|
||||
|
||||
// 更新检测次数
|
||||
svc.dao.UsePhoneCaptcha(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检测手机号是否存在
|
||||
func (svc *Service) CheckPhoneExist(uid int64, phone string) bool {
|
||||
u, err := svc.dao.GetUserByPhone(phone)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.Model == nil || u.ID == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.ID == uid {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 密码加密&生成salt
|
||||
func (svc *Service) EncryptPasswordAndSalt(password string) (string, string) {
|
||||
salt := uuid.Must(uuid.NewV4()).String()[:8]
|
||||
password = util.EncodeMD5(util.EncodeMD5(password) + salt)
|
||||
|
||||
return password, salt
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
func (svc *Service) Register(username, password string) (*model.User, error) {
|
||||
password, salt := svc.EncryptPasswordAndSalt(password)
|
||||
|
||||
user := &model.User{
|
||||
Nickname: username,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Avatar: svc.GetRandomAvatar(),
|
||||
Salt: salt,
|
||||
Status: model.UserStatusNormal,
|
||||
}
|
||||
|
||||
user, err := svc.dao.CreateUser(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
func (svc *Service) GetUserInfo(param *AuthRequest) (*model.User, error) {
|
||||
user, err := svc.dao.GetUserByUsername(param.Username)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, errcode.UnauthorizedAuthNotExist
|
||||
}
|
||||
|
||||
func (svc *Service) GetUserByUsername(username string) (*model.User, error) {
|
||||
user, err := svc.dao.GetUserByUsername(username)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user.Model != nil && user.ID > 0 {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, errcode.NoExistUsername
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
func (svc *Service) UpdateUserInfo(user *model.User) error {
|
||||
return svc.dao.UpdateUser(user)
|
||||
}
|
||||
|
||||
// 获取用户收藏列表
|
||||
func (svc *Service) GetUserCollections(userID int64, offset, limit int) ([]*model.PostFormated, int64, error) {
|
||||
collections, err := svc.dao.GetUserPostCollections(userID, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalRows, err := svc.dao.GetUserPostCollectionCount(userID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
postIDs := []int64{}
|
||||
for _, collection := range collections {
|
||||
postIDs = append(postIDs, collection.PostID)
|
||||
}
|
||||
|
||||
// 获取Posts
|
||||
posts, err := svc.dao.GetPosts(&model.ConditionsT{
|
||||
"id IN ?": postIDs,
|
||||
"ORDER": "id DESC",
|
||||
}, 0, 0)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
postsFormated, err := svc.FormatPosts(posts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return postsFormated, totalRows, nil
|
||||
}
|
||||
|
||||
// 获取用户点赞列表
|
||||
func (svc *Service) GetUserStars(userID int64, offset, limit int) ([]*model.PostFormated, int64, error) {
|
||||
stars, err := svc.dao.GetUserPostStars(userID, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalRows, err := svc.dao.GetUserPostStarCount(userID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
postIDs := []int64{}
|
||||
for _, star := range stars {
|
||||
postIDs = append(postIDs, star.PostID)
|
||||
}
|
||||
|
||||
// 获取Posts
|
||||
posts, err := svc.dao.GetPosts(&model.ConditionsT{
|
||||
"id IN ?": postIDs,
|
||||
"ORDER": "id DESC",
|
||||
}, 0, 0)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
postsFormated, err := svc.FormatPosts(posts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return postsFormated, totalRows, nil
|
||||
}
|
||||
|
||||
// 获取用户账单列表
|
||||
func (svc *Service) GetUserWalletBills(userID int64, offset, limit int) ([]*model.WalletStatement, int64, error) {
|
||||
bills, err := svc.dao.GetUserWalletBills(userID, offset, limit)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalRows, err := svc.dao.GetUserWalletBillCount(userID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return bills, totalRows, nil
|
||||
}
|
||||
|
||||
// 发送短信验证码
|
||||
func (svc *Service) SendPhoneCaptcha(phone string) error {
|
||||
|
||||
err := svc.dao.SendPhoneCaptcha(phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入计数缓存
|
||||
global.Redis.Incr(svc.ctx, "PaoPaoSmsCaptcha:"+phone).Result()
|
||||
|
||||
currentTime := time.Now()
|
||||
endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
|
||||
|
||||
global.Redis.Expire(svc.ctx, "PaoPaoSmsCaptcha:"+phone, endTime.Sub(currentTime))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据关键词获取用户推荐
|
||||
func (svc *Service) GetSuggestUsers(keyword string) ([]string, error) {
|
||||
users, err := svc.dao.GetUsersByKeyword(keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usernames := []string{}
|
||||
for _, user := range users {
|
||||
usernames = append(usernames, user.Username)
|
||||
}
|
||||
|
||||
return usernames, nil
|
||||
}
|
||||
|
||||
// 根据关键词获取标签推荐
|
||||
func (svc *Service) GetSuggestTags(keyword string) ([]string, error) {
|
||||
tags, err := svc.dao.GetTagsByKeyword(keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts := []string{}
|
||||
for _, t := range tags {
|
||||
ts = append(ts, t.Tag)
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
type RechargeReq struct {
|
||||
Amount int64 `json:"amount" form:"amount" binding:"required"`
|
||||
}
|
||||
|
||||
func (svc *Service) GetRechargeByID(id int64) (*model.WalletRecharge, error) {
|
||||
return svc.dao.GetRechargeByID(id)
|
||||
}
|
||||
|
||||
func (svc *Service) CreateRecharge(userID, amount int64) (*model.WalletRecharge, error) {
|
||||
return svc.dao.CreateRecharge(userID, amount)
|
||||
}
|
||||
|
||||
func (svc *Service) FinishRecharge(id int64, tradeNo string) error {
|
||||
if ok, _ := global.Redis.SetNX(svc.ctx, "PaoPaoRecharge:"+tradeNo, 1, time.Second*5).Result(); ok {
|
||||
recharge, err := svc.dao.GetRechargeByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recharge.TradeStatus != "TRADE_SUCCESS" {
|
||||
|
||||
// 标记为已付款
|
||||
err := svc.dao.HandleRechargeSuccess(recharge, tradeNo)
|
||||
defer global.Redis.Del(svc.ctx, "PaoPaoRecharge:"+tradeNo)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) BuyPostAttachment(post *model.Post, user *model.User) error {
|
||||
if user.Balance < post.AttachmentPrice {
|
||||
return errcode.InsuffientDownloadMoney
|
||||
}
|
||||
|
||||
// 执行购买
|
||||
return svc.dao.HandlePostAttachmentBought(post, user)
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/routers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gin.SetMode(global.ServerSetting.RunMode)
|
||||
|
||||
router := routers.NewRouter()
|
||||
s := &http.Server{
|
||||
Addr: global.ServerSetting.HttpIp + ":" + global.ServerSetting.HttpPort,
|
||||
Handler: router,
|
||||
ReadTimeout: global.ServerSetting.ReadTimeout,
|
||||
WriteTimeout: global.ServerSetting.WriteTimeout,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
fmt.Printf("\x1b[36m%s\x1b[0m\n", "PaoPao service listen on http://"+global.ServerSetting.HttpIp+":"+global.ServerSetting.HttpPort)
|
||||
s.ListenAndServe()
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/pkg/errcode"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Ctx *gin.Context
|
||||
}
|
||||
|
||||
type Pager struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalRows int64 `json:"total_rows"`
|
||||
}
|
||||
|
||||
func NewResponse(ctx *gin.Context) *Response {
|
||||
return &Response{Ctx: ctx}
|
||||
}
|
||||
|
||||
func (r *Response) ToResponse(data interface{}) {
|
||||
hostname, _ := os.Hostname()
|
||||
if data == nil {
|
||||
data = gin.H{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"tracehost": hostname,
|
||||
}
|
||||
} else {
|
||||
data = gin.H{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": data,
|
||||
"tracehost": hostname,
|
||||
}
|
||||
}
|
||||
r.Ctx.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
func (r *Response) ToResponseList(list interface{}, totalRows int64) {
|
||||
r.ToResponse(gin.H{
|
||||
"list": list,
|
||||
"pager": Pager{
|
||||
Page: GetPage(r.Ctx),
|
||||
PageSize: GetPageSize(r.Ctx),
|
||||
TotalRows: totalRows,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Response) ToErrorResponse(err *errcode.Error) {
|
||||
response := gin.H{"code": err.Code(), "msg": err.Msg()}
|
||||
details := err.Details()
|
||||
if len(details) > 0 {
|
||||
response["details"] = details
|
||||
}
|
||||
|
||||
r.Ctx.JSON(err.StatusCode(), response)
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ValidError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
type ValidErrors []*ValidError
|
||||
|
||||
func (v *ValidError) Error() string {
|
||||
return v.Message
|
||||
}
|
||||
|
||||
func (v ValidErrors) Error() string {
|
||||
return strings.Join(v.Errors(), ",")
|
||||
}
|
||||
|
||||
func (v ValidErrors) Errors() []string {
|
||||
var errs []string
|
||||
for _, err := range v {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func BindAndValid(c *gin.Context, v interface{}) (bool, ValidErrors) {
|
||||
var errs ValidErrors
|
||||
err := c.ShouldBind(v)
|
||||
if err != nil {
|
||||
errs = append(errs, &ValidError{
|
||||
Message: err.Error(),
|
||||
})
|
||||
|
||||
return false, errs
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/internal/model"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
UID int64 `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func GetJWTSecret() []byte {
|
||||
return []byte(global.JWTSetting.Secret)
|
||||
}
|
||||
|
||||
func GenerateToken(User *model.User) (string, error) {
|
||||
nowTime := time.Now()
|
||||
expireTime := nowTime.Add(global.JWTSetting.Expire)
|
||||
claims := Claims{
|
||||
UID: User.ID,
|
||||
Username: User.Username,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: expireTime.Unix(),
|
||||
Issuer: global.JWTSetting.Issuer + ":" + User.Salt,
|
||||
},
|
||||
}
|
||||
|
||||
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
token, err := tokenClaims.SignedString(GetJWTSecret())
|
||||
return token, err
|
||||
}
|
||||
|
||||
func ParseToken(token string) (*Claims, error) {
|
||||
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return GetJWTSecret(), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tokenClaims != nil {
|
||||
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/pkg/convert"
|
||||
)
|
||||
|
||||
func GetPage(c *gin.Context) int {
|
||||
page := convert.StrTo(c.Query("page")).MustInt()
|
||||
if page <= 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
func GetPageSize(c *gin.Context) int {
|
||||
pageSize := convert.StrTo(c.Query("page_size")).MustInt()
|
||||
if pageSize <= 0 {
|
||||
return global.AppSetting.DefaultPageSize
|
||||
}
|
||||
if pageSize > global.AppSetting.MaxPageSize {
|
||||
return global.AppSetting.MaxPageSize
|
||||
}
|
||||
|
||||
return pageSize
|
||||
}
|
||||
|
||||
func GetPageOffset(page, pageSize int) int {
|
||||
result := 0
|
||||
if page > 0 {
|
||||
result = (page - 1) * pageSize
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package convert
|
||||
|
||||
import "strconv"
|
||||
|
||||
type StrTo string
|
||||
|
||||
func (s StrTo) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s StrTo) Int() (int, error) {
|
||||
v, err := strconv.Atoi(s.String())
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (s StrTo) MustInt() int {
|
||||
v, _ := s.Int()
|
||||
return v
|
||||
}
|
||||
|
||||
func (s StrTo) UInt32() (uint32, error) {
|
||||
v, err := strconv.Atoi(s.String())
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
func (s StrTo) MustUInt32() uint32 {
|
||||
v, _ := s.UInt32()
|
||||
return v
|
||||
}
|
||||
|
||||
func (s StrTo) Int64() (int64, error) {
|
||||
v, err := strconv.ParseInt(s.String(), 10, 64)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (s StrTo) MustInt64() int64 {
|
||||
v, _ := s.Int64()
|
||||
return v
|
||||
}
|
||||
|
||||
func (s StrTo) Float64() (float64, error) {
|
||||
return strconv.ParseFloat(s.String(), 64)
|
||||
}
|
||||
|
||||
func (s StrTo) MustFloat64() float64 {
|
||||
v, _ := strconv.ParseFloat(s.String(), 64)
|
||||
return v
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func Sign(hash []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) {
|
||||
return crypto.Sign(hash, privateKey)
|
||||
}
|
||||
|
||||
func VerifySignature(publicKey, hash, signature []byte) bool {
|
||||
return crypto.VerifySignature(publicKey, hash, signature)
|
||||
}
|
||||
|
||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
func PKCS7UnPadding(origData []byte) []byte {
|
||||
length := len(origData)
|
||||
unpadding := int(origData[length-1])
|
||||
return origData[:(length - unpadding)]
|
||||
}
|
||||
|
||||
func AesEncrypt(origData, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
origData = PKCS7Padding(origData, blockSize)
|
||||
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||
crypted := make([]byte, len(origData))
|
||||
blockMode.CryptBlocks(crypted, origData)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func AesDecrypt(crypted, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
origData := make([]byte, len(crypted))
|
||||
blockMode.CryptBlocks(origData, crypted)
|
||||
origData = PKCS7UnPadding(origData)
|
||||
return origData, nil
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
code int
|
||||
msg string
|
||||
details []string
|
||||
}
|
||||
|
||||
var codes = map[int]string{}
|
||||
|
||||
func NewError(code int, msg string) *Error {
|
||||
if _, ok := codes[code]; ok {
|
||||
panic(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code))
|
||||
}
|
||||
codes[code] = msg
|
||||
return &Error{code: code, msg: msg}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("错误码: %d, 错误信息: %s", e.Code(), e.Msg())
|
||||
}
|
||||
|
||||
func (e *Error) Code() int {
|
||||
return e.code
|
||||
}
|
||||
|
||||
func (e *Error) Msg() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func (e *Error) Msgf(args []interface{}) string {
|
||||
return fmt.Sprintf(e.msg, args...)
|
||||
}
|
||||
|
||||
func (e *Error) Details() []string {
|
||||
return e.details
|
||||
}
|
||||
|
||||
func (e *Error) WithDetails(details ...string) *Error {
|
||||
newError := *e
|
||||
newError.details = []string{}
|
||||
newError.details = append(newError.details, details...)
|
||||
|
||||
return &newError
|
||||
}
|
||||
|
||||
func (e *Error) StatusCode() int {
|
||||
switch e.Code() {
|
||||
case Success.Code():
|
||||
return http.StatusOK
|
||||
case ServerError.Code():
|
||||
return http.StatusInternalServerError
|
||||
case InvalidParams.Code():
|
||||
return http.StatusBadRequest
|
||||
case UnauthorizedAuthNotExist.Code():
|
||||
fallthrough
|
||||
case UnauthorizedAuthFailed.Code():
|
||||
fallthrough
|
||||
case UnauthorizedTokenError.Code():
|
||||
fallthrough
|
||||
case UnauthorizedTokenGenerate.Code():
|
||||
fallthrough
|
||||
case UnauthorizedTokenTimeout.Code():
|
||||
return http.StatusUnauthorized
|
||||
case TooManyRequests.Code():
|
||||
return http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
return http.StatusInternalServerError
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package errcode
|
||||
|
||||
var (
|
||||
UsernameHasExisted = NewError(20001, "用户名已存在")
|
||||
UsernameLengthLimit = NewError(20002, "用户名长度3~12")
|
||||
UsernameCharLimit = NewError(20003, "用户名只能包含字母、数字")
|
||||
PasswordLengthLimit = NewError(20004, "密码长度6~16")
|
||||
UserRegisterFailed = NewError(20005, "用户注册失败")
|
||||
UserHasBeenBanned = NewError(20006, "该账户已被封停")
|
||||
NoPermission = NewError(20007, "无权限执行该请求")
|
||||
UserHasBindOTP = NewError(20008, "当前用户已绑定二次验证")
|
||||
UserOTPInvalid = NewError(20009, "二次验证码验证失败")
|
||||
UserNoBindOTP = NewError(20010, "当前用户未绑定二次验证")
|
||||
ErrorOldPassword = NewError(20011, "当前用户密码验证失败")
|
||||
ErrorCaptchaPassword = NewError(20012, "图形验证码验证失败")
|
||||
AccountNoPhoneBind = NewError(20013, "拒绝操作: 账户未绑定手机号")
|
||||
TooManyLoginError = NewError(20014, "登录失败次数过多,请稍后再试")
|
||||
GetPhoneCaptchaError = NewError(20015, "短信验证码获取失败")
|
||||
TooManyPhoneCaptchaSend = NewError(20016, "短信验证码获取次数已达今日上限")
|
||||
ExistedUserPhone = NewError(20017, "该手机号已被绑定")
|
||||
ErrorPhoneCaptcha = NewError(20018, "手机验证码不正确")
|
||||
MaxPhoneCaptchaUseTimes = NewError(20019, "手机验证码已达最大使用次数")
|
||||
NicknameLengthLimit = NewError(20020, "昵称长度2~12")
|
||||
NoExistUsername = NewError(20021, "用户不存在")
|
||||
|
||||
GetPostsFailed = NewError(30001, "获取动态列表失败")
|
||||
CreatePostFailed = NewError(30002, "动态发布失败")
|
||||
GetPostFailed = NewError(30003, "获取动态详情失败")
|
||||
DeletePostFailed = NewError(30004, "动态删除失败")
|
||||
LockPostFailed = NewError(30005, "动态锁定失败")
|
||||
GetPostTagsFailed = NewError(30006, "获取话题列表失败")
|
||||
InvalidDownloadReq = NewError(30007, "附件下载请求不合法")
|
||||
DownloadReqError = NewError(30008, "附件下载请求失败")
|
||||
InsuffientDownloadMoney = NewError(30009, "附件下载失败:账户资金不足")
|
||||
DownloadExecFail = NewError(30010, "附件下载失败:扣费失败")
|
||||
|
||||
GetCommentsFailed = NewError(40001, "获取评论列表失败")
|
||||
CreateCommentFailed = NewError(40002, "评论发布失败")
|
||||
GetCommentFailed = NewError(40003, "获取评论详情失败")
|
||||
DeleteCommentFailed = NewError(40004, "评论删除失败")
|
||||
CreateReplyFailed = NewError(40005, "评论回复失败")
|
||||
GetReplyFailed = NewError(40006, "获取评论详情失败")
|
||||
MaxCommentCount = NewError(40007, "评论数已达最大限制")
|
||||
|
||||
GetMessagesFailed = NewError(50001, "获取消息列表失败")
|
||||
ReadMessageFailed = NewError(50002, "标记消息已读失败")
|
||||
|
||||
GetCollectionsFailed = NewError(60001, "获取收藏列表失败")
|
||||
GetStarsFailed = NewError(60002, "获取点赞列表失败")
|
||||
|
||||
RechargeReqFail = NewError(70001, "充值请求失败")
|
||||
RechargeNotifyError = NewError(70002, "充值回调失败")
|
||||
GetRechargeFailed = NewError(70003, "充值详情获取失败")
|
||||
)
|
@ -0,0 +1,83 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/rocboss/paopao-api/global"
|
||||
"github.com/rocboss/paopao-api/pkg/setting"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
"gopkg.in/resty.v1"
|
||||
)
|
||||
|
||||
type ZincLogIndex struct {
|
||||
Index map[string]string `json:"index"`
|
||||
}
|
||||
|
||||
type ZincLogData struct {
|
||||
Time time.Time `json:"time"`
|
||||
Level logrus.Level `json:"level"`
|
||||
Message string `json:"message"`
|
||||
Data logrus.Fields `json:"data"`
|
||||
}
|
||||
|
||||
type ZincLogHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *ZincLogHook) Fire(entry *logrus.Entry) error {
|
||||
index := &ZincLogIndex{
|
||||
Index: map[string]string{
|
||||
"_index": global.LoggerSetting.LogZincIndex,
|
||||
},
|
||||
}
|
||||
indexBytes, _ := json.Marshal(index)
|
||||
|
||||
data := &ZincLogData{
|
||||
Time: entry.Time,
|
||||
Level: entry.Level,
|
||||
Message: entry.Message,
|
||||
Data: entry.Data,
|
||||
}
|
||||
dataBytes, _ := json.Marshal(data)
|
||||
|
||||
logStr := string(indexBytes) + "\n" + string(dataBytes) + "\n"
|
||||
client := resty.New()
|
||||
|
||||
if _, err := client.SetDisableWarn(true).R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBasicAuth(global.LoggerSetting.LogZincUser, global.LoggerSetting.LogZincPassword).
|
||||
SetBody(logStr).
|
||||
Post(global.LoggerSetting.LogZincHost); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ZincLogHook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
func New(s *setting.LoggerSettingS) (*logrus.Logger, error) {
|
||||
log := logrus.New()
|
||||
log.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
switch s.LogType {
|
||||
case setting.LogFileType:
|
||||
log.Out = &lumberjack.Logger{
|
||||
Filename: s.LogFileSavePath + "/" + s.LogFileName + s.LogFileExt,
|
||||
MaxSize: 600,
|
||||
MaxAge: 10,
|
||||
LocalTime: true,
|
||||
}
|
||||
case setting.LogZincType:
|
||||
log.Out = io.Discard
|
||||
log.AddHook(&ZincLogHook{})
|
||||
}
|
||||
|
||||
return log, nil
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
vp *viper.Viper
|
||||
}
|
||||
|
||||
type LogType string
|
||||
|
||||
const LogFileType LogType = "file"
|
||||
const LogZincType LogType = "zinc"
|
||||
|
||||
type LoggerSettingS struct {
|
||||
LogType LogType
|
||||
LogFileSavePath string
|
||||
LogFileName string
|
||||
LogFileExt string
|
||||
LogZincHost string
|
||||
LogZincIndex string
|
||||
LogZincUser string
|
||||
LogZincPassword string
|
||||
}
|
||||
|
||||
type ServerSettingS struct {
|
||||
RunMode string
|
||||
HttpIp string
|
||||
HttpPort string
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
type AppSettingS struct {
|
||||
BarkToken string
|
||||
MaxCommentCount int64
|
||||
AttachmentIncomeRate float64
|
||||
DefaultContextTimeout time.Duration
|
||||
DefaultPageSize int
|
||||
MaxPageSize int
|
||||
IsShastaTestnet bool
|
||||
TronApiKeys []string
|
||||
SmsJuheKey string
|
||||
SmsJuheTplID string
|
||||
SmsJuheTplVal string
|
||||
AlipayAppID string
|
||||
AlipayPrivateKey string
|
||||
}
|
||||
|
||||
type SearchSettingS struct {
|
||||
ZincHost string
|
||||
ZincIndex string
|
||||
ZincUser string
|
||||
ZincPassword string
|
||||
}
|
||||
|
||||
type DatabaseSettingS struct {
|
||||
DBType string
|
||||
UserName string
|
||||
Password string
|
||||
Host string
|
||||
DBName string
|
||||
TablePrefix string
|
||||
Charset string
|
||||
ParseTime bool
|
||||
LogLevel logger.LogLevel
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
}
|
||||
type AliossSettingS struct {
|
||||
AliossAccessKeyID string
|
||||
AliossAccessKeySecret string
|
||||
AliossEndpoint string
|
||||
AliossBucket string
|
||||
AliossDomain string
|
||||
}
|
||||
|
||||
type RedisSettingS struct {
|
||||
Host string
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
type JWTSettingS struct {
|
||||
Secret string
|
||||
Issuer string
|
||||
Expire time.Duration
|
||||
}
|
||||
|
||||
func NewSetting() (*Setting, error) {
|
||||
vp := viper.New()
|
||||
vp.SetConfigName("config")
|
||||
vp.AddConfigPath(".")
|
||||
vp.AddConfigPath("configs/")
|
||||
vp.SetConfigType("yaml")
|
||||
err := vp.ReadInConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Setting{vp}, nil
|
||||
}
|
||||
|
||||
func (s *Setting) ReadSection(k string, v interface{}) error {
|
||||
err := s.vp.UnmarshalKey(k, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"time"
|
||||
|
||||
"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/rocboss/paopao-api/pkg/crypto"
|
||||
)
|
||||
|
||||
// SignTransaction 签名交易
|
||||
func SignTransaction(transaction *core.Transaction, key *ecdsa.PrivateKey) ([]byte, error) {
|
||||
transaction.GetRawData().Timestamp = time.Now().UnixNano() / 1000000
|
||||
rawData, err := proto.Marshal(transaction.GetRawData())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h256h := sha256.New()
|
||||
h256h.Write(rawData)
|
||||
hash := h256h.Sum(nil)
|
||||
contractList := transaction.GetRawData().GetContract()
|
||||
for range contractList {
|
||||
signature, err := crypto.Sign(hash, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transaction.Signature = append(transaction.Signature, signature)
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func TrimLeftZeroes(s []byte) []byte {
|
||||
idx := 0
|
||||
for ; idx < len(s); idx++ {
|
||||
if s[idx] != 48 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[idx:]
|
||||
}
|
@ -0,0 +1 @@
|
||||
package types
|
@ -0,0 +1,12 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/yinheli/qqwry"
|
||||
)
|
||||
|
||||
func GetIPLoc(ip string) string {
|
||||
q := qqwry.NewQQwry("qqwry.dat")
|
||||
q.Find(ip)
|
||||
|
||||
return q.Country
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func EncodeMD5(value string) string {
|
||||
m := md5.New()
|
||||
m.Write([]byte(value))
|
||||
|
||||
return hex.EncodeToString(m.Sum(nil))
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StrType int
|
||||
|
||||
const (
|
||||
NUM StrType = iota // 数字
|
||||
LOWER // 小写字母
|
||||
UPPER // 大写字母
|
||||
ALL // 全部
|
||||
CLEAR // 去除部分易混淆的字符
|
||||
)
|
||||
|
||||
var fontKinds = [][]int{{10, 48}, {26, 97}, {26, 65}}
|
||||
var letters = []byte("34578acdefghjkmnpqstwxyABCDEFGHJKMNPQRSVWXY")
|
||||
|
||||
// 生成随机字符串
|
||||
// size 个数 kind 模式
|
||||
func RandStr(size int, kind StrType) []byte {
|
||||
ikind, result := kind, make([]byte, size)
|
||||
isAll := kind > 2 || kind < 0
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
for i := 0; i < size; i++ {
|
||||
if isAll {
|
||||
ikind = StrType(rand.Intn(int(ALL)))
|
||||
}
|
||||
scope, base := fontKinds[ikind][0], fontKinds[ikind][1]
|
||||
result[i] = uint8(base + rand.Intn(scope))
|
||||
// 不易混淆字符模式:重新生成字符
|
||||
if kind == 4 {
|
||||
result[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
package zinc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type ZincClient struct {
|
||||
*ZincClientConfig
|
||||
}
|
||||
|
||||
type ZincClientConfig struct {
|
||||
ZincHost string
|
||||
ZincUser string
|
||||
ZincPassword string
|
||||
}
|
||||
|
||||
type ZincIndex struct {
|
||||
Name string `json:"name"`
|
||||
StorageType string `json:"storage_type"`
|
||||
Mappings *ZincIndexMappings `json:"mappings"`
|
||||
}
|
||||
|
||||
type ZincIndexMappings struct {
|
||||
Properties *ZincIndexProperty `json:"properties"`
|
||||
}
|
||||
|
||||
type ZincIndexProperty map[string]*ZincIndexPropertyT
|
||||
|
||||
type ZincIndexPropertyT struct {
|
||||
Type string `json:"type"`
|
||||
Index bool `json:"index"`
|
||||
Store bool `json:"store"`
|
||||
Sortable bool `json:"sortable"`
|
||||
Aggregatable bool `json:"aggregatable"`
|
||||
Highlightable bool `json:"highlightable"`
|
||||
Analyzer string `json:"analyzer"`
|
||||
SearchAnalyzer string `json:"search_analyzer"`
|
||||
Format string `json:"format"`
|
||||
}
|
||||
|
||||
type QueryResultT struct {
|
||||
Took int `json:"took"`
|
||||
TimedOut bool `json:"timed_out"`
|
||||
Hits *HitsResultT `json:"hits"`
|
||||
}
|
||||
type HitsResultT struct {
|
||||
Total *HitsResultTotalT `json:"total"`
|
||||
MaxScore float64 `json:"max_score"`
|
||||
Hits []*HitItem `json:"hits"`
|
||||
}
|
||||
type HitsResultTotalT struct {
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
type HitItem struct {
|
||||
Index string `json:"_index"`
|
||||
Type string `json:"_type"`
|
||||
ID string `json:"_id"`
|
||||
Score float64 `json:"_score"`
|
||||
Timestamp time.Time `json:"@timestamp"`
|
||||
Source interface{} `json:"_source"`
|
||||
}
|
||||
|
||||
// 创建索引
|
||||
func (c *ZincClient) CreateIndex(name string, p *ZincIndexProperty) bool {
|
||||
data := &ZincIndex{
|
||||
Name: name,
|
||||
StorageType: "disk",
|
||||
Mappings: &ZincIndexMappings{
|
||||
Properties: p,
|
||||
},
|
||||
}
|
||||
client := resty.New() // 创建一个restry客户端
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBody(data).SetBasicAuth(c.ZincUser, c.ZincPassword).Put(c.ZincHost + "/api/index")
|
||||
|
||||
if err != nil || resp.StatusCode() != http.StatusOK {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查索引是否存在
|
||||
func (c *ZincClient) ExistIndex(name string) bool {
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBasicAuth(c.ZincUser, c.ZincPassword).Get(c.ZincHost + "/api/index")
|
||||
|
||||
if err != nil || resp.StatusCode() != http.StatusOK {
|
||||
return false
|
||||
}
|
||||
|
||||
retData := &map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(resp.String()), retData)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := (*retData)[name]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 新增/更新文档
|
||||
func (c *ZincClient) PutDoc(name string, id int64, doc interface{}) (bool, error) {
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBody(doc).SetBasicAuth(c.ZincUser, c.ZincPassword).Put(fmt.Sprintf("%s/api/%s/_doc/%d", c.ZincHost, name, id))
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return false, errors.New(resp.Status())
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 批量新增文档
|
||||
func (c *ZincClient) BulkPushDoc(docs []map[string]interface{}) (bool, error) {
|
||||
dataStr := ""
|
||||
for _, doc := range docs {
|
||||
str, err := json.Marshal(doc)
|
||||
if err == nil {
|
||||
dataStr = dataStr + string(str) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBody(dataStr).SetBasicAuth(c.ZincUser, c.ZincPassword).Post(fmt.Sprintf("%s/api/_bulk", c.ZincHost))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return false, errors.New(resp.Status())
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *ZincClient) EsQuery(indexName string, q interface{}) (*QueryResultT, error) {
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBody(q).SetBasicAuth(c.ZincUser, c.ZincPassword).Post(fmt.Sprintf("%s/es/%s/_search", c.ZincHost, indexName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return nil, errors.New(resp.Status())
|
||||
}
|
||||
|
||||
result := &QueryResultT{}
|
||||
err = json.Unmarshal(resp.Body(), result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *ZincClient) ApiQuery(indexName string, q interface{}) (*QueryResultT, error) {
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBody(q).SetBasicAuth(c.ZincUser, c.ZincPassword).Post(fmt.Sprintf("%s/api/%s/_search", c.ZincHost, indexName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return nil, errors.New(resp.Status())
|
||||
}
|
||||
|
||||
result := &QueryResultT{}
|
||||
err = json.Unmarshal(resp.Body(), result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *ZincClient) DelDoc(indexName, id string) error {
|
||||
client := resty.New()
|
||||
client.DisableWarn = true
|
||||
resp, err := client.R().SetBasicAuth(c.ZincUser, c.ZincPassword).Delete(fmt.Sprintf("%s/api/%s/_doc/%s", c.ZincHost, indexName, id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return errors.New(resp.Status())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
VITE_HOST="https://api.paopao.info"
|
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["johnsoncodehk.volar"]
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
FROM library/nginx
|
||||
|
||||
USER root
|
||||
|
||||
# copy static files
|
||||
COPY ./dist/ /usr/share/nginx/html/
|
||||
|
||||
# HEALTHCHECK
|
||||
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD service nginx status | grep running || exit 1
|
@ -0,0 +1,7 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
|
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||
<title>泡泡</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "paopao-web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vicons/carbon": "^0.12.0",
|
||||
"@vicons/fa": "^0.12.0",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vicons/material": "^0.12.0",
|
||||
"@vicons/tabler": "^0.12.0",
|
||||
"axios": "^0.26.1",
|
||||
"less": "^4.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.3",
|
||||
"naive-ui": "^2.28.0",
|
||||
"nonesir-video": "^1.0.3",
|
||||
"qrcanvas-vue": "^3.0.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"unplugin-vue-components": "^0.19.3",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "4",
|
||||
"vue3-player-video": "^1.2.5",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"vite": "^2.9.2"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<n-config-provider :theme="theme">
|
||||
<n-message-provider>
|
||||
<div
|
||||
class="app-container"
|
||||
:class="{ dark: theme?.name === 'dark' }"
|
||||
>
|
||||
<div has-sider class="main-wrap" position="static">
|
||||
<!-- 侧边栏 -->
|
||||
<sidebar />
|
||||
|
||||
<div class="content-wrap">
|
||||
<router-view class="app-wrap" v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component
|
||||
v-if="$route.meta.keepAlive"
|
||||
:is="Component"
|
||||
/>
|
||||
</keep-alive>
|
||||
<component
|
||||
v-if="!$route.meta.keepAlive"
|
||||
:is="Component"
|
||||
/>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
<!-- 右侧 -->
|
||||
<rightbar />
|
||||
</div>
|
||||
<!-- 登录/注册公共组件 -->
|
||||
<auth />
|
||||
</div>
|
||||
</n-message-provider>
|
||||
<n-global-style />
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { darkTheme } from 'naive-ui';
|
||||
|
||||
const store = useStore();
|
||||
const theme = computed(() => (store.state.theme === 'dark' ? darkTheme : null));
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '@/assets/css/main.less';
|
||||
</style>
|
@ -0,0 +1,62 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} params
|
||||
* - @param {string} username
|
||||
* - @param {string} password
|
||||
* @returns Promise
|
||||
*/
|
||||
export const userLogin = (params = {}) => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/auth/login',
|
||||
data: params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 注册用户
|
||||
* @param {Object} params
|
||||
* - @param {string} username
|
||||
* - @param {string} password
|
||||
* @returns Promise
|
||||
*/
|
||||
export const userRegister = (params = {}) => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/auth/register',
|
||||
data: params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const userInfo = (token = '') => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/info',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改用户密码
|
||||
* @param {Object} params
|
||||
* - @param {string} password 新密码
|
||||
* - @param {string} old_password 旧密码
|
||||
* @returns Promise
|
||||
*/
|
||||
export const updateUserPassword = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/api/user/password',
|
||||
data,
|
||||
});
|
||||
};
|
@ -0,0 +1,208 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取动态列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getPosts = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/posts',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取标签列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getTags = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/tags',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取动态详情
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getPost = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/post',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取动态点赞状态
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getPostStar = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/post/star',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 动态点赞
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const postStar = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/post/star',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取动态收藏状态
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getPostCollection = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/post/collection',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 动态收藏
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const postCollection = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/post/collection',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取动态评论列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getPostComments = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/post/comments',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 发布动态
|
||||
* @param {Object} data
|
||||
* - @param {array} contents 内容
|
||||
* - @param {array} users at用户
|
||||
* - @param {array} tags 话题
|
||||
* @returns Promise
|
||||
*/
|
||||
export const createPost = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除动态
|
||||
* @param {Object} data
|
||||
* - @param {number} id
|
||||
* @returns Promise
|
||||
*/
|
||||
export const deletePost = data => {
|
||||
return request({
|
||||
method: 'delete',
|
||||
url: '/post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 锁定/解锁动态
|
||||
* @param {Object} data
|
||||
* - @param {number} id
|
||||
* @returns Promise
|
||||
*/
|
||||
export const lockPost = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/post/lock',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 发布动态评论
|
||||
* @param {Object} data
|
||||
* - @param {array} contents 内容
|
||||
* - @param {array} users at用户
|
||||
* @returns Promise
|
||||
*/
|
||||
export const createComment = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/post/comment',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
* @param {Object} data
|
||||
* - @param {number} id
|
||||
* @returns Promise
|
||||
*/
|
||||
export const deleteComment = data => {
|
||||
return request({
|
||||
method: 'delete',
|
||||
url: '/post/comment',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 发布评论回复
|
||||
* @param {Object} data
|
||||
* - @param {string} content 内容
|
||||
* - @param {number} comment_id 评论ID
|
||||
* - @param {number} at_user_id at用户ID
|
||||
* @returns Promise
|
||||
*/
|
||||
export const createCommentReply = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/post/comment/reply',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除评论回复
|
||||
* @param {Object} data
|
||||
* - @param {number} id 评论ID
|
||||
* @returns Promise
|
||||
*/
|
||||
export const deleteCommentReply = data => {
|
||||
return request({
|
||||
method: 'delete',
|
||||
url: '/post/comment/reply',
|
||||
data
|
||||
});
|
||||
};
|
@ -0,0 +1,261 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getCaptcha = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/captcha',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const sendCaptcha = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/captcha',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 绑定用户手机
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const bindUserPhone = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/user/phone',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更改密码
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const changePassword = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/user/password',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更改昵称
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const changeNickname = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/user/nickname',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更改头像
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const changeAvatar = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/user/avatar',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取未读消息数
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getUnreadMsgCount = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/msgcount/unread',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getMessages = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/messages',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 阅读消息
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const readMessage = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/user/message/read',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取收藏列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getCollections = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/collections',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取点赞列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getStars = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/stars',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户基础信息
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getUserProfile = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/profile',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取点赞列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getUserPosts = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/posts',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取账单列表
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getBills = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/wallet/bills',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 发起充值请求
|
||||
* @param {Object} data
|
||||
* @returns Promise
|
||||
*/
|
||||
export const reqRecharge = data => {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/user/recharge',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取充值状态
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getRecharge = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/user/recharge',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取推荐用户
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getSuggestUsers = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/suggest/users',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取推荐用户
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getSuggestTags = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/suggest/tags',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取附件
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const precheckAttachment = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/attachment/precheck',
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取附件
|
||||
* @param {Object} params
|
||||
* @returns Promise
|
||||
*/
|
||||
export const getAttachment = params => {
|
||||
return request({
|
||||
method: 'get',
|
||||
url: '/attachment',
|
||||
params
|
||||
});
|
||||
};
|
@ -0,0 +1,82 @@
|
||||
.app-container {
|
||||
margin: 0;
|
||||
|
||||
.app-wrap {
|
||||
width: 100%;
|
||||
// max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.main-wrap {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
|
||||
.content-wrap {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-content-wrap {
|
||||
margin: 0;
|
||||
border-top: none;
|
||||
border-radius: 0;
|
||||
|
||||
.n-list-item {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-wrap {
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hash-link,
|
||||
.user-link {
|
||||
color: #18a058;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.username-link {
|
||||
color: #000;
|
||||
color: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
||||
.hash-link,
|
||||
.user-link {
|
||||
color: #63e2b7;
|
||||
}
|
||||
|
||||
.username-link {
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 821px) {
|
||||
.content-wrap {
|
||||
top: 0;
|
||||
left: 60px;
|
||||
position: absolute !important;
|
||||
width: calc(100% - 60px) !important;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="store.state.authModelShow"
|
||||
class="auth-card"
|
||||
preset="card"
|
||||
size="small"
|
||||
:mask-closable="false"
|
||||
:bordered="false"
|
||||
:style="{
|
||||
width: '360px',
|
||||
}"
|
||||
>
|
||||
<div class="auth-wrap">
|
||||
<n-card :bordered="false">
|
||||
<n-tabs
|
||||
:default-value="store.state.authModelTab"
|
||||
size="large"
|
||||
justify-content="space-evenly"
|
||||
>
|
||||
<n-tab-pane name="signin" tab="登录">
|
||||
<n-form
|
||||
ref="loginRef"
|
||||
:model="loginForm"
|
||||
:rules="{
|
||||
username: {
|
||||
required: true,
|
||||
message: '请输入账户名',
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
},
|
||||
}"
|
||||
>
|
||||
<n-form-item-row label="账户" path="username">
|
||||
<n-input
|
||||
v-model:value="loginForm.username"
|
||||
placeholder="请输入用户名"
|
||||
@keyup.enter.prevent="handleLogin"
|
||||
/>
|
||||
</n-form-item-row>
|
||||
<n-form-item-row label="密码" path="password">
|
||||
<n-input
|
||||
type="password"
|
||||
show-password-on="mousedown"
|
||||
v-model:value="loginForm.password"
|
||||
placeholder="请输入账户密码"
|
||||
@keyup.enter.prevent="handleLogin"
|
||||
/>
|
||||
</n-form-item-row>
|
||||
</n-form>
|
||||
<n-button
|
||||
type="primary"
|
||||
block
|
||||
secondary
|
||||
strong
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录
|
||||
</n-button>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="signup" tab="注册">
|
||||
<n-form
|
||||
ref="registerRef"
|
||||
:model="registerForm"
|
||||
:rules="{
|
||||
username: {
|
||||
required: true,
|
||||
message: '请输入账户名',
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
},
|
||||
repassword: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
},
|
||||
{
|
||||
validator(rule, value) {
|
||||
return (
|
||||
!!registerForm.password &&
|
||||
registerForm.password.startsWith(
|
||||
value
|
||||
) &&
|
||||
registerForm.password.length >=
|
||||
value.length
|
||||
);
|
||||
},
|
||||
message: '两次密码输入不一致',
|
||||
trigger: 'input',
|
||||
},
|
||||
],
|
||||
}"
|
||||
>
|
||||
<n-form-item-row label="用户名" path="username">
|
||||
<n-input
|
||||
v-model:value="registerForm.username"
|
||||
placeholder="用户名注册后无法修改"
|
||||
/>
|
||||
</n-form-item-row>
|
||||
<n-form-item-row label="密码" path="password">
|
||||
<n-input
|
||||
type="password"
|
||||
show-password-on="mousedown"
|
||||
placeholder="密码不少于6位"
|
||||
v-model:value="registerForm.password"
|
||||
@keyup.enter.prevent="handleRegister"
|
||||
/>
|
||||
</n-form-item-row>
|
||||
<n-form-item-row label="重复密码" path="repassword">
|
||||
<n-input
|
||||
type="password"
|
||||
show-password-on="mousedown"
|
||||
placeholder="请再次输入密码"
|
||||
v-model:value="registerForm.repassword"
|
||||
@keyup.enter.prevent="handleRegister"
|
||||
/>
|
||||
</n-form-item-row>
|
||||
</n-form>
|
||||
<n-button
|
||||
type="primary"
|
||||
block
|
||||
secondary
|
||||
strong
|
||||
:loading="loading"
|
||||
@click="handleRegister"
|
||||
>
|
||||
注册
|
||||
</n-button>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { userLogin, userRegister, userInfo } from '@/api/auth';
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const loginRef = ref(null);
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
const registerRef = ref(null);
|
||||
const registerForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
repassword: '',
|
||||
});
|
||||
const handleLogin = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
loginRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
loading.value = true;
|
||||
|
||||
userLogin({
|
||||
username: loginForm.username,
|
||||
password: loginForm.password,
|
||||
})
|
||||
.then((res) => {
|
||||
const token = res?.token || '';
|
||||
// 写入用户信息
|
||||
localStorage.setItem('PAOPAO_TOKEN', token);
|
||||
|
||||
return userInfo(token);
|
||||
})
|
||||
.then((res) => {
|
||||
window.$message.success('登录成功');
|
||||
loading.value = false;
|
||||
|
||||
store.commit('updateUserinfo', res);
|
||||
store.commit('triggerAuth', false);
|
||||
loginForm.username = '';
|
||||
loginForm.password = '';
|
||||
})
|
||||
.catch((err) => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegister = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
registerRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
loading.value = true;
|
||||
|
||||
userRegister({
|
||||
username: registerForm.username,
|
||||
password: registerForm.password,
|
||||
})
|
||||
.then((res) => {
|
||||
return userLogin({
|
||||
username: registerForm.username,
|
||||
password: registerForm.password,
|
||||
});
|
||||
})
|
||||
.then((res) => {
|
||||
const token = res?.token || '';
|
||||
// 写入用户信息
|
||||
localStorage.setItem('PAOPAO_TOKEN', token);
|
||||
|
||||
return userInfo(token);
|
||||
})
|
||||
.then((res) => {
|
||||
window.$message.success('注册成功');
|
||||
loading.value = false;
|
||||
|
||||
store.commit('updateUserinfo', res);
|
||||
store.commit('triggerAuth', false);
|
||||
registerForm.username = '';
|
||||
registerForm.password = '';
|
||||
registerForm.repassword = '';
|
||||
})
|
||||
.catch((err) => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const token = localStorage.getItem('PAOPAO_TOKEN') || '';
|
||||
if (token) {
|
||||
userInfo(token)
|
||||
.then((res) => {
|
||||
store.commit('updateUserinfo', res);
|
||||
store.commit('triggerAuth', false);
|
||||
})
|
||||
.catch((err) => {
|
||||
store.commit('userLogout');
|
||||
});
|
||||
} else {
|
||||
store.commit('userLogout');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.auth-wrap {
|
||||
margin-top: -30px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="reply-compose-wrap">
|
||||
<div class="reply-switch">
|
||||
<span class="show" v-if="!showReply" @click="switchReply(true)">
|
||||
回复
|
||||
</span>
|
||||
<span class="hide" v-if="showReply" @click="switchReply(false)">
|
||||
取消
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="reply-input-wrap" v-if="showReply">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
ref="inputInstRef"
|
||||
size="small"
|
||||
:placeholder="
|
||||
props.atUsername
|
||||
? '@' + props.atUsername
|
||||
: '请输入回复内容..'
|
||||
"
|
||||
maxlength="100"
|
||||
v-model:value="replyContent"
|
||||
show-count
|
||||
clearable
|
||||
/>
|
||||
<n-button
|
||||
type="primary"
|
||||
size="small"
|
||||
ghost
|
||||
:loading="submitting"
|
||||
@click="submitReply"
|
||||
>
|
||||
回复
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { createCommentReply } from '@/api/post';
|
||||
|
||||
const props = defineProps({
|
||||
commentId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
atUserid: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
atUsername: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['reload', 'reset']);
|
||||
const inputInstRef = ref(null);
|
||||
const showReply = ref(false);
|
||||
const replyContent = ref('');
|
||||
const submitting = ref(false);
|
||||
const switchReply = (status) => {
|
||||
showReply.value = status;
|
||||
|
||||
if (status) {
|
||||
setTimeout(() => {
|
||||
inputInstRef.value?.focus();
|
||||
}, 10);
|
||||
} else {
|
||||
submitting.value = false;
|
||||
replyContent.value = '';
|
||||
emit('reset');
|
||||
}
|
||||
};
|
||||
const submitReply = () => {
|
||||
submitting.value = true;
|
||||
createCommentReply({
|
||||
comment_id: props.commentId,
|
||||
at_user_id: props.atUserid,
|
||||
content: replyContent.value,
|
||||
})
|
||||
.then((res) => {
|
||||
switchReply(false);
|
||||
window.$message.success('评论成功');
|
||||
emit('reload');
|
||||
})
|
||||
.catch((err) => {
|
||||
submitting.value = false;
|
||||
});
|
||||
};
|
||||
defineExpose({ switchReply });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.reply-compose-wrap {
|
||||
.reply-switch {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
margin: 10px 0;
|
||||
|
||||
.show {
|
||||
color: #18a058;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hide {
|
||||
opacity: 0.75;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue