feat: 实现后端业务逻辑和Inertia.js管理后台
- 实现数据库连接和自动迁移 - 实现用户服务(登录、资料、JWT认证) - 实现许愿服务(创建、列表、机器人许愿) - 实现订单服务(创建、支付、状态管理) - 实现JWT认证中间件 - 实现管理后台API(仪表盘、用户、订单、许愿、设置) - 创建Inertia.js前端页面(Vue3 + Tailwind CSS) - 修复Go模块依赖问题
This commit is contained in:
+25
-19
@@ -2,25 +2,28 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/gouki/lunar-server/internal/config"
|
"github.com/gouki/lunar-server/internal/config"
|
||||||
"github.com/gouki/lunar-server/internal/handler"
|
"github.com/gouki/lunar-server/internal/handler"
|
||||||
"github.com/gouki/lunar-server/internal/middleware"
|
"github.com/gouki/lunar-server/internal/middleware"
|
||||||
|
"github.com/gouki/lunar-server/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 加载环境变量
|
// 加载配置
|
||||||
if err := godotenv.Load("../.env.local"); err != nil {
|
|
||||||
log.Println("No .env.local file found, using system environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化配置
|
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
|
|
||||||
// 设置 Gin 模式
|
// 初始化数据库
|
||||||
|
if err := config.InitDB(cfg); err != nil {
|
||||||
|
log.Fatalf("Failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动机器人许愿定时任务
|
||||||
|
wishService := service.NewWishService()
|
||||||
|
wishService.StartRobotWishJob()
|
||||||
|
|
||||||
|
// 设置运行模式
|
||||||
if cfg.Server.Env == "production" {
|
if cfg.Server.Env == "production" {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
@@ -32,9 +35,9 @@ func main() {
|
|||||||
r.Use(middleware.CORS())
|
r.Use(middleware.CORS())
|
||||||
r.Use(middleware.Logger())
|
r.Use(middleware.Logger())
|
||||||
|
|
||||||
// 静态文件(Inertia.js 前端构建产物)
|
// 静态文件
|
||||||
r.Static("/build", "./web/public/build")
|
r.Static("/static", "./web/static")
|
||||||
r.LoadHTMLGlob("web/*.html")
|
r.StaticFile("/", "./web/index.html")
|
||||||
|
|
||||||
// API 路由
|
// API 路由
|
||||||
api := r.Group("/api")
|
api := r.Group("/api")
|
||||||
@@ -54,31 +57,34 @@ func main() {
|
|||||||
{
|
{
|
||||||
pay.POST("/create", middleware.Auth(), handler.CreateOrder)
|
pay.POST("/create", middleware.Auth(), handler.CreateOrder)
|
||||||
pay.POST("/notify", handler.PayNotify)
|
pay.POST("/notify", handler.PayNotify)
|
||||||
pay.GET("/status/:orderId", middleware.Auth(), handler.GetPayStatus)
|
pay.GET("/status/:orderId", handler.GetPayStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订单管理
|
// 订单相关
|
||||||
order := api.Group("/order")
|
order := api.Group("/order")
|
||||||
{
|
{
|
||||||
order.GET("/list", middleware.Auth(), handler.GetOrderList)
|
order.GET("/list", middleware.Auth(), handler.GetOrderList)
|
||||||
order.GET("/detail/:id", middleware.Auth(), handler.GetOrderDetail)
|
order.GET("/:id", middleware.Auth(), handler.GetOrderDetail)
|
||||||
order.POST("/cancel/:id", middleware.Auth(), handler.CancelOrder)
|
order.POST("/cancel/:id", middleware.Auth(), handler.CancelOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 许愿树
|
// 许愿相关
|
||||||
wish := api.Group("/wish")
|
wish := api.Group("/wish")
|
||||||
{
|
{
|
||||||
wish.GET("/tree", handler.GetWishTree)
|
wish.GET("/tree", handler.GetWishTree)
|
||||||
wish.POST("/create", middleware.Auth(), handler.CreateWish)
|
wish.POST("/create", middleware.Auth(), handler.CreateWish)
|
||||||
wish.GET("/list", handler.GetWishList)
|
wish.GET("/list", handler.GetWishList)
|
||||||
|
wish.GET("/:id", handler.GetWishDetail)
|
||||||
wish.DELETE("/:id", middleware.Auth(), handler.DeleteWish)
|
wish.DELETE("/:id", middleware.Auth(), handler.DeleteWish)
|
||||||
|
wish.GET("/products", handler.GetWishProducts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 管理后台(Inertia.js)
|
// 管理后台路由(Inertia.js)
|
||||||
admin := r.Group("/admin")
|
admin := r.Group("/admin")
|
||||||
|
admin.Use(middleware.AdminAuth())
|
||||||
{
|
{
|
||||||
admin.GET("/", handler.AdminIndex)
|
admin.GET("/", handler.AdminDashboard)
|
||||||
admin.GET("/users", handler.AdminUsers)
|
admin.GET("/users", handler.AdminUsers)
|
||||||
admin.GET("/orders", handler.AdminOrders)
|
admin.GET("/orders", handler.AdminOrders)
|
||||||
admin.GET("/wishes", handler.AdminWishes)
|
admin.GET("/wishes", handler.AdminWishes)
|
||||||
@@ -89,6 +95,6 @@ func main() {
|
|||||||
addr := ":" + cfg.Server.Port
|
addr := ":" + cfg.Server.Port
|
||||||
log.Printf("Server starting on %s", addr)
|
log.Printf("Server starting on %s", addr)
|
||||||
if err := r.Run(addr); err != nil {
|
if err := r.Run(addr); err != nil {
|
||||||
log.Fatal("Failed to start server:", err)
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-8
@@ -4,14 +4,7 @@ go 1.22
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-inertia/inertia-go v1.0.0
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/redis/go-redis/v9 v9.4.0
|
|
||||||
golang.org/x/crypto v0.17.0
|
|
||||||
gorm.io/gorm v1.25.5
|
|
||||||
gorm.io/driver/mysql v1.5.2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -35,7 +28,18 @@ require (
|
|||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.20.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
|
gorm.io/driver/mysql v1.6.0 // indirect
|
||||||
|
gorm.io/gorm v1.31.2 // indirect
|
||||||
|
)
|
||||||
|
|||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
|
||||||
|
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"github.com/gouki/lunar-server/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *gorm.DB
|
||||||
|
|
||||||
|
// InitDB 初始化数据库连接
|
||||||
|
func InitDB(cfg *Config) error {
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
|
cfg.Database.User,
|
||||||
|
cfg.Database.Password,
|
||||||
|
cfg.Database.Host,
|
||||||
|
cfg.Database.Port,
|
||||||
|
cfg.Database.Name,
|
||||||
|
)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动迁移
|
||||||
|
if err := autoMigrate(); err != nil {
|
||||||
|
return fmt.Errorf("failed to migrate database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Database connected and migrated successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// autoMigrate 自动迁移数据库表
|
||||||
|
func autoMigrate() error {
|
||||||
|
return DB.AutoMigrate(
|
||||||
|
&model.User{},
|
||||||
|
&model.UserProfile{},
|
||||||
|
&model.Order{},
|
||||||
|
&model.OrderItem{},
|
||||||
|
&model.WishTree{},
|
||||||
|
&model.Wish{},
|
||||||
|
&model.WishProduct{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDB 获取数据库连接
|
||||||
|
func GetDB() *gorm.DB {
|
||||||
|
return DB
|
||||||
|
}
|
||||||
@@ -6,10 +6,11 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminIndex 管理后台首页
|
// AdminDashboard 管理后台首页
|
||||||
func AdminIndex(c *gin.Context) {
|
func AdminDashboard(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
"title": "管理后台",
|
"title": "管理后台",
|
||||||
|
"page": "dashboard",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ func AdminIndex(c *gin.Context) {
|
|||||||
func AdminUsers(c *gin.Context) {
|
func AdminUsers(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
"title": "用户管理",
|
"title": "用户管理",
|
||||||
|
"page": "users",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ func AdminUsers(c *gin.Context) {
|
|||||||
func AdminOrders(c *gin.Context) {
|
func AdminOrders(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
"title": "订单管理",
|
"title": "订单管理",
|
||||||
|
"page": "orders",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +34,7 @@ func AdminOrders(c *gin.Context) {
|
|||||||
func AdminWishes(c *gin.Context) {
|
func AdminWishes(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
"title": "许愿管理",
|
"title": "许愿管理",
|
||||||
|
"page": "wishes",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,5 +42,6 @@ func AdminWishes(c *gin.Context) {
|
|||||||
func AdminSettings(c *gin.Context) {
|
func AdminSettings(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
||||||
"title": "系统设置",
|
"title": "系统设置",
|
||||||
|
"page": "settings",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,43 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gouki/lunar-server/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetOrderList 获取订单列表
|
// GetOrderList 获取订单列表
|
||||||
func GetOrderList(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"list": []interface{}{},
|
"list": orders,
|
||||||
"total": 0,
|
"total": total,
|
||||||
|
"page": page,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -21,23 +46,41 @@ func GetOrderList(c *gin.Context) {
|
|||||||
// GetOrderDetail 获取订单详情
|
// GetOrderDetail 获取订单详情
|
||||||
func GetOrderDetail(c *gin.Context) {
|
func GetOrderDetail(c *gin.Context) {
|
||||||
id := c.Param("id")
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": order,
|
||||||
"id": id,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelOrder 取消订单
|
// CancelOrder 取消订单
|
||||||
func CancelOrder(c *gin.Context) {
|
func CancelOrder(c *gin.Context) {
|
||||||
id := c.Param("id")
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
|
||||||
"id": id,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,117 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gouki/lunar-server/internal/model"
|
||||||
|
"github.com/gouki/lunar-server/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateOrder 创建订单
|
// CreateOrder 创建订单
|
||||||
func CreateOrder(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"orderId": "example-order-id",
|
"orderId": order.ID,
|
||||||
|
"orderNo": order.OrderNo,
|
||||||
|
"payParams": payParams,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayNotify 支付回调
|
// PayNotify 支付回调
|
||||||
func PayNotify(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": "SUCCESS",
|
"code": "SUCCESS",
|
||||||
"msg": "OK",
|
"msg": "OK",
|
||||||
@@ -27,13 +123,26 @@ func PayNotify(c *gin.Context) {
|
|||||||
|
|
||||||
// GetPayStatus 获取支付状态
|
// GetPayStatus 获取支付状态
|
||||||
func GetPayStatus(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"orderId": orderId,
|
"orderId": order.ID,
|
||||||
"status": "pending",
|
"orderNo": order.OrderNo,
|
||||||
|
"status": order.Status,
|
||||||
|
"payTime": order.PayTime,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,72 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"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 用户登录
|
// UserLogin 用户登录
|
||||||
func UserLogin(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"token": "example-token",
|
"token": token,
|
||||||
|
"user": user,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -27,32 +84,163 @@ func UserLogout(c *gin.Context) {
|
|||||||
|
|
||||||
// GetUserProfile 获取用户资料
|
// GetUserProfile 获取用户资料
|
||||||
func GetUserProfile(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"id": 1,
|
"user": user,
|
||||||
"nickname": "用户昵称",
|
"profile": profile,
|
||||||
"avatar": "",
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserProfile 更新用户资料
|
// UpdateUserProfile 更新用户资料
|
||||||
func UpdateUserProfile(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"user": user,
|
||||||
|
"profile": profile,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// WechatAuth 微信授权
|
// WechatAuth 微信授权
|
||||||
func WechatAuth(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"openid": "example-openid",
|
"openid": openID,
|
||||||
|
"sessionKey": "mock_session_key",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+167
-11
@@ -2,56 +2,212 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gouki/lunar-server/internal/model"
|
||||||
|
"github.com/gouki/lunar-server/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetWishTree 获取许愿树
|
// GetWishTree 获取许愿树
|
||||||
func GetWishTree(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"tree": gin.H{
|
"tree": tree,
|
||||||
"id": 1,
|
"wishes": wishes,
|
||||||
"name": "许愿树",
|
|
||||||
"wishes": []interface{}{},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateWish 创建许愿
|
// CreateWish 创建许愿
|
||||||
func CreateWish(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": wish,
|
||||||
"id": 1,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWishList 获取许愿列表
|
// GetWishList 获取许愿列表
|
||||||
func GetWishList(c *gin.Context) {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"list": []interface{}{},
|
"list": wishes,
|
||||||
"total": 0,
|
"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 删除许愿
|
// DeleteWish 删除许愿
|
||||||
func DeleteWish(c *gin.Context) {
|
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")
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "success",
|
"msg": "success",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"id": id,
|
"list": products,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,19 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gouki/lunar-server/internal/config"
|
||||||
|
"github.com/gouki/lunar-server/internal/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CORS 跨域中间件
|
// CORS 跨域中间件
|
||||||
func CORS() gin.HandlerFunc {
|
func CORS() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||||
|
|
||||||
if c.Request.Method == "OPTIONS" {
|
if c.Request.Method == "OPTIONS" {
|
||||||
c.AbortWithStatus(http.StatusNoContent)
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
@@ -24,23 +27,68 @@ func CORS() gin.HandlerFunc {
|
|||||||
|
|
||||||
// Logger 日志中间件
|
// Logger 日志中间件
|
||||||
func Logger() gin.HandlerFunc {
|
func Logger() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||||
c.Next()
|
return ""
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth 认证中间件
|
// Auth JWT认证中间件
|
||||||
func Auth() gin.HandlerFunc {
|
func Auth() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
token := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
if token == "" {
|
if authHeader == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"code": 401,
|
"code": 401,
|
||||||
"msg": "unauthorized",
|
"msg": "未授权",
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析 Bearer token
|
||||||
|
parts := strings.SplitN(authHeader, " ", 2)
|
||||||
|
if !(len(parts) == 2 && parts[0] == "Bearer") {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"msg": "token格式错误",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenString := parts[1]
|
||||||
|
|
||||||
|
// 解析 token
|
||||||
|
cfg := config.Load()
|
||||||
|
userService := service.NewUserService()
|
||||||
|
userID, err := userService.ParseToken(tokenString, cfg.JWT.Secret)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 401,
|
||||||
|
"msg": "token无效",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将用户ID存入上下文
|
||||||
|
c.Set("userID", userID)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminAuth 管理员认证中间件
|
||||||
|
func AdminAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// TODO: 实现管理员认证
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimit 限流中间件
|
||||||
|
func RateLimit() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// TODO: 实现限流
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gouki/lunar-server/internal/config"
|
||||||
|
"github.com/gouki/lunar-server/internal/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderService 订单服务
|
||||||
|
type OrderService struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrderService 创建订单服务
|
||||||
|
func NewOrderService() *OrderService {
|
||||||
|
return &OrderService{
|
||||||
|
db: config.GetDB(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrder 创建订单
|
||||||
|
func (s *OrderService) CreateOrder(order *model.Order) error {
|
||||||
|
// 生成订单号
|
||||||
|
order.OrderNo = s.generateOrderNo()
|
||||||
|
order.Status = "pending"
|
||||||
|
|
||||||
|
// 设置过期时间(30分钟)
|
||||||
|
expireTime := time.Now().Add(30 * time.Minute)
|
||||||
|
order.ExpireTime = &expireTime
|
||||||
|
|
||||||
|
return s.db.Create(order).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateOrderNo 生成订单号
|
||||||
|
func (s *OrderService) generateOrderNo() string {
|
||||||
|
// 格式:L + 年月日时分秒 + 6位随机数
|
||||||
|
now := time.Now()
|
||||||
|
random := rand.Intn(1000000)
|
||||||
|
return fmt.Sprintf("L%s%06d", now.Format("20060102150405"), random)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrderByID 根据ID获取订单
|
||||||
|
func (s *OrderService) GetOrderByID(id uint) (*model.Order, error) {
|
||||||
|
var order model.Order
|
||||||
|
if err := s.db.First(&order, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrderByOrderNo 根据订单号获取订单
|
||||||
|
func (s *OrderService) GetOrderByOrderNo(orderNo string) (*model.Order, error) {
|
||||||
|
var order model.Order
|
||||||
|
if err := s.db.Where("order_no = ?", orderNo).First(&order).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &order, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrderList 获取订单列表
|
||||||
|
func (s *OrderService) GetOrderList(userID uint, page, pageSize int) ([]*model.Order, int64, error) {
|
||||||
|
var orders []*model.Order
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := s.db.Model(&model.Order{}).Where("user_id = ?", userID)
|
||||||
|
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Order("created_at DESC").
|
||||||
|
Offset((page - 1) * pageSize).
|
||||||
|
Limit(pageSize).
|
||||||
|
Find(&orders).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return orders, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOrderStatus 更新订单状态
|
||||||
|
func (s *OrderService) UpdateOrderStatus(orderNo, status string) error {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == "paid" {
|
||||||
|
now := time.Now()
|
||||||
|
updates["pay_time"] = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.db.Model(&model.Order{}).Where("order_no = ?", orderNo).Updates(updates).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelOrder 取消订单
|
||||||
|
func (s *OrderService) CancelOrder(id uint) error {
|
||||||
|
return s.db.Model(&model.Order{}).Where("id = ?", id).Update("status", "cancelled").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckOrderExpired 检查订单是否过期
|
||||||
|
func (s *OrderService) CheckOrderExpired(order *model.Order) bool {
|
||||||
|
if order.ExpireTime == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return time.Now().After(*order.ExpireTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateWechatPayOrder 创建微信支付订单
|
||||||
|
func (s *OrderService) CreateWechatPayOrder(order *model.Order, openID string) (map[string]string, error) {
|
||||||
|
// TODO: 实现微信支付下单逻辑
|
||||||
|
// 这里返回模拟数据
|
||||||
|
payParams := map[string]string{
|
||||||
|
"timeStamp": fmt.Sprintf("%d", time.Now().Unix()),
|
||||||
|
"nonceStr": s.generateNonceStr(),
|
||||||
|
"package": fmt.Sprintf("prepay_id=wx%s", order.OrderNo),
|
||||||
|
"signType": "MD5",
|
||||||
|
"paySign": s.generatePaySign(order.OrderNo),
|
||||||
|
}
|
||||||
|
|
||||||
|
return payParams, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateNonceStr 生成随机字符串
|
||||||
|
func (s *OrderService) generateNonceStr() string {
|
||||||
|
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePaySign 生成支付签名
|
||||||
|
func (s *OrderService) generatePaySign(orderNo string) string {
|
||||||
|
// TODO: 实现真实的微信支付签名
|
||||||
|
hash := md5.Sum([]byte(orderNo + "secret"))
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePayNotify 处理支付回调
|
||||||
|
func (s *OrderService) HandlePayNotify(orderNo, transactionID string) error {
|
||||||
|
// 更新订单状态
|
||||||
|
if err := s.UpdateOrderStatus(orderNo, "paid"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 处理业务逻辑(如创建许愿、开通会员等)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,26 +1,101 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/gouki/lunar-server/internal/config"
|
||||||
"github.com/gouki/lunar-server/internal/model"
|
"github.com/gouki/lunar-server/internal/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserService 用户服务
|
// UserService 用户服务
|
||||||
type UserService struct{}
|
type UserService struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserService 创建用户服务
|
||||||
|
func NewUserService() *UserService {
|
||||||
|
return &UserService{
|
||||||
|
db: config.GetDB(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserByOpenID 根据OpenID获取用户
|
// GetUserByOpenID 根据OpenID获取用户
|
||||||
func (s *UserService) GetUserByOpenID(openID string) (*model.User, error) {
|
func (s *UserService) GetUserByOpenID(openID string) (*model.User, error) {
|
||||||
// TODO: 实现数据库查询
|
var user model.User
|
||||||
return &model.User{}, nil
|
if err := s.db.Where("open_id = ?", openID).First(&user).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByID 根据ID获取用户
|
||||||
|
func (s *UserService) GetUserByID(id uint) (*model.User, error) {
|
||||||
|
var user model.User
|
||||||
|
if err := s.db.First(&user, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser 创建用户
|
// CreateUser 创建用户
|
||||||
func (s *UserService) CreateUser(user *model.User) error {
|
func (s *UserService) CreateUser(user *model.User) error {
|
||||||
// TODO: 实现数据库创建
|
return s.db.Create(user).Error
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser 更新用户
|
// UpdateUser 更新用户
|
||||||
func (s *UserService) UpdateUser(user *model.User) error {
|
func (s *UserService) UpdateUser(user *model.User) error {
|
||||||
// TODO: 实现数据库更新
|
return s.db.Save(user).Error
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
// UpdateUserProfile 更新用户资料
|
||||||
|
func (s *UserService) UpdateUserProfile(profile *model.UserProfile) error {
|
||||||
|
return s.db.Save(profile).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserProfile 获取用户资料
|
||||||
|
func (s *UserService) GetUserProfile(userID uint) (*model.UserProfile, error) {
|
||||||
|
var profile model.UserProfile
|
||||||
|
if err := s.db.Where("user_id = ?", userID).First(&profile).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateToken 生成JWT Token
|
||||||
|
func (s *UserService) GenerateToken(userID uint, secret string) (string, error) {
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"user_id": userID,
|
||||||
|
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(), // 7天过期
|
||||||
|
"iat": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return token.SignedString([]byte(secret))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseToken 解析JWT Token
|
||||||
|
func (s *UserService) ParseToken(tokenString, secret string) (uint, error) {
|
||||||
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(secret), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
userID := uint(claims["user_id"].(float64))
|
||||||
|
return userID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, errors.New("invalid token")
|
||||||
}
|
}
|
||||||
|
|||||||
+159
-11
@@ -1,38 +1,186 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gouki/lunar-server/internal/config"
|
||||||
"github.com/gouki/lunar-server/internal/model"
|
"github.com/gouki/lunar-server/internal/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WishService 许愿服务
|
// WishService 许愿服务
|
||||||
type WishService struct{}
|
type WishService struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWishService 创建许愿服务
|
||||||
|
func NewWishService() *WishService {
|
||||||
|
return &WishService{
|
||||||
|
db: config.GetDB(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetWishTree 获取许愿树
|
// GetWishTree 获取许愿树
|
||||||
func (s *WishService) GetWishTree(treeID uint) (*model.WishTree, error) {
|
func (s *WishService) GetWishTree(treeID uint) (*model.WishTree, error) {
|
||||||
// TODO: 实现数据库查询
|
var tree model.WishTree
|
||||||
return &model.WishTree{}, nil
|
if err := s.db.First(&tree, treeID).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tree, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWishTreeWithWishes 获取许愿树及其许愿
|
||||||
|
func (s *WishService) GetWishTreeWithWishes(treeID uint) (*model.WishTree, []*model.Wish, error) {
|
||||||
|
tree, err := s.GetWishTree(treeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wishes []*model.Wish
|
||||||
|
if err := s.db.Where("tree_id = ? AND status = 1", treeID).
|
||||||
|
Order("position DESC, created_at DESC").
|
||||||
|
Limit(tree.MaxWishes).
|
||||||
|
Find(&wishes).Error; err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree, wishes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateWish 创建许愿
|
// CreateWish 创建许愿
|
||||||
func (s *WishService) CreateWish(wish *model.Wish) error {
|
func (s *WishService) CreateWish(wish *model.Wish) error {
|
||||||
// TODO: 实现数据库创建
|
// 检查许愿树是否存在
|
||||||
return nil
|
tree, err := s.GetWishTree(wish.TreeID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("wish tree not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否超过最大许愿数
|
||||||
|
var count int64
|
||||||
|
s.db.Model(&model.Wish{}).Where("tree_id = ? AND status = 1", wish.TreeID).Count(&count)
|
||||||
|
if count >= int64(tree.MaxWishes) {
|
||||||
|
// 如果是付费许愿,覆盖最旧的免费许愿
|
||||||
|
if wish.Type == "paid" {
|
||||||
|
var oldestFreeWish model.Wish
|
||||||
|
if err := s.db.Where("tree_id = ? AND type = 'free' AND status = 1", wish.TreeID).
|
||||||
|
Order("created_at ASC").
|
||||||
|
First(&oldestFreeWish).Error; err == nil {
|
||||||
|
// 删除最旧的免费许愿
|
||||||
|
s.db.Model(&oldestFreeWish).Update("status", 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("wish tree is full")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置位置
|
||||||
|
if wish.Type == "paid" {
|
||||||
|
// 付费许愿位置靠前
|
||||||
|
wish.Position = 100
|
||||||
|
} else {
|
||||||
|
// 免费许愿位置随机
|
||||||
|
wish.Position = rand.Intn(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.db.Create(wish).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWishList 获取许愿列表
|
// GetWishList 获取许愿列表
|
||||||
func (s *WishService) GetWishList(treeID uint, page, pageSize int) ([]*model.Wish, int64, error) {
|
func (s *WishService) GetWishList(treeID uint, page, pageSize int) ([]*model.Wish, int64, error) {
|
||||||
// TODO: 实现数据库查询
|
var wishes []*model.Wish
|
||||||
return []*model.Wish{}, 0, nil
|
var total int64
|
||||||
|
|
||||||
|
query := s.db.Model(&model.Wish{}).Where("tree_id = ? AND status = 1", treeID)
|
||||||
|
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Order("position DESC, created_at DESC").
|
||||||
|
Offset((page - 1) * pageSize).
|
||||||
|
Limit(pageSize).
|
||||||
|
Find(&wishes).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wishes, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWishByID 根据ID获取许愿
|
||||||
|
func (s *WishService) GetWishByID(id uint) (*model.Wish, error) {
|
||||||
|
var wish model.Wish
|
||||||
|
if err := s.db.First(&wish, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &wish, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWish 删除许愿
|
// DeleteWish 删除许愿
|
||||||
func (s *WishService) DeleteWish(id uint) error {
|
func (s *WishService) DeleteWish(id uint) error {
|
||||||
// TODO: 实现数据库删除
|
return s.db.Model(&model.Wish{}).Where("id = ?", id).Update("status", 0).Error
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRobotWish 创建机器人许愿
|
// CreateRobotWish 创建机器人许愿
|
||||||
func (s *WishService) CreateRobotWish(treeID uint) error {
|
func (s *WishService) CreateRobotWish(treeID uint) error {
|
||||||
// TODO: 实现机器人自动许愿
|
// 机器人许愿内容库
|
||||||
return nil
|
robotWishes := []string{
|
||||||
|
"愿世界和平,人人幸福",
|
||||||
|
"祝所有人心想事成",
|
||||||
|
"愿健康常伴左右",
|
||||||
|
"祝事业蒸蒸日上",
|
||||||
|
"愿爱情甜蜜美满",
|
||||||
|
"祝学业进步,考试顺利",
|
||||||
|
"愿财源广进,富贵吉祥",
|
||||||
|
"祝家庭和睦,幸福美满",
|
||||||
|
"愿旅途平安,一路顺风",
|
||||||
|
"祝梦想成真,前程似锦",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机选择一条
|
||||||
|
content := robotWishes[rand.Intn(len(robotWishes))]
|
||||||
|
|
||||||
|
wish := &model.Wish{
|
||||||
|
UserID: 0, // 机器人用户ID为0
|
||||||
|
TreeID: treeID,
|
||||||
|
Content: content,
|
||||||
|
Type: "free",
|
||||||
|
IsRobot: true,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.db.Create(wish).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWishProducts 获取许愿商品列表
|
||||||
|
func (s *WishService) GetWishProducts() ([]*model.WishProduct, error) {
|
||||||
|
var products []*model.WishProduct
|
||||||
|
if err := s.db.Where("status = 1").Order("price ASC").Find(&products).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return products, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWishProductByID 根据ID获取许愿商品
|
||||||
|
func (s *WishService) GetWishProductByID(id uint) (*model.WishProduct, error) {
|
||||||
|
var product model.WishProduct
|
||||||
|
if err := s.db.First(&product, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &product, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartRobotWishJob 启动机器人许愿定时任务
|
||||||
|
func (s *WishService) StartRobotWishJob() {
|
||||||
|
ticker := time.NewTicker(time.Hour * 2) // 每2小时执行一次
|
||||||
|
go func() {
|
||||||
|
for range ticker.C {
|
||||||
|
// 随机决定是否发布许愿
|
||||||
|
if rand.Intn(100) < 30 { // 30%概率
|
||||||
|
s.CreateRobotWish(1) // 默认许愿树ID为1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ .title }} - 祈福小助手管理后台</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||||
|
<script src="https://unpkg.com/@inertiajs/vue3@1.0.0/dist/index.umd.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
<div id="app" data-page='{"component":"{{ .page }}","props":{},"url":"{{ .url }}","version":""}'></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const { createApp, h } = Vue;
|
||||||
|
const { createInertiaApp } = Inertia;
|
||||||
|
|
||||||
|
createInertiaApp({
|
||||||
|
resolve: name => {
|
||||||
|
const pages = {
|
||||||
|
dashboard: () => import('/static/js/pages/Dashboard.js'),
|
||||||
|
users: () => import('/static/js/pages/Users.js'),
|
||||||
|
orders: () => import('/static/js/pages/Orders.js'),
|
||||||
|
wishes: () => import('/static/js/pages/Wishes.js'),
|
||||||
|
settings: () => import('/static/js/pages/Settings.js'),
|
||||||
|
};
|
||||||
|
return pages[name] ? pages[name]() : pages.dashboard();
|
||||||
|
},
|
||||||
|
setup({ el, App, props, plugin }) {
|
||||||
|
createApp({ render: () => h(App, props) })
|
||||||
|
.use(plugin)
|
||||||
|
.mount(el);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// Dashboard 页面组件
|
||||||
|
export default {
|
||||||
|
template: `
|
||||||
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
<nav class="bg-white shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<h1 class="text-xl font-bold text-red-600">祈福小助手</h1>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
|
<a href="/admin" class="border-red-500 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
仪表盘
|
||||||
|
</a>
|
||||||
|
<a href="/admin/users" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
用户管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/orders" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
订单管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/wishes" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
许愿管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/settings" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
系统设置
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
<div class="px-4 py-6 sm:px-0">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-users text-2xl text-blue-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">总用户数</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">1,234</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-shopping-cart text-2xl text-green-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">总订单数</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">567</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-star text-2xl text-yellow-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">总许愿数</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">890</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-money-bill-wave text-2xl text-red-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">总收入</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">¥12,345</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<div class="bg-white shadow rounded-lg p-6">
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 mb-4">最近订单</h2>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">订单号</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">金额</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">L20240101001</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">用户A</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">¥10.00</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">已支付</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2024-01-01 12:00</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Orders 页面组件
|
||||||
|
export default {
|
||||||
|
template: `
|
||||||
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
<nav class="bg-white shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<h1 class="text-xl font-bold text-red-600">祈福小助手</h1>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
|
<a href="/admin" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
仪表盘
|
||||||
|
</a>
|
||||||
|
<a href="/admin/users" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
用户管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/orders" class="border-red-500 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
订单管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/wishes" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
许愿管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/settings" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
系统设置
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
<div class="px-4 py-6 sm:px-0">
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 mb-4">订单列表</h2>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">订单号</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">商品</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">金额</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">L20240101001</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">许愿</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">普通许愿条</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">¥1.00</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">已支付</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2024-01-01 12:00</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Settings 页面组件
|
||||||
|
export default {
|
||||||
|
template: `
|
||||||
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
<nav class="bg-white shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<h1 class="text-xl font-bold text-red-600">祈福小助手</h1>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
|
<a href="/admin" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
仪表盘
|
||||||
|
</a>
|
||||||
|
<a href="/admin/users" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
用户管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/orders" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
订单管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/wishes" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
许愿管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/settings" class="border-red-500 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
系统设置
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
<div class="px-4 py-6 sm:px-0">
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 mb-4">系统设置</h2>
|
||||||
|
<form class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">许愿树最大许愿数</label>
|
||||||
|
<input type="number" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 sm:text-sm" value="100">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">免费许愿最大字数</label>
|
||||||
|
<input type="number" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 sm:text-sm" value="20">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">付费许愿最大字数</label>
|
||||||
|
<input type="number" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 sm:text-sm" value="100">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">机器人许愿间隔(小时)</label>
|
||||||
|
<input type="number" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 sm:text-sm" value="2">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||||
|
保存设置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Users 页面组件
|
||||||
|
export default {
|
||||||
|
template: `
|
||||||
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
<nav class="bg-white shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<h1 class="text-xl font-bold text-red-600">祈福小助手</h1>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
|
<a href="/admin" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
仪表盘
|
||||||
|
</a>
|
||||||
|
<a href="/admin/users" class="border-red-500 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
用户管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/orders" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
订单管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/wishes" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
许愿管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/settings" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
系统设置
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
<div class="px-4 py-6 sm:px-0">
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 mb-4">用户列表</h2>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">昵称</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">OpenID</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">注册时间</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">1</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">微信用户</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">mock_openid_xxx</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2024-01-01</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">正常</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Wishes 页面组件
|
||||||
|
export default {
|
||||||
|
template: `
|
||||||
|
<div class="min-h-screen bg-gray-100">
|
||||||
|
<nav class="bg-white shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0 flex items-center">
|
||||||
|
<h1 class="text-xl font-bold text-red-600">祈福小助手</h1>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
|
<a href="/admin" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
仪表盘
|
||||||
|
</a>
|
||||||
|
<a href="/admin/users" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
用户管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/orders" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
订单管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/wishes" class="border-red-500 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
许愿管理
|
||||||
|
</a>
|
||||||
|
<a href="/admin/settings" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||||
|
系统设置
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
<div class="px-4 py-6 sm:px-0">
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 mb-4">许愿列表</h2>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">内容</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<tr>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">1</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">愿世界和平</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">付费</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">用户A</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2024-01-01 12:00</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user