feat: PhotoWall 毕业照存储系统初始版本
后端: Go(Gin+GORM+MySQL+Redis) - 17张业务表(yz_pw_前缀), 自动迁移 - JWT认证(3小时) + 盐+MD5密码 + 图形验证码 - 班级CRUD/加入(8人姓名验证/邀请码)/审核/30天自动清理 - 系统配置(16项)/菜单管理(动态路由)/数据统计 - 文件MD5去重/数据隔离/账号封禁/敏感词DFA检测 前端: Vue3+Vite+Element Plus+Less+ECharts+FontAwesome - 动态路由(数据库菜单驱动) - 蓝白配色, H5响应式 - 登录/注册/忘记密码/个人中心 - 班级创建向导/详情/加入/列表 - 管理后台: 审核/配置/菜单/统计/用户/敏感词 数据库: MySQL 10.31.100.3:3306/photowall 管理员: hero920103 / 920103
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/database"
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"photowall/pkg/sensitive"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
svc *service.AdminService
|
||||
}
|
||||
|
||||
func NewAdminHandler(svc *service.AdminService) *AdminHandler {
|
||||
return &AdminHandler{svc: svc}
|
||||
}
|
||||
|
||||
// Stats GET /api/admin/stats
|
||||
func (h *AdminHandler) Stats(c *gin.Context) {
|
||||
stats, err := h.svc.Stats()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, stats)
|
||||
}
|
||||
|
||||
// DetailedStats GET /api/admin/stats/detailed 详细数据统计(仪表盘用)
|
||||
func (h *AdminHandler) DetailedStats(c *gin.Context) {
|
||||
stats, err := h.svc.DetailedStats()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, stats)
|
||||
}
|
||||
|
||||
// AuditQueue GET /api/admin/classes
|
||||
func (h *AdminHandler) AuditQueue(c *gin.Context) {
|
||||
var req service.AuditQueueReq
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := h.svc.ListAuditQueue(&req)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// Approve POST /api/admin/classes/:id/approve
|
||||
func (h *AdminHandler) Approve(c *gin.Context) {
|
||||
auditorID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
c.ShouldBindJSON(&body)
|
||||
if err := h.svc.Approve(uint(id), auditorID, body.Reason); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已通过审核"})
|
||||
}
|
||||
|
||||
// Reject POST /api/admin/classes/:id/reject
|
||||
func (h *AdminHandler) Reject(c *gin.Context) {
|
||||
auditorID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "请填写打回原因")
|
||||
return
|
||||
}
|
||||
if err := h.svc.Reject(uint(id), auditorID, body.Reason); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已打回,等待用户补齐"})
|
||||
}
|
||||
|
||||
// Delete DELETE /api/admin/classes/:id
|
||||
func (h *AdminHandler) Delete(c *gin.Context) {
|
||||
auditorID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
c.ShouldBindJSON(&body)
|
||||
if err := h.svc.ManualDelete(uint(id), auditorID, body.Reason); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已删除"})
|
||||
}
|
||||
|
||||
// AuditLogs GET /api/admin/classes/:id/logs
|
||||
func (h *AdminHandler) AuditLogs(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
logs, err := h.svc.AuditLogs(uint(id))
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, logs)
|
||||
}
|
||||
|
||||
// TriggerCleanup POST /api/admin/cleanup 手动触发过期清理(也可由定时任务自动执行)
|
||||
func (h *AdminHandler) TriggerCleanup(c *gin.Context) {
|
||||
count, err := h.svc.CleanupExpired()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"deleted": count, "message": "清理完成"})
|
||||
}
|
||||
|
||||
// ============ 账号封禁管理 ============
|
||||
|
||||
// BanUser POST /api/admin/users/:id/ban 封禁用户
|
||||
func (h *AdminHandler) BanUser(c *gin.Context) {
|
||||
adminID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Reason string `json:"reason" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "请填写封禁原因")
|
||||
return
|
||||
}
|
||||
req := &service.BanUserReq{UserID: uint(id), Reason: body.Reason}
|
||||
if err := h.svc.BanUser(req, adminID); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已封禁"})
|
||||
}
|
||||
|
||||
// UnbanUser POST /api/admin/users/:id/unban 解封用户
|
||||
func (h *AdminHandler) UnbanUser(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.svc.UnbanUser(uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已解封"})
|
||||
}
|
||||
|
||||
// BannedUsers GET /api/admin/users/banned 封禁用户列表
|
||||
func (h *AdminHandler) BannedUsers(c *gin.Context) {
|
||||
list, err := h.svc.ListBannedUsers()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// ============ 敏感词管理 ============
|
||||
|
||||
// SensitiveWords GET /api/admin/sensitive-words 敏感词列表
|
||||
func (h *AdminHandler) SensitiveWords(c *gin.Context) {
|
||||
list, err := h.svc.ListSensitiveWords()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// AddSensitiveWord POST /api/admin/sensitive-words 添加敏感词
|
||||
func (h *AdminHandler) AddSensitiveWord(c *gin.Context) {
|
||||
var body struct {
|
||||
Word string `json:"word" binding:"required"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "请填写敏感词")
|
||||
return
|
||||
}
|
||||
if body.Category == "" {
|
||||
body.Category = "other"
|
||||
}
|
||||
sw, err := h.svc.AddSensitiveWord(body.Word, body.Category)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 重新加载敏感词过滤器
|
||||
sensitive.Reload(database.DB)
|
||||
response.Created(c, sw)
|
||||
}
|
||||
|
||||
// DeleteSensitiveWord DELETE /api/admin/sensitive-words/:id 删除敏感词
|
||||
func (h *AdminHandler) DeleteSensitiveWord(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.svc.DeleteSensitiveWord(uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
sensitive.Reload(database.DB)
|
||||
response.OK(c, gin.H{"message": "已删除"})
|
||||
}
|
||||
|
||||
// ToggleSensitiveWord PUT /api/admin/sensitive-words/:id/toggle 启用/禁用
|
||||
func (h *AdminHandler) ToggleSensitiveWord(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
c.ShouldBindJSON(&body)
|
||||
if err := h.svc.ToggleSensitiveWord(uint(id), body.Enabled); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
sensitive.Reload(database.DB)
|
||||
response.OK(c, gin.H{"message": "已更新"})
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/captcha"
|
||||
"photowall/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
auth *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(auth *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{auth: auth}
|
||||
}
|
||||
|
||||
// Captcha GET /api/captcha 获取图形验证码
|
||||
func (h *AuthHandler) Captcha(c *gin.Context) {
|
||||
id, img := captcha.Generate()
|
||||
response.OK(c, gin.H{
|
||||
"captcha_id": id,
|
||||
"image": img,
|
||||
})
|
||||
}
|
||||
|
||||
// Register POST /api/auth/register
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req service.RegisterReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
user, err := h.auth.Register(&req)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, user)
|
||||
}
|
||||
|
||||
// Login POST /api/auth/login
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req service.LoginReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
resp, err := h.auth.Login(&req)
|
||||
if err != nil {
|
||||
response.Unauthorized(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, resp)
|
||||
}
|
||||
|
||||
// Me GET /api/auth/me
|
||||
func (h *AuthHandler) Me(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
username := middleware.CurrentUsername(c)
|
||||
role := middleware.CurrentRole(c)
|
||||
response.OK(c, gin.H{
|
||||
"user_id": userID,
|
||||
"username": username,
|
||||
"role": role,
|
||||
})
|
||||
}
|
||||
|
||||
// ForgotPassword POST /api/auth/forgot-password 忘记密码(生成重置码)
|
||||
func (h *AuthHandler) ForgotPassword(c *gin.Context) {
|
||||
var req service.ForgotPasswordReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
code, err := h.auth.ForgotPassword(&req)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 开发阶段返回重置码(生产环境应通过邮件发送,不返回)
|
||||
response.OK(c, gin.H{
|
||||
"message": "重置码已生成,有效期30分钟",
|
||||
"reset_code": code,
|
||||
"note": "开发阶段直接返回,生产环境将通过邮箱发送",
|
||||
})
|
||||
}
|
||||
|
||||
// ResetPassword POST /api/auth/reset-password 重置密码
|
||||
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
||||
var req service.ResetPasswordReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.auth.ResetPassword(&req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "密码重置成功,请使用新密码登录"})
|
||||
}
|
||||
|
||||
// SendSms POST /api/auth/send-sms 发送短信验证码
|
||||
func (h *AuthHandler) SendSms(c *gin.Context) {
|
||||
var req service.SendSmsReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
code, err := h.auth.SendSmsCode(&req)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{
|
||||
"message": "验证码已发送,有效期5分钟",
|
||||
"code": code, // 开发阶段返回,生产环境不返回
|
||||
"note": "短信参数未配置时为开发模式,直接返回验证码",
|
||||
})
|
||||
}
|
||||
|
||||
// WechatLogin POST /api/auth/wechat-login 微信扫码登录
|
||||
func (h *AuthHandler) WechatLogin(c *gin.Context) {
|
||||
var req service.WechatLoginReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
resp, err := h.auth.WechatLogin(&req)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, resp)
|
||||
}
|
||||
|
||||
// WechatQrcode GET /api/auth/wechat-qrcode 获取微信扫码登录二维码(预留)
|
||||
func (h *AuthHandler) WechatQrcode(c *gin.Context) {
|
||||
// TODO: 接入微信开放平台后生成真实扫码二维码
|
||||
response.OK(c, gin.H{
|
||||
"message": "微信扫码登录功能开发中,请配置微信开放平台参数后使用",
|
||||
"qrcode": "",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ClassHandler struct {
|
||||
svc *service.ClassService
|
||||
}
|
||||
|
||||
func NewClassHandler(svc *service.ClassService) *ClassHandler {
|
||||
return &ClassHandler{svc: svc}
|
||||
}
|
||||
|
||||
// Create POST /api/classes
|
||||
func (h *ClassHandler) Create(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
var req service.CreateClassReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
class, err := h.svc.Create(userID, &req)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, class)
|
||||
}
|
||||
|
||||
// Detail GET /api/classes/:id
|
||||
func (h *ClassHandler) Detail(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
userID := middleware.CurrentUserID(c)
|
||||
isSuperAdmin := middleware.CurrentRole(c) == "admin"
|
||||
detail, err := h.svc.GetDetail(uint(id), userID, isSuperAdmin)
|
||||
if err != nil {
|
||||
response.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, detail)
|
||||
}
|
||||
|
||||
// Search GET /api/classes/search
|
||||
func (h *ClassHandler) Search(c *gin.Context) {
|
||||
var req service.SearchClassReq
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := h.svc.Search(&req)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// JoinBySearch POST /api/classes/join/search
|
||||
func (h *ClassHandler) JoinBySearch(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
var req service.JoinBySearchReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.svc.JoinBySearch(userID, &req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "加入成功"})
|
||||
}
|
||||
|
||||
// JoinByInvitation POST /api/classes/join/invite
|
||||
func (h *ClassHandler) JoinByInvitation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
var req service.JoinByInvitationReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "邀请码不能为空")
|
||||
return
|
||||
}
|
||||
class, err := h.svc.JoinByInvitation(userID, &req)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "加入成功", "class_id": class.ID, "class_name": class.Name})
|
||||
}
|
||||
|
||||
// Mine GET /api/classes/mine
|
||||
func (h *ClassHandler) Mine(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
list, err := h.svc.ListMyClasses(userID)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// CreateInvitation POST /api/classes/:id/invitation
|
||||
func (h *ClassHandler) CreateInvitation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
MaxUses int `json:"max_uses"`
|
||||
ExpireDays int `json:"expire_days"`
|
||||
}
|
||||
c.ShouldBindJSON(&body)
|
||||
inv, err := h.svc.CreateInvitation(uint(id), userID, body.MaxUses, body.ExpireDays)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, inv)
|
||||
}
|
||||
|
||||
// ListInvitations GET /api/classes/:id/invitations
|
||||
func (h *ClassHandler) ListInvitations(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
list, err := h.svc.ListInvitations(uint(id), userID)
|
||||
if err != nil {
|
||||
response.Forbidden(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// DisableInvitation DELETE /api/classes/:id/invitations/:inv_id
|
||||
func (h *ClassHandler) DisableInvitation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
invID, _ := strconv.ParseUint(c.Param("inv_id"), 10, 64)
|
||||
if err := h.svc.DisableInvitation(uint(id), uint(invID), userID); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已禁用"})
|
||||
}
|
||||
|
||||
// TransferAdmin POST /api/classes/:id/transfer
|
||||
func (h *ClassHandler) TransferAdmin(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
ToUserID uint `json:"to_user_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "请指定目标用户")
|
||||
return
|
||||
}
|
||||
if err := h.svc.TransferAdmin(uint(id), userID, body.ToUserID); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "管理员已转移"})
|
||||
}
|
||||
|
||||
// RemoveMember DELETE /api/classes/:id/members/:user_id
|
||||
func (h *ClassHandler) RemoveMember(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
targetID, _ := strconv.ParseUint(c.Param("user_id"), 10, 64)
|
||||
if err := h.svc.RemoveMember(uint(id), userID, uint(targetID)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已移除"})
|
||||
}
|
||||
|
||||
// UploadPhoto POST /api/classes/:id/photos
|
||||
func (h *ClassHandler) UploadPhoto(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
PhotoURL string `json:"photo_url" binding:"required"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "请先上传图片获取URL")
|
||||
return
|
||||
}
|
||||
photo, err := h.svc.UploadPhoto(uint(id), userID, body.PhotoURL, body.Caption)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, photo)
|
||||
}
|
||||
|
||||
// DeletePhoto DELETE /api/classes/:id/photos/:photo_id
|
||||
func (h *ClassHandler) DeletePhoto(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
isSuperAdmin := middleware.CurrentRole(c) == "admin"
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
photoID, _ := strconv.ParseUint(c.Param("photo_id"), 10, 64)
|
||||
if err := h.svc.DeletePhoto(uint(id), uint(photoID), userID, isSuperAdmin); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已删除"})
|
||||
}
|
||||
|
||||
// Resubmit POST /api/classes/:id/resubmit
|
||||
func (h *ClassHandler) Resubmit(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var req service.ResubmitReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.svc.Resubmit(uint(id), userID, &req); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "已重新提交审核"})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ConfigHandler struct {
|
||||
cfg *service.ConfigService
|
||||
}
|
||||
|
||||
func NewConfigHandler(cfg *service.ConfigService) *ConfigHandler {
|
||||
return &ConfigHandler{cfg: cfg}
|
||||
}
|
||||
|
||||
// GetAll GET /api/admin/config 获取所有系统配置
|
||||
func (h *ConfigHandler) GetAll(c *gin.Context) {
|
||||
list, err := h.cfg.GetAll()
|
||||
if err != nil {
|
||||
response.Internal(c, "获取配置失败")
|
||||
return
|
||||
}
|
||||
// 按分类分组返回
|
||||
result := gin.H{
|
||||
"basic": []gin.H{},
|
||||
"geetest": []gin.H{},
|
||||
"sms": []gin.H{},
|
||||
"wechat": []gin.H{},
|
||||
"storage": []gin.H{},
|
||||
}
|
||||
for _, item := range list {
|
||||
result[item.Category] = append(result[item.Category].([]gin.H), gin.H{
|
||||
"key": item.Key,
|
||||
"value": item.Value,
|
||||
"label": item.Label,
|
||||
"description": item.Description,
|
||||
"placeholder": item.Placeholder,
|
||||
"doc_link": item.DocLink,
|
||||
})
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// Update PUT /api/admin/config 批量更新配置
|
||||
func (h *ConfigHandler) Update(c *gin.Context) {
|
||||
var req service.UpdateConfigReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.cfg.Update(&req); err != nil {
|
||||
response.Internal(c, "更新配置失败")
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "配置更新成功,部分配置需重启服务生效"})
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/model"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MenuHandler struct {
|
||||
menu *service.MenuService
|
||||
}
|
||||
|
||||
func NewMenuHandler(menu *service.MenuService) *MenuHandler {
|
||||
return &MenuHandler{menu: menu}
|
||||
}
|
||||
|
||||
// GetUserMenus GET /api/menus 获取当前用户的菜单树(动态路由用)
|
||||
func (h *MenuHandler) GetUserMenus(c *gin.Context) {
|
||||
isAdmin := middleware.CurrentRole(c) == "admin"
|
||||
tree, err := h.menu.GetMenuTree(isAdmin)
|
||||
if err != nil {
|
||||
response.Internal(c, "获取菜单失败")
|
||||
return
|
||||
}
|
||||
response.OK(c, tree)
|
||||
}
|
||||
|
||||
// GetAll GET /api/admin/menus 获取所有菜单(管理用)
|
||||
func (h *MenuHandler) GetAll(c *gin.Context) {
|
||||
list, err := h.menu.GetAll()
|
||||
if err != nil {
|
||||
response.Internal(c, "获取菜单失败")
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// Create POST /api/admin/menus 创建菜单
|
||||
func (h *MenuHandler) Create(c *gin.Context) {
|
||||
var m model.Menu
|
||||
if err := c.ShouldBindJSON(&m); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
if err := h.menu.Create(&m); err != nil {
|
||||
response.Internal(c, "创建菜单失败")
|
||||
return
|
||||
}
|
||||
response.Created(c, m)
|
||||
}
|
||||
|
||||
// Update PUT /api/admin/menus/:id 更新菜单
|
||||
func (h *MenuHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var m model.Menu
|
||||
if err := c.ShouldBindJSON(&m); err != nil {
|
||||
response.BadRequest(c, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
m.ID = uint(id)
|
||||
if err := h.menu.Update(&m); err != nil {
|
||||
response.Internal(c, "更新菜单失败")
|
||||
return
|
||||
}
|
||||
response.OK(c, m)
|
||||
}
|
||||
|
||||
// Delete DELETE /api/admin/menus/:id 删除菜单
|
||||
func (h *MenuHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if err := h.menu.Delete(uint(id)); err != nil {
|
||||
response.Internal(c, "删除菜单失败")
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type RegionHandler struct {
|
||||
svc *service.RegionService
|
||||
}
|
||||
|
||||
func NewRegionHandler(svc *service.RegionService) *RegionHandler {
|
||||
return &RegionHandler{svc: svc}
|
||||
}
|
||||
|
||||
// List GET /api/regions?parent_id=0
|
||||
func (h *RegionHandler) List(c *gin.Context) {
|
||||
parentID, _ := strconv.ParseUint(c.DefaultQuery("parent_id", "0"), 10, 64)
|
||||
list, err := h.svc.ListByParent(uint(parentID))
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// Countries GET /api/regions/countries
|
||||
func (h *RegionHandler) Countries(c *gin.Context) {
|
||||
list, err := h.svc.ListCountries()
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// Path GET /api/regions/:id/path
|
||||
func (h *RegionHandler) Path(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
path, err := h.svc.GetPath(uint(id))
|
||||
if err != nil {
|
||||
response.NotFound(c, "地区不存在")
|
||||
return
|
||||
}
|
||||
response.OK(c, path)
|
||||
}
|
||||
|
||||
// Search GET /api/regions/search?keyword=xxx
|
||||
func (h *RegionHandler) Search(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
if keyword == "" {
|
||||
response.BadRequest(c, "请输入搜索关键词")
|
||||
return
|
||||
}
|
||||
list, err := h.svc.Search(keyword, 20)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/model"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SchoolHandler struct {
|
||||
svc *service.SchoolService
|
||||
}
|
||||
|
||||
func NewSchoolHandler(svc *service.SchoolService) *SchoolHandler {
|
||||
return &SchoolHandler{svc: svc}
|
||||
}
|
||||
|
||||
// List GET /api/schools
|
||||
func (h *SchoolHandler) List(c *gin.Context) {
|
||||
var q service.SchoolQuery
|
||||
if err := c.ShouldBindQuery(&q); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
result, err := h.svc.List(&q)
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
// Create POST /api/schools
|
||||
func (h *SchoolHandler) Create(c *gin.Context) {
|
||||
var school model.School
|
||||
if err := c.ShouldBindJSON(&school); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.svc.Create(&school); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, school)
|
||||
}
|
||||
|
||||
// Get GET /api/schools/:id
|
||||
func (h *SchoolHandler) Get(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
school, err := h.svc.GetByID(uint(id))
|
||||
if err != nil {
|
||||
response.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 附带学院列表
|
||||
colleges, _ := h.svc.ListColleges(uint(id))
|
||||
response.OK(c, gin.H{"school": school, "colleges": colleges})
|
||||
}
|
||||
|
||||
// Update PUT /api/schools/:id
|
||||
func (h *SchoolHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
// 只允许更新部分字段
|
||||
updates := map[string]interface{}{}
|
||||
if v, ok := body["name"]; ok {
|
||||
updates["name"] = v
|
||||
}
|
||||
if v, ok := body["name_en"]; ok {
|
||||
updates["name_en"] = v
|
||||
}
|
||||
if v, ok := body["school_type"]; ok {
|
||||
updates["school_type"] = v
|
||||
}
|
||||
if v, ok := body["address"]; ok {
|
||||
updates["address"] = v
|
||||
}
|
||||
if err := h.svc.Update(uint(id), updates); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// Delete DELETE /api/schools/:id
|
||||
func (h *SchoolHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.svc.Delete(uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
// ---- 学院 ----
|
||||
|
||||
// ListColleges GET /api/schools/:id/colleges
|
||||
func (h *SchoolHandler) ListColleges(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
list, err := h.svc.ListColleges(uint(id))
|
||||
if err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, list)
|
||||
}
|
||||
|
||||
// CreateCollege POST /api/schools/:id/colleges
|
||||
func (h *SchoolHandler) CreateCollege(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "学院名称不能为空")
|
||||
return
|
||||
}
|
||||
college, err := h.svc.CreateCollege(uint(id), body.Name)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, college)
|
||||
}
|
||||
|
||||
// UpdateCollege PUT /api/colleges/:id
|
||||
func (h *SchoolHandler) UpdateCollege(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "学院名称不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.svc.UpdateCollege(uint(id), body.Name); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// DeleteCollege DELETE /api/colleges/:id
|
||||
func (h *SchoolHandler) DeleteCollege(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.svc.DeleteCollege(uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"photowall/internal/config"
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/model"
|
||||
"photowall/pkg/response"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UploadHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUploadHandler(db *gorm.DB) *UploadHandler {
|
||||
return &UploadHandler{db: db}
|
||||
}
|
||||
|
||||
// Upload POST /api/upload
|
||||
// 通用文件上传,MD5去重,返回可访问的 URL。type 参数:class(毕业照)/photo(个人照片)/avatar
|
||||
func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
uploadType := c.DefaultPostForm("type", "photo")
|
||||
|
||||
allowedDirs := map[string]string{
|
||||
"class": "classes",
|
||||
"photo": "photos",
|
||||
"avatar": "avatars",
|
||||
}
|
||||
subDir, ok := allowedDirs[uploadType]
|
||||
if !ok {
|
||||
subDir = "photos"
|
||||
}
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
response.BadRequest(c, "请选择要上传的文件")
|
||||
return
|
||||
}
|
||||
|
||||
// 限制大小 10MB
|
||||
if file.Size > 10*1024*1024 {
|
||||
response.BadRequest(c, "文件大小不能超过 10MB")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验图片格式
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true}
|
||||
if !allowedExts[ext] {
|
||||
response.BadRequest(c, "仅支持 JPG/PNG/GIF/WEBP 格式")
|
||||
return
|
||||
}
|
||||
|
||||
// 打开文件计算 MD5
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
response.Internal(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
hash := md5.New()
|
||||
if _, err := io.Copy(hash, src); err != nil {
|
||||
response.Internal(c, "文件MD5计算失败")
|
||||
return
|
||||
}
|
||||
fileMd5 := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
// MD5 去重:已存在则返回已有路径
|
||||
var existing model.UploadFile
|
||||
if err := h.db.Where("md5 = ?", fileMd5).First(&existing).Error; err == nil {
|
||||
// 引用次数+1
|
||||
h.db.Model(&existing).Update("ref_count", gorm.Expr("ref_count + 1"))
|
||||
response.OK(c, gin.H{
|
||||
"url": existing.FileUrl,
|
||||
"filename": existing.FileName,
|
||||
"size": existing.FileSize,
|
||||
"md5": fileMd5,
|
||||
"duplicate": true,
|
||||
"message": "文件已存在,使用已有路径",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 新文件:保存到磁盘
|
||||
src.Seek(0, io.SeekStart) // 重置文件指针
|
||||
now := time.Now().Format("20060102")
|
||||
filename := fmt.Sprintf("%s_%d_%s%s", now, userID, uuid.New().String()[:8], ext)
|
||||
relPath := filepath.Join(subDir, filename)
|
||||
fullPath := filepath.Join(config.C.UploadDir, relPath)
|
||||
|
||||
if err := c.SaveUploadedFile(file, fullPath); err != nil {
|
||||
response.Internal(c, "文件保存失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 记录到文件表
|
||||
url := "/uploads/" + strings.ReplaceAll(relPath, "\\", "/")
|
||||
record := model.UploadFile{
|
||||
Md5: fileMd5,
|
||||
FilePath: relPath,
|
||||
FileUrl: url,
|
||||
FileName: filename,
|
||||
FileSize: file.Size,
|
||||
FileType: uploadType,
|
||||
UploaderID: userID,
|
||||
RefCount: 1,
|
||||
}
|
||||
h.db.Create(&record)
|
||||
|
||||
response.OK(c, gin.H{
|
||||
"url": url,
|
||||
"filename": filename,
|
||||
"size": file.Size,
|
||||
"md5": fileMd5,
|
||||
"duplicate": false,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"photowall/internal/middleware"
|
||||
"photowall/internal/model"
|
||||
"photowall/internal/service"
|
||||
"photowall/pkg/response"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
svc *service.UserService
|
||||
}
|
||||
|
||||
func NewUserHandler(svc *service.UserService) *UserHandler {
|
||||
return &UserHandler{svc: svc}
|
||||
}
|
||||
|
||||
// Profile GET /api/user/profile
|
||||
func (h *UserHandler) Profile(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
profile, err := h.svc.GetProfile(userID)
|
||||
if err != nil {
|
||||
response.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, profile)
|
||||
}
|
||||
|
||||
// UpdateProfile PUT /api/user/profile
|
||||
func (h *UserHandler) UpdateProfile(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
var req service.UpdateProfileReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.svc.UpdateProfile(userID, &req); err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// AddContact POST /api/user/contacts
|
||||
func (h *UserHandler) AddContact(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
var body struct {
|
||||
Type model.ContactType `json:"type" binding:"required"`
|
||||
Value string `json:"value" binding:"required"`
|
||||
IsPrimary bool `json:"is_primary"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
contact, err := h.svc.AddContact(userID, body.Type, body.Value, body.IsPrimary)
|
||||
if err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, contact)
|
||||
}
|
||||
|
||||
// UpdateContact PUT /api/user/contacts/:id
|
||||
func (h *UserHandler) UpdateContact(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var body struct {
|
||||
Value string `json:"value"`
|
||||
IsPrimary *bool `json:"is_primary"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.svc.UpdateContact(userID, uint(id), body.Value, body.IsPrimary); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// DeleteContact DELETE /api/user/contacts/:id
|
||||
func (h *UserHandler) DeleteContact(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.svc.DeleteContact(userID, uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
// AddEducation POST /api/user/education
|
||||
func (h *UserHandler) AddEducation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
var e model.EducationHistory
|
||||
if err := c.ShouldBindJSON(&e); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if e.SchoolName == "" {
|
||||
response.BadRequest(c, "学校名称不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.svc.AddEducation(userID, &e); err != nil {
|
||||
response.Internal(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.Created(c, e)
|
||||
}
|
||||
|
||||
// UpdateEducation PUT /api/user/education/:id
|
||||
func (h *UserHandler) UpdateEducation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
var e model.EducationHistory
|
||||
if err := c.ShouldBindJSON(&e); err != nil {
|
||||
response.BadRequest(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.svc.UpdateEducation(userID, uint(id), &e); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "更新成功"})
|
||||
}
|
||||
|
||||
// DeleteEducation DELETE /api/user/education/:id
|
||||
func (h *UserHandler) DeleteEducation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := h.svc.DeleteEducation(userID, uint(id)); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"message": "删除成功"})
|
||||
}
|
||||
|
||||
// HasEducation GET /api/user/has-education (创建班级前置检查)
|
||||
func (h *UserHandler) HasEducation(c *gin.Context) {
|
||||
userID := middleware.CurrentUserID(c)
|
||||
response.OK(c, gin.H{"has_education": h.svc.HasEducation(userID)})
|
||||
}
|
||||
|
||||
// PublicProfile GET /api/user/:id 公开信息(班级成员查看)
|
||||
func (h *UserHandler) PublicProfile(c *gin.Context) {
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
user, err := h.svc.GetUserByID(uint(id))
|
||||
if err != nil {
|
||||
response.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
contacts := h.svc.GetUserContacts(uint(id))
|
||||
response.OK(c, gin.H{"user": user, "contacts": contacts})
|
||||
}
|
||||
Reference in New Issue
Block a user