“â初步完成后端文件下载

dev
truthhun 1 year ago
parent 9b59d50bec
commit b364dbbda2

@ -123,6 +123,10 @@ message SearchDocumentReply {
repeated Document document = 3;
}
message DownloadDocumentReply {
string url = 1;
}
service DocumentAPI {
rpc ListDocumentForHome(ListDocumentForHomeRequest) returns ( ListDocumentForHomeResponse) {
option (google.api.http) = {
@ -163,6 +167,12 @@ service DocumentAPI {
};
}
rpc DownloadDocument(Document) returns (DownloadDocumentReply) {
option (google.api.http) = {
get : '/api/v1/document/download',
};
}
rpc ListDocument(ListDocumentRequest) returns (ListDocumentReply) {
option (google.api.http) = {
get : '/api/v1/document/list',

@ -35,6 +35,7 @@ message User {
string identity = 16;
string realname = 17;
repeated int64 group_id = 18;
int32 credit_count = 22;
}
message RegisterAndLoginRequest {
@ -98,6 +99,27 @@ message SetUserRequest {
repeated int64 group_id = 4;
}
message Dynamic {
int64 id = 1;
int64 user_id = 2;
string content = 3;
int32 type = 4; //
string username = 7;
google.protobuf.Timestamp created_at = 5 [ (gogoproto.stdtime) = true ];
google.protobuf.Timestamp updated_at = 6 [ (gogoproto.stdtime) = true ];
}
message ListUserDynamicRequest {
int64 page = 1;
int64 size = 2;
int64 user_id = 3;
}
message ListUserDynamicReply {
int64 total = 1;
repeated Dynamic dynamic = 2;
}
service UserAPI {
//
rpc Register(RegisterAndLoginRequest) returns (LoginReply) {
@ -202,6 +224,13 @@ service UserAPI {
};
}
//
rpc ListUserDynamic(ListUserDynamicRequest) returns (ListUserDynamicReply) {
option (google.api.http) = {
get : '/api/v1/user/dynamic',
};
}
//
// rpc ListUserFans(ListUserFansRequest) returns (ListUserReply) {
// option (google.api.http) = {

@ -18,6 +18,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"github.com/golang-jwt/jwt"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -257,6 +258,24 @@ func (s *AttachmentAPIService) ViewDocumentCover(ctx *gin.Context) {
ctx.File(file)
}
// DownloadDocument 下载文档
func (s *AttachmentAPIService) DownloadDocument(ctx *gin.Context) {
claims := &jwt.StandardClaims{}
token := ctx.Param("jwt")
cfg := s.dbModel.GetConfigOfDownload(model.ConfigDownloadSecretKey)
// 验证JWT是否合法
jwtToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
return []byte(cfg.SecretKey), nil
})
if err != nil || !jwtToken.Valid {
ctx.String(http.StatusBadRequest, "下载链接已失效")
return
}
filename := ctx.Query("filename")
file := fmt.Sprintf("documents/%s/%s%s", strings.Join(strings.Split(claims.Id, "")[:5], "/"), claims.Id, filepath.Ext(filename))
ctx.FileAttachment(file, filename)
}
// UploadArticle 上传文章相关图片和视频。这里不验证文件格式。
//
// 注意当前适配了wangeditor的接口规范如果需要适配其他编辑器需要修改此接口或者增加其他接口

@ -67,6 +67,9 @@ func (s *CommentAPIService) CreateComment(ctx context.Context, req *pb.CreateCom
return nil, status.Error(codes.PermissionDenied, err.Error())
}
if ips, _ := util.GetGRPCRemoteIP(ctx); len(ips) > 0 {
comment.IP = ips[0]
}
comment.Status = defaultStatus
comment.UserId = userClaims.UserId
err = s.dbModel.CreateComment(comment)

@ -3,6 +3,7 @@ package biz
import (
"context"
"fmt"
"net/url"
"strings"
"time"
@ -12,6 +13,7 @@ import (
"moredoc/util"
"moredoc/util/filetil"
"github.com/golang-jwt/jwt"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -557,3 +559,90 @@ func (s *DocumentAPIService) SearchDocument(ctx context.Context, req *pb.SearchD
res.Spend = fmt.Sprintf("%.3f", time.Since(now).Seconds())
return res, nil
}
// 下载文档
// 0. 查询用户是否登录
// 1. 查询文档是否存在
// 2. 查询用户是否购买和下载过
// 3. 查询文档是否免费
func (s *DocumentAPIService) DownloadDocument(ctx context.Context, req *pb.Document) (res *pb.DownloadDocumentReply, err error) {
cfg := s.dbModel.GetConfigOfDownload()
userClaims, err := s.checkLogin(ctx)
if err != nil && !cfg.EnableGuestDownload {
// 未登录且不允许游客下载
return res, status.Errorf(codes.PermissionDenied, err.Error())
}
var userId int64
if userClaims != nil {
userId = userClaims.UserId
}
downloadToday := s.dbModel.CountDownloadToday(userId)
if downloadToday >= int64(cfg.TimesEveryDay) {
return res, status.Errorf(codes.PermissionDenied, "今日下载次数已达上限(%d)", cfg.TimesEveryDay)
}
// 查询文档存不存在
doc, err := s.dbModel.GetDocument(req.Id, "id", "price", "status", "title", "ext")
if err != nil || doc.Status == model.DocumentStatusDisabled {
return res, status.Errorf(codes.NotFound, "文档不存在")
}
// 文档不免费且未登录
if doc.Price > 0 && userId == 0 {
return res, status.Errorf(codes.PermissionDenied, "付费文档,请先登录再下载")
}
// 查询附件存不存在
attachment := s.dbModel.GetAttachmentByTypeAndTypeId(model.AttachmentTypeDocument, doc.Id, "id", "hash")
if attachment.Id == 0 {
return res, status.Errorf(codes.NotFound, "附件不存在")
}
ip := ""
if ips, _ := util.GetGRPCRemoteIP(ctx); len(ips) > 0 {
ip = ips[0]
}
user, _ := s.dbModel.GetUser(userId)
if user.CreditCount < doc.Price {
return res, status.Errorf(codes.PermissionDenied, "魔豆不足,无法下载")
}
// 直接返回下载地址
err = s.dbModel.CreateDownload(&model.Download{
UserId: userId,
DocumentId: doc.Id,
Ip: ip,
IsPay: s.dbModel.CanIFreeDownload(userId, doc.Id),
})
if err != nil {
return res, status.Errorf(codes.Internal, "创建下载失败:%s", err.Error())
}
link, err := s.generateDownloadURL(doc, cfg, attachment.Hash)
if err != nil {
return res, status.Errorf(codes.Internal, "生成下载地址失败:%s", err.Error())
}
res = &pb.DownloadDocumentReply{
Url: link,
}
// TODO: 添加用户的下载动态
return res, nil
}
// 通过JWT生成下载文档的URL
func (s *DocumentAPIService) generateDownloadURL(document model.Document, cfg model.ConfigDownload, hash string) (link string, err error) {
expiredAt := time.Now().Add(time.Second * time.Duration(cfg.UrlDuration)).Unix()
claims := jwt.StandardClaims{
ExpiresAt: expiredAt,
Id: hash,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(cfg.SecretKey))
if err != nil {
return "", err
}
return fmt.Sprintf("/download/%s?filename=%s", tokenString, url.QueryEscape(document.Title+document.Ext)), nil
}

@ -24,6 +24,7 @@ type Comment struct {
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:文档ID;index:idx_document_id;"`
Status int8 `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(4);size:4;default:0;comment:0 待审1过审2拒绝;"`
CommentCount int `form:"comment_count" json:"comment_count,omitempty" gorm:"column:comment_count;type:int(11);size:11;default:0;comment:评论数量;"`
IP string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(20);size:20;default:'';comment:IP地址;"`
CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:评论时间;"`
UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:评论更新时间;"`
}

@ -27,6 +27,8 @@ const (
ConfigCategoryFooter = "footer"
// ConfigCategoryConverter 转换配置项
ConfigCategoryConverter = "converter"
// 下载配置
ConfigCategoryDownload = "download"
)
type Config struct {
@ -236,6 +238,27 @@ const (
ConfigConverterEnableGZIP = "enable_gzip" // 是否启用 GZIP
)
const (
// 是否允许游客下载
ConfigDownloadEnableGuestDownload = "enable_guest_download"
// 每天允许下载的次数
ConfigDownloadTimesEveryDay = "times_every_day"
// 下载链接地址签名密钥
ConfigDownloadSecretKey = "secret_key"
// 生成的下载链接有效期,单位为秒
ConfigDownloadUrlDuration = "url_duration"
// 购买文档后多少天内允许免费重复下载
ConfigDownloadFreeDownloadDuration = "free_download_duration"
)
type ConfigDownload struct {
EnableGuestDownload bool `json:"enable_guest_download"` // 是否允许游客下载
TimesEveryDay int32 `json:"times_every_day"` // 每天允许下载的次数
SecretKey string `json:"secret_key"` // 下载链接地址签名密钥
UrlDuration int32 `json:"url_duration"` // 生成的下载链接有效期,单位为秒
FreeDownloadDuration int32 `json:"free_download_duration"` // 购买文档后多少天内允许免费重复下载
}
// ConfigConverter 转换配置
type ConfigConverter struct {
MaxPreview int `json:"max_preview"` // 文档所允许的最大预览页数0 表示不限制,全部转换
@ -280,6 +303,37 @@ func (m *DBModel) GetConfigOfFooter() (config ConfigFooter) {
return
}
func (m *DBModel) GetConfigOfDownload(name ...string) (config ConfigDownload) {
var configs []Config
db := m.db
if len(name) > 0 {
db = db.Where("name IN (?)", name)
}
err := db.Where("category = ?", ConfigCategoryDownload).Find(&configs).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetConfigOfDownload", zap.Error(err))
}
var data = make(map[string]interface{})
for _, cfg := range configs {
switch cfg.Name {
case ConfigDownloadEnableGuestDownload:
data[cfg.Name], _ = strconv.ParseBool(cfg.Value)
case ConfigDownloadTimesEveryDay, ConfigDownloadUrlDuration, ConfigDownloadFreeDownloadDuration:
data[cfg.Name], _ = strconv.ParseInt(cfg.Value, 10, 32)
default:
data[cfg.Name] = cfg.Value
}
}
bytes, _ := json.Marshal(data)
json.Unmarshal(bytes, &config)
return
}
// GetConfigOfCaptcha 获取验证码配置
func (m *DBModel) GetConfigOfCaptcha() (config ConfigCaptcha) {
var configs []Config
@ -290,22 +344,22 @@ func (m *DBModel) GetConfigOfCaptcha() (config ConfigCaptcha) {
for _, cfg := range configs {
switch cfg.Name {
case "length":
case ConfigCaptchaLength:
config.Length, _ = strconv.Atoi(cfg.Value)
if config.Length <= 0 {
config.Length = 6
}
case "width":
case ConfigCaptchaWidth:
config.Width, _ = strconv.Atoi(cfg.Value)
if config.Width <= 0 {
config.Width = 240
}
case "height":
case ConfigCaptchaHeight:
config.Height, _ = strconv.Atoi(cfg.Value)
if config.Height <= 0 {
config.Height = 60
}
case "type":
case ConfigCaptchaType:
// 验证码类型
config.Type = cfg.Value
}
@ -438,6 +492,13 @@ func (m *DBModel) initConfig() (err error) {
{Category: ConfigCategoryConverter, Name: ConfigConverterTimeout, Label: "转换超时(分钟)", Value: "30", Placeholder: "文档转换超时时间默认为30分钟", InputType: "number", Sort: 16, Options: ""},
{Category: ConfigCategoryConverter, Name: ConfigConverterEnableGZIP, Label: "是否启用GZIP压缩", Value: "true", Placeholder: "是否对文档SVG预览文件启用GZIP压缩启用后转换效率会【稍微】下降但相对直接的SVG文件减少75%的存储空间", InputType: "switch", Sort: 17, Options: ""},
{Category: ConfigCategoryConverter, Name: ConfigConverterEnableSVGO, Label: "是否启用SVGO", Value: "false", Placeholder: "是否对文档SVG预览文件启用SVGO压缩启用后转换效率会【明显】下降但相对直接的SVG文件减少50%左右的存储空间", InputType: "switch", Sort: 18, Options: ""},
// 下载配置
{Category: ConfigCategoryDownload, Name: ConfigDownloadEnableGuestDownload, Label: "是否允许游客下载", Value: "false", Placeholder: "是否允许游客下载。启用之后,未登录用户可以下载免费文档,且不受下载次数控制", InputType: "switch", Sort: 10, Options: ""},
{Category: ConfigCategoryDownload, Name: ConfigDownloadFreeDownloadDuration, Label: "购买文档后多少天内允许免费重复下载", Value: "0", Placeholder: "0表示不再次下载仍需购买大于0表示指定多少天内有效", InputType: "number", Sort: 20, Options: ""},
{Category: ConfigCategoryDownload, Name: ConfigDownloadUrlDuration, Label: "下载链接有效时长(秒)", Value: "60", Placeholder: "生成文档下载链接后多少秒之后链接失效", InputType: "number", Sort: 30, Options: ""},
{Category: ConfigCategoryDownload, Name: ConfigDownloadTimesEveryDay, Label: "每天允许下载次数", Value: "0", Placeholder: "每天允许下载多少篇文档0表示不限制", InputType: "number", Sort: 40, Options: ""},
{Category: ConfigCategoryDownload, Name: ConfigDownloadSecretKey, Label: "链接签名密钥", Value: "moredoc", Placeholder: "链接签名密钥,用于加密下载链接", InputType: "text", Sort: 50, Options: ""},
}
for _, cfg := range cfgs {

@ -11,50 +11,56 @@ import (
type Download struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;index:user_id;comment:下载文档的用户ID;"`
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;default:0;comment:被下载的文档ID;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;index:idx_user_id;comment:下载文档的用户ID;"`
DocumentId int64 `form:"document_id" json:"document_id,omitempty" gorm:"column:document_id;type:bigint(20);size:20;index:idx_document_id;default:0;comment:被下载的文档ID;"`
Ip string `form:"ip" json:"ip,omitempty" gorm:"column:ip;type:varchar(16);size:16;comment:下载文档的用户IP;"`
CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
IsPay bool `form:"is_pay" json:"is_pay,omitempty" gorm:"column:is_pay;type:tinyint(1);size:1;default:0;comment:是否付费下载;"`
CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;index:idx_created_at"`
UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整
//message Download {
// int64 id = 1;
// int64 user_id = 2;
// int64 document_id = 3;
// string ip = 4;
// google.protobuf.Timestamp created_at = 5 [ (gogoproto.stdtime) = true ];
// google.protobuf.Timestamp updated_at = 6 [ (gogoproto.stdtime) = true ];
//}
func (Download) TableName() string {
return tablePrefix + "download"
}
// CreateDownload 创建Download
// TODO: 创建成功之后,注意相关表统计字段数值的增减
func (m *DBModel) CreateDownload(download *Download) (err error) {
err = m.db.Create(download).Error
tx := m.db.Begin()
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
err = tx.Create(download).Error
if err != nil {
m.logger.Error("CreateDownload", zap.Error(err))
return
}
return
}
// UpdateDownload 更新Download如果需要更新指定字段则请指定updateFields参数
func (m *DBModel) UpdateDownload(download *Download, updateFields ...string) (err error) {
db := m.db.Model(download)
updateFields = m.FilterValidFields(Download{}.TableName(), updateFields...)
if len(updateFields) > 0 { // 更新指定字段
db = db.Select(updateFields)
err = tx.Model(&Document{}).Where("id = ?", download.DocumentId).Update("download_count", gorm.Expr("download_count + ?", 1)).Error
if err != nil {
m.logger.Error("CreateDownload", zap.Error(err))
return
}
err = db.Where("id = ?", download.Id).Updates(download).Error
if err != nil {
m.logger.Error("UpdateDownload", zap.Error(err))
doc, _ := m.GetDocument(download.DocumentId, "id", "user_id", "price")
if download.IsPay && doc.Price > 0 && download.UserId > 0 {
// 下载该文档的用户扣除积分
err = tx.Model(&User{}).Where("id = ?", download.UserId).Update("credit_count", gorm.Expr("credit_count - ?", doc.Price)).Error
if err != nil {
m.logger.Error("CreateDownload", zap.Error(err))
return
}
// 文档的作者增加积分
err = tx.Model(&User{}).Where("id = ?", doc.UserId).Update("credit_count", gorm.Expr("credit_count + ?", doc.Price)).Error
if err != nil {
m.logger.Error("CreateDownload", zap.Error(err))
return
}
}
return
}
@ -162,12 +168,27 @@ func (m *DBModel) GetDownloadList(opt OptionGetDownloadList) (downloadList []Dow
return
}
// DeleteDownload 删除数据
// TODO: 删除数据之后,存在 download_id 的关联表,需要删除对应数据,同时相关表的统计数值,也要随着减少
func (m *DBModel) DeleteDownload(ids []interface{}) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Download{}).Error
// CanIFreeDownload 判断用户是否可以免费下载
// 最后一次付费下载时间 + 免费下载时长 > 当前时间
func (m *DBModel) CanIFreeDownload(userId, documentId int64) (yes bool) {
var download Download
m.db.Where("user_id = ? and document_id = ? and is_pay = ?", userId, documentId, 1).Last(&download)
if download.Id == 0 {
return false
}
cfg := m.GetConfigOfDownload(ConfigDownloadFreeDownloadDuration)
return download.CreatedAt.Add(time.Duration(cfg.FreeDownloadDuration) * time.Hour * 24).After(time.Now())
}
// CountDownloadToday 统计今日下载次数
func (m *DBModel) CountDownloadToday(userId int64) (total int64) {
if userId == 0 {
return
}
err := m.db.Model(&Download{}).Where("user_id = ?", userId).Where("created_at >= ?", time.Now().Format("2006-01-02")).Count(&total).Error
if err != nil {
m.logger.Error("DeleteDownload", zap.Error(err))
m.logger.Error("CountDownloadToday", zap.Error(err))
}
return
}

@ -0,0 +1,148 @@
package model
import (
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
type Dynamic struct {
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
UserId int64 `form:"user_id" json:"user_id,omitempty" gorm:"column:user_id;type:bigint(20);size:20;default:0;index:idx_user_id;comment:;"`
Content string `form:"content" json:"content,omitempty" gorm:"column:content;type:tinytext;comment:内容存储JSON;"`
Type int `form:"type" json:"type,omitempty" gorm:"column:type;type:smallint(6);size:6;default:0;comment:类型;index:idx_type;"`
CreatedAt *time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整
//message Dynamic {
// int64 id = 1;
// int64 user_id = 2;
// string content = 3;
// int32 type = 4;
// = 0;
// = 0;
//}
const (
DynamicTypeComment = 1 // 发表评论
DynamicTypeFavorite = 2 // 收藏文档
DynamicTypeUpload = 3 // 上传文档
DynamicTypeDownload = 4 // 下载文档
DynamicTypeLogin = 5 // 登录
DynamicTypeRegister = 6 // 注册
DynamicTypeAvatar = 7 // 更新了头像
DynamicTypePassword = 8 // 修改密码
DynamicTypeInfo = 9 // 修改个人信息
DynamicTypeVerify = 10 // 实名认证
DynamicTypeSign = 11 // 签到
DynamicTypeShare = 12 // 分享文档
DynamicTypeFollow = 13 // 关注用户
)
func (Dynamic) TableName() string {
return tablePrefix + "dynamic"
}
// CreateDynamic 创建Dynamic
func (m *DBModel) CreateDynamic(dynamic *Dynamic) (err error) {
err = m.db.Create(dynamic).Error
if err != nil {
m.logger.Error("CreateDynamic", zap.Error(err))
return
}
return
}
// UpdateDynamic 更新Dynamic如果需要更新指定字段则请指定updateFields参数
func (m *DBModel) UpdateDynamic(dynamic *Dynamic, updateFields ...string) (err error) {
db := m.db.Model(dynamic)
tableName := Dynamic{}.TableName()
updateFields = m.FilterValidFields(tableName, updateFields...)
if len(updateFields) > 0 { // 更新指定字段
db = db.Select(updateFields)
} else { // 更新全部字段,包括零值字段
db = db.Select(m.GetTableFields(tableName))
}
err = db.Where("id = ?", dynamic.Id).Updates(dynamic).Error
if err != nil {
m.logger.Error("UpdateDynamic", zap.Error(err))
}
return
}
// GetDynamic 根据id获取Dynamic
func (m *DBModel) GetDynamic(id interface{}, fields ...string) (dynamic Dynamic, err error) {
db := m.db
fields = m.FilterValidFields(Dynamic{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
err = db.Where("id = ?", id).First(&dynamic).Error
return
}
type OptionGetDynamicList struct {
Page int
Size int
WithCount bool // 是否返回总数
Ids []interface{} // id列表
SelectFields []string // 查询字段
QueryRange map[string][2]interface{} // map[field][]{min,max}
QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
QueryLike map[string][]interface{} // map[field][]{value1,value2,...}
Sort []string
}
// GetDynamicList 获取Dynamic列表
func (m *DBModel) GetDynamicList(opt *OptionGetDynamicList) (dynamicList []Dynamic, total int64, err error) {
tableName := Dynamic{}.TableName()
db := m.db.Model(&Dynamic{})
db = m.generateQueryRange(db, tableName, opt.QueryRange)
db = m.generateQueryIn(db, tableName, opt.QueryIn)
db = m.generateQueryLike(db, tableName, opt.QueryLike)
if len(opt.Ids) > 0 {
db = db.Where("id in (?)", opt.Ids)
}
if opt.WithCount {
err = db.Count(&total).Error
if err != nil {
m.logger.Error("GetDynamicList", zap.Error(err))
return
}
}
opt.SelectFields = m.FilterValidFields(tableName, opt.SelectFields...)
if len(opt.SelectFields) > 0 {
db = db.Select(opt.SelectFields)
}
// TODO: 没有排序参数的话,可以自行指定排序字段
db = m.generateQuerySort(db, tableName, opt.Sort)
db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
err = db.Find(&dynamicList).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetDynamicList", zap.Error(err))
}
return
}
// DeleteDynamic 删除数据
// TODO: 删除数据之后,存在 dynamic_id 的关联表,需要删除对应数据,同时相关表的统计数值,也要随着减少
func (m *DBModel) DeleteDynamic(ids []interface{}) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Dynamic{}).Error
if err != nil {
m.logger.Error("DeleteDynamic", zap.Error(err))
}
return
}

@ -172,6 +172,7 @@ func (m *DBModel) SyncDB() (err error) {
&Article{},
&Favorite{},
&Comment{},
&Dynamic{},
}
if err = m.db.AutoMigrate(tableModels...); err != nil {
m.logger.Fatal("SyncDB", zap.Error(err))

@ -41,6 +41,7 @@ type User struct {
FansCount int `form:"fans_count" json:"fans_count,omitempty" gorm:"column:fans_count;type:int(10);default:0;comment:粉丝数;"`
FavoriteCount int `form:"favorite_count" json:"favorite_count,omitempty" gorm:"column:favorite_count;type:int(10);default:0;comment:收藏数;"`
CommentCount int `form:"comment_count" json:"comment_count,omitempty" gorm:"column:comment_count;type:int(11);size:11;default:0;comment:评论数;"`
CreditCount int `form:"credit_count" json:"credit_count,omitempty" gorm:"column:credit_count;type:int(11);size:11;default:0;comment:积分数,魔豆;"`
Status int8 `form:"status" json:"status,omitempty" gorm:"column:status;type:tinyint(4);size:4;default:0;index:status;comment:用户状态0正常 1禁用 2审核中 3审核拒绝 4审核忽略;"`
Avatar string `form:"avatar" json:"avatar,omitempty" gorm:"column:avatar;type:varchar(255);size:255;comment:头像;"`
Identity string `form:"identity" json:"identity,omitempty" gorm:"column:identity;type:char(18);size:18;comment:身份证号码;"`
@ -50,31 +51,6 @@ type User struct {
UpdatedAt *time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
}
// 这里是proto文件中的结构体可以根据需要删除或者调整
//message User {
// int64 id = 1;
// string username = 2;
// string password = 3;
// string mobile = 5;
// string email = 6;
// string address = 7;
// string signature = 8;
// string last_login_ip = 9;
// string register_ip = 10;
// int32 doc_count = 11;
// int32 follow_count = 12;
// int32 fans_count = 13;
// int32 favorite_count = 14;
// int32 comment_count = 15;
// int32 status = 16;
// string avatar = 17;
// string identity = 18;
// string realname = 19;
// google.protobuf.Timestamp login_at = 20 [ (gogoproto.stdtime) = true ];
// google.protobuf.Timestamp created_at = 21 [ (gogoproto.stdtime) = true ];
// google.protobuf.Timestamp updated_at = 22 [ (gogoproto.stdtime) = true ];
//}
func (User) TableName() string {
return tablePrefix + "user"
}
@ -160,9 +136,12 @@ func (m *DBModel) UpdateUser(user *User, updateFields ...string) (err error) {
}
// GetUser 根据id获取User
func (m *DBModel) GetUser(id interface{}, fields ...string) (user User, err error) {
db := m.db
func (m *DBModel) GetUser(id int64, fields ...string) (user User, err error) {
if id <= 0 {
return user, gorm.ErrRecordNotFound
}
db := m.db
fields = m.FilterValidFields(User{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)

@ -19,6 +19,7 @@ func RegisterGinRouter(app *gin.Engine, dbModel *model.DBModel, logger *zap.Logg
})
app.GET("/view/page/:hash/:page", attachmentAPIService.ViewDocumentPages)
app.GET("/view/cover/:hash", attachmentAPIService.ViewDocumentCover)
app.GET("/download/:jwt", attachmentAPIService.DownloadDocument)
checkPermissionGroup := app.Group("/api/v1/upload")
checkPermissionGroup.Use(auth.AuthGin())

@ -24,7 +24,7 @@
<el-col :span="8"
><div>财富</div>
<div class="el-link el-link--primary">
{{ user.money || 0 }}
{{ user.credit_count || 0 }}
</div>
</el-col>
</el-row>
@ -52,7 +52,7 @@ export default {
signature: '',
doc_count: 0,
favorite_count: 0,
money: 0,
credit_count: 0,
}
},
},

@ -52,6 +52,10 @@ export default {
label: '转换配置',
value: 'converter',
},
{
label: '下载配置',
value: 'download',
},
],
}
},

Loading…
Cancel
Save