优化页面、修复BUG
This commit is contained in:
@@ -65,12 +65,7 @@ func Login(c *gin.Context) {
|
||||
utils.Success(c, gin.H{
|
||||
"token": tokenString,
|
||||
"expire": expireUnix,
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
},
|
||||
"user": repositories.BuildUserResponse(user),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func GetCategories(c *gin.Context) {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, categories)
|
||||
utils.Success(c, repositories.BuildCategoriesResponse(categories))
|
||||
}
|
||||
|
||||
// GetCategoryByID 根据ID获取分类
|
||||
@@ -38,7 +38,7 @@ func GetCategoryByID(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, category)
|
||||
utils.Success(c, repositories.BuildCategoryResponse(category))
|
||||
}
|
||||
|
||||
// AdminCreateCategory 创建分类
|
||||
@@ -54,7 +54,7 @@ func AdminCreateCategory(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, category)
|
||||
utils.Success(c, repositories.BuildCategoryResponse(&category))
|
||||
}
|
||||
|
||||
// AdminUpdateCategory 更新分类
|
||||
@@ -78,7 +78,7 @@ func AdminUpdateCategory(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, category)
|
||||
utils.Success(c, repositories.BuildCategoryResponse(&category))
|
||||
}
|
||||
|
||||
// AdminDeleteCategory 删除分类
|
||||
|
||||
@@ -16,7 +16,7 @@ func GetColumns(c *gin.Context) {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
utils.Success(c, columns)
|
||||
utils.Success(c, repositories.BuildColumnsResponse(columns))
|
||||
}
|
||||
|
||||
// GetColumnByID 根据ID获取专栏
|
||||
|
||||
@@ -20,7 +20,11 @@ func GetSettings(c *gin.Context) {
|
||||
|
||||
// 只返回前端需要的公开配置项
|
||||
publicSettings := make(map[string]string)
|
||||
publicKeys := []string{"site_title", "site_description", "site_author", "site_keywords", "visible_menus", "posts_per_page"}
|
||||
publicKeys := []string{
|
||||
"site_title", "site_description", "site_author", "site_keywords",
|
||||
"visible_menus", "posts_per_page", "works_per_page", "snippets_per_page",
|
||||
"footer_icp", "contact_email",
|
||||
}
|
||||
|
||||
for _, key := range publicKeys {
|
||||
if value, exists := settingsMap[key]; exists {
|
||||
|
||||
@@ -26,6 +26,7 @@ func main() {
|
||||
repositories.MigratePostSnippets()
|
||||
repositories.MigratePostUserID()
|
||||
repositories.MigrateUserAvatar()
|
||||
repositories.MigrateUserProfileFields()
|
||||
|
||||
// 初始化ip2region (如果文件不存在,将降级为普通IP记录)
|
||||
// 函数会自动从环境变量或可执行文件目录查找 ip2region.xdb
|
||||
@@ -101,6 +102,10 @@ func main() {
|
||||
// 咨询相关路由
|
||||
api.POST("/inquiries", handlers.SubmitInquiry)
|
||||
api.GET("/email-suffixes", handlers.GetEmailSuffixes)
|
||||
|
||||
// 用户公开资料
|
||||
api.GET("/users/:id/profile", handlers.GetUserProfile)
|
||||
api.GET("/users/:id/posts", handlers.GetUserPosts)
|
||||
}
|
||||
|
||||
// 管理员API路由组
|
||||
@@ -113,6 +118,11 @@ func main() {
|
||||
authAdmin := admin.Group("/")
|
||||
authAdmin.Use(middleware.AuthMiddleware(), middleware.OperationLogMiddleware())
|
||||
{
|
||||
// 当前用户
|
||||
authAdmin.GET("/me", handlers.GetCurrentUser)
|
||||
authAdmin.PUT("/me", handlers.UpdateCurrentUser)
|
||||
authAdmin.PUT("/me/password", handlers.UpdateCurrentUserPassword)
|
||||
|
||||
// 用户管理
|
||||
authAdmin.GET("/users", middleware.PermissionMiddleware("users", "read"), handlers.AdminGetUsers)
|
||||
authAdmin.GET("/users/:id", middleware.PermissionMiddleware("users", "read"), handlers.AdminGetUser)
|
||||
@@ -198,6 +208,7 @@ func main() {
|
||||
|
||||
// 标签管理
|
||||
authAdmin.GET("/tags", middleware.PermissionMiddleware("tags", "read"), handlers.AdminGetTags)
|
||||
authAdmin.GET("/tags/:id", middleware.PermissionMiddleware("tags", "read"), handlers.GetTag)
|
||||
authAdmin.POST("/tags", middleware.PermissionMiddleware("tags", "create"), handlers.AdminCreateTag)
|
||||
authAdmin.PUT("/tags/:id", middleware.PermissionMiddleware("tags", "update"), handlers.AdminUpdateTag)
|
||||
authAdmin.DELETE("/tags/:id", middleware.PermissionMiddleware("tags", "delete"), handlers.AdminDeleteTag)
|
||||
|
||||
@@ -43,3 +43,14 @@ func (c *Category) BeforeUpdate(tx *gorm.DB) error {
|
||||
c.UpdatedAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CategoryResponse 分类 API 响应
|
||||
type CategoryResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description string `json:"description"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
}
|
||||
|
||||
@@ -45,6 +45,20 @@ func (c *Column) BeforeUpdate(tx *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ColumnResponse 专栏 API 响应
|
||||
type ColumnResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cover string `json:"cover"`
|
||||
IsActive int `json:"isActive"`
|
||||
SortOrder uint `json:"sortOrder"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
PostCount int64 `json:"postCount,omitempty"`
|
||||
LastUpdated string `json:"lastUpdated,omitempty"`
|
||||
}
|
||||
|
||||
// ColumnPost 专栏文章关联模型
|
||||
type ColumnPost struct {
|
||||
ColumnID uint `json:"columnId" gorm:"primaryKey;column:column_id"`
|
||||
|
||||
@@ -42,6 +42,15 @@ func (t *Tag) BeforeUpdate(tx *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TagResponse 标签 API 响应
|
||||
type TagResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// PostTag 文章标签关联模型
|
||||
type PostTag struct {
|
||||
PostID uint `json:"postId" gorm:"primaryKey;column:post_id"`
|
||||
|
||||
@@ -12,6 +12,10 @@ type User struct {
|
||||
Username string `json:"username" gorm:"column:username;uniqueIndex;not null"`
|
||||
Email string `json:"email" gorm:"column:email"`
|
||||
Avatar string `json:"avatar" gorm:"column:avatar"`
|
||||
Bio string `json:"bio" gorm:"column:bio;type:text"`
|
||||
Phone string `json:"phone" gorm:"column:phone"`
|
||||
Wechat string `json:"wechat" gorm:"column:wechat"`
|
||||
WechatQrcode string `json:"wechatQrcode" gorm:"column:wechat_qrcode"`
|
||||
Password string `json:"password,omitempty" gorm:"-"` // Virtual field for input
|
||||
PasswordHash string `json:"-" gorm:"column:password_hash"`
|
||||
RoleID uint `json:"roleId" gorm:"column:role_id"`
|
||||
@@ -50,15 +54,47 @@ func (u *User) BeforeUpdate(tx *gorm.DB) error {
|
||||
|
||||
// UserResponse 用户响应模型
|
||||
type UserResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
RoleID uint `json:"roleId"`
|
||||
Role string `json:"role"`
|
||||
IsActive int `json:"isActive"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Wechat string `json:"wechat,omitempty"`
|
||||
WechatQrcode string `json:"wechatQrcode,omitempty"`
|
||||
RoleID uint `json:"roleId"`
|
||||
Role string `json:"role"`
|
||||
IsActive int `json:"isActive"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// UserPublicProfile 公开用户资料(前台展示)
|
||||
type UserPublicProfile struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Wechat string `json:"wechat,omitempty"`
|
||||
WechatQrcode string `json:"wechatQrcode,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateProfileRequest 当前用户更新资料请求
|
||||
type UpdateProfileRequest struct {
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
Bio string `json:"bio"`
|
||||
Phone string `json:"phone"`
|
||||
Wechat string `json:"wechat"`
|
||||
WechatQrcode string `json:"wechatQrcode"`
|
||||
}
|
||||
|
||||
// UpdatePasswordRequest 修改密码请求
|
||||
type UpdatePasswordRequest struct {
|
||||
OldPassword string `json:"oldPassword" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required"`
|
||||
}
|
||||
|
||||
// LoginRequest 登录请求模型
|
||||
|
||||
@@ -78,3 +78,25 @@ func DeleteCategory(id uint) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildCategoryResponse 构建分类响应
|
||||
func BuildCategoryResponse(category *models.Category) *models.CategoryResponse {
|
||||
return &models.CategoryResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Slug: category.Slug,
|
||||
Description: category.Description,
|
||||
SortOrder: category.SortOrder,
|
||||
CreatedAt: formatTimestamp(category.CreatedAt),
|
||||
UpdatedAt: formatTimestamp(category.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildCategoriesResponse 构建分类列表响应
|
||||
func BuildCategoriesResponse(categories []models.Category) []models.CategoryResponse {
|
||||
responses := make([]models.CategoryResponse, 0, len(categories))
|
||||
for _, category := range categories {
|
||||
responses = append(responses, *BuildCategoryResponse(&category))
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
@@ -87,24 +87,46 @@ func GetColumnStats(columnID uint) (int64, int64, error) {
|
||||
}
|
||||
|
||||
// BuildColumnResponse 构建专栏响应(包含统计信息)
|
||||
func BuildColumnResponse(col *models.Column) map[string]interface{} {
|
||||
func BuildColumnResponse(col *models.Column) *models.ColumnResponse {
|
||||
postCount, lastUpdated, _ := GetColumnStats(col.ID)
|
||||
|
||||
return map[string]interface{}{
|
||||
"id": col.ID,
|
||||
"name": col.Name,
|
||||
"description": col.Description,
|
||||
"cover": col.Cover,
|
||||
"isActive": col.IsActive,
|
||||
"sortOrder": col.SortOrder,
|
||||
"createdAt": col.CreatedAt,
|
||||
"updatedAt": col.UpdatedAt,
|
||||
"deletedAt": col.DeletedAt,
|
||||
"postCount": postCount,
|
||||
"lastUpdated": lastUpdated,
|
||||
return &models.ColumnResponse{
|
||||
ID: col.ID,
|
||||
Name: col.Name,
|
||||
Description: col.Description,
|
||||
Cover: col.Cover,
|
||||
IsActive: col.IsActive,
|
||||
SortOrder: col.SortOrder,
|
||||
CreatedAt: formatTimestamp(col.CreatedAt),
|
||||
UpdatedAt: formatTimestamp(col.UpdatedAt),
|
||||
PostCount: postCount,
|
||||
LastUpdated: formatTimestamp(lastUpdated),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildColumnItemResponse 构建专栏列表项响应(不含统计)
|
||||
func BuildColumnItemResponse(col *models.Column) *models.ColumnResponse {
|
||||
return &models.ColumnResponse{
|
||||
ID: col.ID,
|
||||
Name: col.Name,
|
||||
Description: col.Description,
|
||||
Cover: col.Cover,
|
||||
IsActive: col.IsActive,
|
||||
SortOrder: col.SortOrder,
|
||||
CreatedAt: formatTimestamp(col.CreatedAt),
|
||||
UpdatedAt: formatTimestamp(col.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildColumnsResponse 构建专栏列表响应
|
||||
func BuildColumnsResponse(columns []models.Column) []models.ColumnResponse {
|
||||
responses := make([]models.ColumnResponse, 0, len(columns))
|
||||
for _, col := range columns {
|
||||
responses = append(responses, *BuildColumnItemResponse(&col))
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
// CreateColumn 创建专栏
|
||||
func CreateColumn(col *models.Column) error {
|
||||
err := config.DB.Create(col).Error
|
||||
|
||||
@@ -175,3 +175,23 @@ func MigrateUserAvatar() {
|
||||
log.Printf("Adding users.avatar...")
|
||||
execSQL("ALTER TABLE `users` ADD COLUMN `avatar` VARCHAR(500) NULL DEFAULT NULL COMMENT '头像URL' AFTER `email`")
|
||||
}
|
||||
|
||||
// MigrateUserProfileFields adds profile fields to users if missing.
|
||||
func MigrateUserProfileFields() {
|
||||
if !columnExists("users", "bio") {
|
||||
log.Printf("Adding users.bio...")
|
||||
execSQL("ALTER TABLE `users` ADD COLUMN `bio` TEXT NULL COMMENT '个人介绍' AFTER `avatar`")
|
||||
}
|
||||
if !columnExists("users", "phone") {
|
||||
log.Printf("Adding users.phone...")
|
||||
execSQL("ALTER TABLE `users` ADD COLUMN `phone` VARCHAR(20) NULL DEFAULT NULL COMMENT '手机号' AFTER `bio`")
|
||||
}
|
||||
if !columnExists("users", "wechat") {
|
||||
log.Printf("Adding users.wechat...")
|
||||
execSQL("ALTER TABLE `users` ADD COLUMN `wechat` VARCHAR(100) NULL DEFAULT NULL COMMENT '微信号' AFTER `phone`")
|
||||
}
|
||||
if !columnExists("users", "wechat_qrcode") {
|
||||
log.Printf("Adding users.wechat_qrcode...")
|
||||
execSQL("ALTER TABLE `users` ADD COLUMN `wechat_qrcode` VARCHAR(500) NULL DEFAULT NULL COMMENT '微信二维码图片URL' AFTER `wechat`")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,36 @@ func GetPosts(keyword string, categoryID uint, tagID uint, columnID uint, page i
|
||||
return posts, total, nil
|
||||
}
|
||||
|
||||
// GetPostsByUserID 获取用户已发布文章(用于公开资料页)
|
||||
func GetPostsByUserID(userID uint, sort string, limit int) ([]models.Post, error) {
|
||||
if limit <= 0 {
|
||||
limit = 5
|
||||
}
|
||||
if limit > 20 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
orderClause := "created_at DESC"
|
||||
if sort == "popular" {
|
||||
orderClause = "read_count DESC, created_at DESC"
|
||||
}
|
||||
|
||||
var posts []models.Post
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Where("user_id = ? AND is_published = ? AND deleted_at = ?", userID, 1, 0).
|
||||
Preload("Category").
|
||||
Preload("Column").
|
||||
Preload("Tags").
|
||||
Order(orderClause).
|
||||
Limit(limit).
|
||||
Find(&posts).Error
|
||||
if err != nil {
|
||||
log.Printf("Error querying posts by user ID: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
// GetPostByID 根据ID获取博客文章
|
||||
func GetPostByID(id uint) (*models.Post, error) {
|
||||
var post models.Post
|
||||
@@ -452,6 +482,8 @@ func buildPostResponse(post *models.Post, includeContent, includeSnippets bool,
|
||||
if post.Category != nil {
|
||||
catName = post.Category.Name
|
||||
catSlug = post.Category.Slug
|
||||
} else if post.CategoryID > 0 {
|
||||
catName = lookupCategoryName(post.CategoryID)
|
||||
}
|
||||
|
||||
colName := ""
|
||||
|
||||
@@ -160,11 +160,21 @@ func GetPostsByTagID(tagID uint) ([]models.Post, error) {
|
||||
}
|
||||
|
||||
// BuildTagsResponse 构建标签列表响应
|
||||
func BuildTagsResponse(tags []models.Tag) []models.Tag {
|
||||
return tags
|
||||
func BuildTagsResponse(tags []models.Tag) []models.TagResponse {
|
||||
responses := make([]models.TagResponse, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
responses = append(responses, *BuildTagResponse(&tag))
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
// BuildTagResponse 构建标签响应
|
||||
func BuildTagResponse(tag *models.Tag) *models.Tag {
|
||||
return tag
|
||||
func BuildTagResponse(tag *models.Tag) *models.TagResponse {
|
||||
return &models.TagResponse{
|
||||
ID: tag.ID,
|
||||
Name: tag.Name,
|
||||
Slug: tag.Slug,
|
||||
CreatedAt: formatTimestamp(tag.CreatedAt),
|
||||
UpdatedAt: formatTimestamp(tag.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,12 +134,16 @@ func UpdateUser(user *models.User) error {
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"role": user.Role,
|
||||
"is_active": user.IsActive,
|
||||
"updated_at": time.Now().Unix(),
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"bio": user.Bio,
|
||||
"phone": user.Phone,
|
||||
"wechat": user.Wechat,
|
||||
"wechat_qrcode": user.WechatQrcode,
|
||||
"role": user.Role,
|
||||
"is_active": user.IsActive,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}
|
||||
|
||||
if user.RoleID != 0 {
|
||||
@@ -205,18 +209,56 @@ func GetUserCount() (int, error) {
|
||||
// BuildUserResponse 构建用户响应
|
||||
func BuildUserResponse(user *models.User) *models.UserResponse {
|
||||
return &models.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Avatar: user.Avatar,
|
||||
RoleID: user.RoleID,
|
||||
Role: user.Role,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: time.Unix(user.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: time.Unix(user.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Avatar: user.Avatar,
|
||||
Bio: user.Bio,
|
||||
Phone: user.Phone,
|
||||
Wechat: user.Wechat,
|
||||
WechatQrcode: user.WechatQrcode,
|
||||
RoleID: user.RoleID,
|
||||
Role: user.Role,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: time.Unix(user.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: time.Unix(user.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildUserPublicProfile 构建公开用户资料
|
||||
func BuildUserPublicProfile(user *models.User) *models.UserPublicProfile {
|
||||
return &models.UserPublicProfile{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Avatar: user.Avatar,
|
||||
Bio: user.Bio,
|
||||
Phone: user.Phone,
|
||||
Wechat: user.Wechat,
|
||||
WechatQrcode: user.WechatQrcode,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCurrentUserProfile 更新当前用户资料(不含角色/状态)
|
||||
func UpdateCurrentUserProfile(id uint, req *models.UpdateProfileRequest) error {
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
Updates(map[string]interface{}{
|
||||
"email": req.Email,
|
||||
"avatar": req.Avatar,
|
||||
"bio": req.Bio,
|
||||
"phone": req.Phone,
|
||||
"wechat": req.Wechat,
|
||||
"wechat_qrcode": req.WechatQrcode,
|
||||
"updated_at": time.Now().Unix(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("Error updating user profile: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildUsersResponse 构建用户列表响应
|
||||
func BuildUsersResponse(users []models.User) []models.UserResponse {
|
||||
var responses []models.UserResponse
|
||||
|
||||
Reference in New Issue
Block a user