feat: 实现后端业务逻辑和Inertia.js管理后台

- 实现数据库连接和自动迁移
- 实现用户服务(登录、资料、JWT认证)
- 实现许愿服务(创建、列表、机器人许愿)
- 实现订单服务(创建、支付、状态管理)
- 实现JWT认证中间件
- 实现管理后台API(仪表盘、用户、订单、许愿、设置)
- 创建Inertia.js前端页面(Vue3 + Tailwind CSS)
- 修复Go模块依赖问题
This commit is contained in:
gouki
2026-08-06 13:11:59 +00:00
parent 11bfc15141
commit 48255faa5b
19 changed files with 1627 additions and 85 deletions
+7 -2
View File
@@ -6,10 +6,11 @@ import (
"github.com/gin-gonic/gin"
)
// AdminIndex 管理后台首页
func AdminIndex(c *gin.Context) {
// AdminDashboard 管理后台首页
func AdminDashboard(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "管理后台",
"page": "dashboard",
})
}
@@ -17,6 +18,7 @@ func AdminIndex(c *gin.Context) {
func AdminUsers(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "用户管理",
"page": "users",
})
}
@@ -24,6 +26,7 @@ func AdminUsers(c *gin.Context) {
func AdminOrders(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "订单管理",
"page": "orders",
})
}
@@ -31,6 +34,7 @@ func AdminOrders(c *gin.Context) {
func AdminWishes(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "许愿管理",
"page": "wishes",
})
}
@@ -38,5 +42,6 @@ func AdminWishes(c *gin.Context) {
func AdminSettings(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "系统设置",
"page": "settings",
})
}
+51 -8
View File
@@ -2,18 +2,43 @@ package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/gouki/lunar-server/internal/service"
)
// GetOrderList 获取订单列表
func GetOrderList(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
orderService := service.NewOrderService()
orders, total, err := orderService.GetOrderList(userID.(uint), page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "获取订单列表失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"list": []interface{}{},
"total": 0,
"list": orders,
"total": total,
"page": page,
},
})
}
@@ -21,23 +46,41 @@ func GetOrderList(c *gin.Context) {
// GetOrderDetail 获取订单详情
func GetOrderDetail(c *gin.Context) {
id := c.Param("id")
orderID, _ := strconv.Atoi(id)
orderService := service.NewOrderService()
order, err := orderService.GetOrderByID(uint(orderID))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"msg": "订单不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"id": id,
},
"data": order,
})
}
// CancelOrder 取消订单
func CancelOrder(c *gin.Context) {
id := c.Param("id")
orderID, _ := strconv.Atoi(id)
orderService := service.NewOrderService()
if err := orderService.CancelOrder(uint(orderID)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "取消订单失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"id": id,
},
})
}
+113 -4
View File
@@ -4,21 +4,117 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gouki/lunar-server/internal/model"
"github.com/gouki/lunar-server/internal/service"
)
// CreateOrder 创建订单
func CreateOrder(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
var req struct {
Type string `json:"type" binding:"required"` // wish:许愿 vip:会员
ProductID uint `json:"productId" binding:"required"` // 商品ID
ProductName string `json:"productName" binding:"required"` // 商品名称
Amount int `json:"amount" binding:"required"` // 金额(分)
Content string `json:"content"` // 许愿内容(许愿类型需要)
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "参数错误",
})
return
}
orderService := service.NewOrderService()
order := &model.Order{
UserID: userID.(uint),
Type: req.Type,
ProductID: req.ProductID,
ProductName: req.ProductName,
Amount: req.Amount,
}
if err := orderService.CreateOrder(order); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "创建订单失败",
})
return
}
// 如果是许愿类型,创建许愿记录
if req.Type == "wish" && req.Content != "" {
wishService := service.NewWishService()
wish := &model.Wish{
UserID: userID.(uint),
TreeID: 1, // 默认许愿树
Content: req.Content,
Type: "paid",
Status: 1,
}
wishService.CreateWish(wish)
}
// 创建微信支付订单
payParams, err := orderService.CreateWechatPayOrder(order, "")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "创建支付订单失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"orderId": "example-order-id",
"orderId": order.ID,
"orderNo": order.OrderNo,
"payParams": payParams,
},
})
}
// PayNotify 支付回调
func PayNotify(c *gin.Context) {
// TODO: 验证微信支付回调签名
var req struct {
OrderNo string `json:"orderNo"`
TransactionID string `json:"transactionId"`
Status string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": "FAIL",
"msg": "参数错误",
})
return
}
if req.Status == "SUCCESS" {
orderService := service.NewOrderService()
if err := orderService.HandlePayNotify(req.OrderNo, req.TransactionID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": "FAIL",
"msg": "处理失败",
})
return
}
}
c.JSON(http.StatusOK, gin.H{
"code": "SUCCESS",
"msg": "OK",
@@ -27,13 +123,26 @@ func PayNotify(c *gin.Context) {
// GetPayStatus 获取支付状态
func GetPayStatus(c *gin.Context) {
orderId := c.Param("orderId")
orderID := c.Param("orderId")
orderService := service.NewOrderService()
order, err := orderService.GetOrderByOrderNo(orderID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"msg": "订单不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"orderId": orderId,
"status": "pending",
"orderId": order.ID,
"orderNo": order.OrderNo,
"status": order.Status,
"payTime": order.PayTime,
},
})
}
+193 -5
View File
@@ -4,15 +4,72 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gouki/lunar-server/internal/config"
"github.com/gouki/lunar-server/internal/model"
"github.com/gouki/lunar-server/internal/service"
)
// UserLogin 用户登录
func UserLogin(c *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "参数错误",
})
return
}
// TODO: 调用微信接口获取 openid
// 这里模拟返回
openID := "mock_openid_" + req.Code
userService := service.NewUserService()
user, err := userService.GetUserByOpenID(openID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "服务器错误",
})
return
}
// 如果用户不存在,创建新用户
if user == nil {
user = &model.User{
OpenID: openID,
Nickname: "微信用户",
Status: 1,
}
if err := userService.CreateUser(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "创建用户失败",
})
return
}
}
// 生成 token
cfg := config.Load()
token, err := userService.GenerateToken(user.ID, cfg.JWT.Secret)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "生成token失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"token": "example-token",
"token": token,
"user": user,
},
})
}
@@ -27,32 +84,163 @@ func UserLogout(c *gin.Context) {
// GetUserProfile 获取用户资料
func GetUserProfile(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
userService := service.NewUserService()
user, err := userService.GetUserByID(userID.(uint))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "获取用户信息失败",
})
return
}
profile, _ := userService.GetUserProfile(user.ID)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"id": 1,
"nickname": "用户昵称",
"avatar": "",
"user": user,
"profile": profile,
},
})
}
// UpdateUserProfile 更新用户资料
func UpdateUserProfile(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
var req struct {
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Gender int `json:"gender"`
RealName string `json:"realName"`
Birthday string `json:"birthday"`
BirthTime string `json:"birthTime"`
Zodiac string `json:"zodiac"`
Constellation string `json:"constellation"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "参数错误",
})
return
}
userService := service.NewUserService()
// 更新用户基本信息
user, err := userService.GetUserByID(userID.(uint))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "获取用户信息失败",
})
return
}
if req.Nickname != "" {
user.Nickname = req.Nickname
}
if req.Avatar != "" {
user.Avatar = req.Avatar
}
if req.Gender > 0 {
user.Gender = req.Gender
}
if err := userService.UpdateUser(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "更新用户信息失败",
})
return
}
// 更新用户资料
profile, _ := userService.GetUserProfile(user.ID)
if profile == nil {
profile = &model.UserProfile{
UserID: user.ID,
}
}
if req.RealName != "" {
profile.RealName = req.RealName
}
if req.Birthday != "" {
profile.Birthday = req.Birthday
}
if req.BirthTime != "" {
profile.BirthTime = req.BirthTime
}
if req.Zodiac != "" {
profile.Zodiac = req.Zodiac
}
if req.Constellation != "" {
profile.Constellation = req.Constellation
}
if err := userService.UpdateUserProfile(profile); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "更新用户资料失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"user": user,
"profile": profile,
},
})
}
// WechatAuth 微信授权
func WechatAuth(c *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "参数错误",
})
return
}
// TODO: 调用微信接口获取 openid 和 session_key
// 这里模拟返回
openID := "mock_openid_" + req.Code
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"openid": "example-openid",
"openid": openID,
"sessionKey": "mock_session_key",
},
})
}
+167 -11
View File
@@ -2,56 +2,212 @@ package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/gouki/lunar-server/internal/model"
"github.com/gouki/lunar-server/internal/service"
)
// GetWishTree 获取许愿树
func GetWishTree(c *gin.Context) {
treeID, _ := strconv.Atoi(c.DefaultQuery("treeId", "1"))
wishService := service.NewWishService()
tree, wishes, err := wishService.GetWishTreeWithWishes(uint(treeID))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "获取许愿树失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"tree": gin.H{
"id": 1,
"name": "许愿树",
"wishes": []interface{}{},
},
"tree": tree,
"wishes": wishes,
},
})
}
// CreateWish 创建许愿
func CreateWish(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
var req struct {
TreeID uint `json:"treeId" binding:"required"`
Content string `json:"content" binding:"required"`
Type string `json:"type" binding:"required"` // free:免费 paid:付费
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "参数错误",
})
return
}
// 检查内容长度
maxLength := 20
if req.Type == "paid" {
maxLength = 100
}
if len(req.Content) > maxLength {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "内容长度超过限制",
})
return
}
wishService := service.NewWishService()
wish := &model.Wish{
UserID: userID.(uint),
TreeID: req.TreeID,
Content: req.Content,
Type: req.Type,
Status: 1,
}
if err := wishService.CreateWish(wish); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"id": 1,
},
"data": wish,
})
}
// GetWishList 获取许愿列表
func GetWishList(c *gin.Context) {
treeID, _ := strconv.Atoi(c.DefaultQuery("treeId", "1"))
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
wishService := service.NewWishService()
wishes, total, err := wishService.GetWishList(uint(treeID), page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "获取许愿列表失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"list": []interface{}{},
"total": 0,
"list": wishes,
"total": total,
"page": page,
},
})
}
// GetWishDetail 获取许愿详情
func GetWishDetail(c *gin.Context) {
id := c.Param("id")
wishID, _ := strconv.Atoi(id)
wishService := service.NewWishService()
wish, err := wishService.GetWishByID(uint(wishID))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"msg": "许愿不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": wish,
})
}
// DeleteWish 删除许愿
func DeleteWish(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
id := c.Param("id")
wishID, _ := strconv.Atoi(id)
// 检查是否是本人的许愿
wishService := service.NewWishService()
wish, err := wishService.GetWishByID(uint(wishID))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"msg": "许愿不存在",
})
return
}
if wish.UserID != userID.(uint) {
c.JSON(http.StatusForbidden, gin.H{
"code": 403,
"msg": "无权删除",
})
return
}
if err := wishService.DeleteWish(uint(wishID)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "删除失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
})
}
// GetWishProducts 获取许愿商品列表
func GetWishProducts(c *gin.Context) {
wishService := service.NewWishService()
products, err := wishService.GetWishProducts()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "获取商品列表失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
"data": gin.H{
"id": id,
"list": products,
},
})
}