feat: 管理端板块管理与旧版数据导入,补充部署运营文档
- 新增管理端板块管理页面与后端接口(admin_board) - 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具 - 聊天用户卡片、板块图标等 UI 组件与界面优化 - 补充 about/公告/1Panel 部署等文档 - gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
This commit is contained in:
@@ -23,6 +23,7 @@ var (
|
||||
type PostService struct {
|
||||
db *gorm.DB
|
||||
setting *SettingService
|
||||
board *BoardService
|
||||
devMode bool
|
||||
}
|
||||
|
||||
@@ -36,6 +37,12 @@ func (s *PostService) WithSetting(setting *SettingService) *PostService {
|
||||
return s
|
||||
}
|
||||
|
||||
// WithBoard 注入板块服务(发帖策略校验用);可链式调用
|
||||
func (s *PostService) WithBoard(board *BoardService) *PostService {
|
||||
s.board = board
|
||||
return s
|
||||
}
|
||||
|
||||
// WithDevMode 开发态:抽奖同 IP 去重对回环地址放宽
|
||||
func (s *PostService) WithDevMode(dev bool) *PostService {
|
||||
s.devMode = dev
|
||||
@@ -744,35 +751,37 @@ func (s *PostService) Create(in CreatePostInput) (*PostDetail, error) {
|
||||
typeMeta, _ = encodeMeta(qm)
|
||||
}
|
||||
|
||||
// 悬赏:创建时托管积分 + 防刷(未结算上限 / 每日上限)
|
||||
// 悬赏:创建时托管积分 + 防刷(未结算上限 / 每日上限);管理角色豁免防刷
|
||||
var bountyPts int
|
||||
if postType == model.PostTypeBounty {
|
||||
bm, _ := parseBountyMeta(typeMeta)
|
||||
bountyPts = bm.Points
|
||||
var bountyPosts []model.Post
|
||||
s.db.Select("type_meta").
|
||||
Where("user_id = ? AND post_type = ? AND deleted_at IS NULL", in.UserID, model.PostTypeBounty).
|
||||
Find(&bountyPosts)
|
||||
open := 0
|
||||
for _, bp := range bountyPosts {
|
||||
m, err := parseBountyMeta(bp.TypeMeta)
|
||||
if err != nil {
|
||||
continue
|
||||
if !s.isStaffUser(in.UserID) {
|
||||
var bountyPosts []model.Post
|
||||
s.db.Select("type_meta").
|
||||
Where("user_id = ? AND post_type = ? AND deleted_at IS NULL", in.UserID, model.PostTypeBounty).
|
||||
Find(&bountyPosts)
|
||||
open := 0
|
||||
for _, bp := range bountyPosts {
|
||||
m, err := parseBountyMeta(bp.TypeMeta)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if m.Escrowed && m.AcceptedCommentID == 0 && !m.Refunded && !m.Expired {
|
||||
open++
|
||||
}
|
||||
}
|
||||
if m.Escrowed && m.AcceptedCommentID == 0 && !m.Refunded && !m.Expired {
|
||||
open++
|
||||
if open >= 3 {
|
||||
return nil, errors.New("未结算悬赏最多同时 3 个,请先采纳或退回")
|
||||
}
|
||||
var todayN int64
|
||||
s.db.Model(&model.Post{}).
|
||||
Where("user_id = ? AND post_type = ? AND created_at >= CURRENT_DATE AND deleted_at IS NULL",
|
||||
in.UserID, model.PostTypeBounty).
|
||||
Count(&todayN)
|
||||
if todayN >= 5 {
|
||||
return nil, errors.New("今日悬赏发帖已达上限(5)")
|
||||
}
|
||||
}
|
||||
if open >= 3 {
|
||||
return nil, errors.New("未结算悬赏最多同时 3 个,请先采纳或退回")
|
||||
}
|
||||
var todayN int64
|
||||
s.db.Model(&model.Post{}).
|
||||
Where("user_id = ? AND post_type = ? AND created_at >= CURRENT_DATE AND deleted_at IS NULL",
|
||||
in.UserID, model.PostTypeBounty).
|
||||
Count(&todayN)
|
||||
if todayN >= 5 {
|
||||
return nil, errors.New("今日悬赏发帖已达上限(5)")
|
||||
}
|
||||
bm.Escrowed = true
|
||||
bm.Refunded = false
|
||||
@@ -1051,7 +1060,7 @@ func (s *PostService) EnsurePostVisible(postID, viewerID uint, loadActor func()
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkNewUserCooldown 新用户发帖冷静期(小时数取自站点设置,0=关闭)
|
||||
// checkNewUserCooldown 新用户发帖冷静期(小时数取自站点设置,0=关闭);管理角色豁免
|
||||
func (s *PostService) checkNewUserCooldown(userID uint) error {
|
||||
hours := DefaultCooldownHours
|
||||
if s.setting != nil {
|
||||
@@ -1066,12 +1075,25 @@ func (s *PostService) checkNewUserCooldown(userID uint) error {
|
||||
if err := s.db.First(&user, userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if model.IsStaff(user.Role) { // 管理角色不受发帖冷静期限制
|
||||
return nil
|
||||
}
|
||||
if time.Since(user.CreatedAt) < time.Duration(hours)*time.Hour {
|
||||
return fmt.Errorf("新用户注册 %d 小时后才能发帖", hours)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isStaffUser 作者是否管理角色(悬赏防刷等限制豁免用)
|
||||
func (s *PostService) isStaffUser(userID uint) bool {
|
||||
var role model.Role
|
||||
if err := s.db.Model(&model.User{}).Where("id = ?", userID).
|
||||
Select("role").Scan(&role).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
return model.IsStaff(role)
|
||||
}
|
||||
|
||||
// DeletePostOpts 删帖选项:staff 软删必填类型与理由;作者自删传零值
|
||||
type DeletePostOpts struct {
|
||||
DeleteType string
|
||||
|
||||
Reference in New Issue
Block a user