fix: 修复帖子提交审核权限缺失 + 防重复提交 + 分页逻辑复用
- 修复 Submit API 缺少 uid 权限检查,任意登录用户可提交他人帖子审核 - 前端 handleSubmit 添加 submitting 锁和按钮禁用/恢复,防止重复提交 - 分页计算 TotalPages/PrevPage/NextPage 提取为 Pagination 方法,消除三处重复代码
This commit is contained in:
@ -23,5 +23,31 @@ func (p *Pagination) Offset() int {
|
||||
return (p.Page - 1) * p.PageSize
|
||||
}
|
||||
|
||||
// TotalPages 根据 total 计算总页数
|
||||
func (p *Pagination) TotalPages(total int64) int {
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
return int((total + int64(p.PageSize) - 1) / int64(p.PageSize))
|
||||
}
|
||||
|
||||
// PrevPage 上一页(最小为 1)
|
||||
func (p *Pagination) PrevPage() int {
|
||||
prev := p.Page - 1
|
||||
if prev < 1 {
|
||||
return 1
|
||||
}
|
||||
return prev
|
||||
}
|
||||
|
||||
// NextPage 下一页(最大为 totalPages)
|
||||
func (p *Pagination) NextPage(totalPages int) int {
|
||||
next := p.Page + 1
|
||||
if next > totalPages {
|
||||
return totalPages
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// 固定时间格式,前后端统一
|
||||
const TimeFormat = time.RFC3339
|
||||
|
||||
@ -43,11 +43,7 @@ func (ctrl *AdminPostController) PostsPage(c *gin.Context) {
|
||||
|
||||
p := common.Pagination{Page: page, PageSize: pageSize}
|
||||
p.DefaultPagination()
|
||||
totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize))
|
||||
prevPage := p.Page - 1
|
||||
if prevPage < 1 { prevPage = 1 }
|
||||
nextPage := p.Page + 1
|
||||
if nextPage > totalPages { nextPage = totalPages }
|
||||
totalPages := p.TotalPages(total)
|
||||
|
||||
c.HTML(http.StatusOK, "admin/posts/index.html", common.BuildAdminPageData(c, gin.H{
|
||||
"Title": "内容管理",
|
||||
@ -55,8 +51,8 @@ func (ctrl *AdminPostController) PostsPage(c *gin.Context) {
|
||||
"Total": total,
|
||||
"Page": p.Page,
|
||||
"TotalPages": totalPages,
|
||||
"PrevPage": prevPage,
|
||||
"NextPage": nextPage,
|
||||
"PrevPage": p.PrevPage(),
|
||||
"NextPage": p.NextPage(totalPages),
|
||||
"Keyword": keyword,
|
||||
"Status": status,
|
||||
"StatusNames": model.PostStatusDisplayNames,
|
||||
|
||||
@ -49,11 +49,7 @@ func (ctrl *PostController) ListPage(c *gin.Context) {
|
||||
|
||||
p := common.Pagination{Page: page, PageSize: pageSize}
|
||||
p.DefaultPagination()
|
||||
totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize))
|
||||
prevPage := p.Page - 1
|
||||
if prevPage < 1 { prevPage = 1 }
|
||||
nextPage := p.Page + 1
|
||||
if nextPage > totalPages { nextPage = totalPages }
|
||||
totalPages := p.TotalPages(total)
|
||||
|
||||
c.HTML(http.StatusOK, "posts/index.html", common.BuildPageData(c, gin.H{
|
||||
"Title": "社区帖子",
|
||||
@ -61,8 +57,8 @@ func (ctrl *PostController) ListPage(c *gin.Context) {
|
||||
"Total": total,
|
||||
"Page": p.Page,
|
||||
"TotalPages": totalPages,
|
||||
"PrevPage": prevPage,
|
||||
"NextPage": nextPage,
|
||||
"PrevPage": p.PrevPage(),
|
||||
"NextPage": p.NextPage(totalPages),
|
||||
"Keyword": keyword,
|
||||
"ExtraCSS": "/static/css/posts.css",
|
||||
}))
|
||||
@ -276,12 +272,30 @@ func (ctrl *PostController) Delete(c *gin.Context) {
|
||||
|
||||
// Submit API 提交审核
|
||||
func (ctrl *PostController) Submit(c *gin.Context) {
|
||||
uidObj, _ := c.Get("uid")
|
||||
uid, ok := uidObj.(uint)
|
||||
if !ok {
|
||||
common.Error(c, http.StatusUnauthorized, "请先登录")
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusBadRequest, "无效的帖子 ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 权限检查:仅帖子作者可提交审核
|
||||
post, err := ctrl.postService.GetByID(uint(id))
|
||||
if err != nil {
|
||||
common.Error(c, http.StatusNotFound, "帖子不存在")
|
||||
return
|
||||
}
|
||||
if post.UserID != uid {
|
||||
common.Error(c, http.StatusForbidden, "无权操作此帖子")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctrl.postService.SubmitForAudit(uint(id)); err != nil {
|
||||
if errors.Is(err, common.ErrPostCannotSubmit) || errors.Is(err, common.ErrPostNotFound) {
|
||||
common.Error(c, http.StatusBadRequest, err.Error())
|
||||
@ -312,13 +326,12 @@ func (ctrl *PostController) ListAPI(c *gin.Context) {
|
||||
|
||||
p := common.Pagination{Page: page, PageSize: pageSize}
|
||||
p.DefaultPagination()
|
||||
totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize))
|
||||
|
||||
common.Ok(c, model.PostListResult{
|
||||
Items: posts,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
TotalPages: totalPages,
|
||||
TotalPages: p.TotalPages(total),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import { common, createLowlight } from 'lowlight'
|
||||
let editor = null
|
||||
let currentHTML = ''
|
||||
let draftTimer = null
|
||||
let submitting = false
|
||||
|
||||
// ---- 初始化 ----
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@ -337,25 +338,34 @@ function handleKeyboard(e) {
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
|
||||
const postId = document.getElementById('postId')?.value
|
||||
const title = document.getElementById('postTitle')?.value.trim()
|
||||
if (submitting) return
|
||||
submitting = true
|
||||
|
||||
if (!title) { alert('请输入标题'); return }
|
||||
|
||||
// 获取编辑器 HTML 内容
|
||||
const body = editor ? editor.getHTML() : ''
|
||||
// 忽略空段落
|
||||
const cleanBody = body === '<p></p>' ? '' : body
|
||||
if (!cleanBody.trim()) { alert('请输入正文'); return }
|
||||
|
||||
const isEdit = !!postId
|
||||
const url = isEdit ? '/api/posts/' + postId : '/api/posts'
|
||||
const method = isEdit ? 'PUT' : 'POST'
|
||||
|
||||
const csrfMeta = document.querySelector('meta[name="csrf-token"]')
|
||||
const csrf = csrfMeta ? csrfMeta.getAttribute('content') : ''
|
||||
const submitBtn = document.querySelector('.btn-submit')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true
|
||||
submitBtn.textContent = '提交中...'
|
||||
}
|
||||
|
||||
try {
|
||||
const postId = document.getElementById('postId')?.value
|
||||
const title = document.getElementById('postTitle')?.value.trim()
|
||||
|
||||
if (!title) { alert('请输入标题'); return }
|
||||
|
||||
// 获取编辑器 HTML 内容
|
||||
const body = editor ? editor.getHTML() : ''
|
||||
// 忽略空段落
|
||||
const cleanBody = body === '<p></p>' ? '' : body
|
||||
if (!cleanBody.trim()) { alert('请输入正文'); return }
|
||||
|
||||
const isEdit = !!postId
|
||||
const url = isEdit ? '/api/posts/' + postId : '/api/posts'
|
||||
const method = isEdit ? 'PUT' : 'POST'
|
||||
|
||||
const csrfMeta = document.querySelector('meta[name="csrf-token"]')
|
||||
const csrf = csrfMeta ? csrfMeta.getAttribute('content') : ''
|
||||
|
||||
const resp = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
@ -380,5 +390,12 @@ async function handleSubmit(e) {
|
||||
} else {
|
||||
alert('请求失败,请重试')
|
||||
}
|
||||
} finally {
|
||||
submitting = false
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = submitBtn.textContent === '提交中...' ?
|
||||
(document.getElementById('postId')?.value ? '保存修改' : '发布帖子') : submitBtn.textContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user