webui: Add accessory management
Signed-off-by: Sean Du <do4suki@gmail.com>
@@ -0,0 +1,338 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"honoka-chan/internal/middleware"
|
||||
usermodel "honoka-chan/internal/model/user"
|
||||
"honoka-chan/internal/router"
|
||||
webuischema "honoka-chan/internal/schema/webui"
|
||||
"honoka-chan/pkg/db"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const accessoryDataPath = "assets/serverdata/accessory_data.json"
|
||||
|
||||
type accessoryInventoryItem struct {
|
||||
AccessoryID int `json:"accessory_id"`
|
||||
Count int `json:"count"`
|
||||
WornCount int `json:"worn_count"`
|
||||
}
|
||||
|
||||
func accessoryPage(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "admin/accessory.html", gin.H{
|
||||
"menu": 3,
|
||||
"url": strings.Split(ctx.Request.URL.String(), "?")[0],
|
||||
})
|
||||
}
|
||||
|
||||
func accessoryListPage(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "admin/accessory_list.html", gin.H{
|
||||
"menu": 3,
|
||||
"url": strings.Split(ctx.Request.URL.String(), "?")[0],
|
||||
})
|
||||
}
|
||||
|
||||
func accessoryData(ctx *gin.Context) {
|
||||
data, err := os.ReadFile(accessoryDataPath)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品数据读取失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data(http.StatusOK, "application/json; charset=utf-8", data)
|
||||
}
|
||||
|
||||
func addAccessory(ctx *gin.Context) {
|
||||
accessoryID, err := strconv.Atoi(strings.TrimSpace(ctx.PostForm("accessory_id")))
|
||||
if err != nil || accessoryID <= 0 {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品 ID 格式有误!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
quantity, err := strconv.Atoi(strings.TrimSpace(ctx.PostForm("quantity")))
|
||||
if err != nil || quantity <= 0 || quantity > 99 {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "数量格式有误!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
exists, err := db.MainEng.Table("accessory_m").
|
||||
Where("accessory_id = ? AND is_material = ? AND rarity = ?", accessoryID, 0, 4).
|
||||
Exist()
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品校验失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品不存在!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
session := db.UserEng.NewSession()
|
||||
defer session.Close()
|
||||
if err := session.Begin(); err != nil {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "数据库事务开启失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for range quantity {
|
||||
if _, err := session.Insert(&usermodel.UserAccessory{
|
||||
UserID: ctx.GetInt("userid"),
|
||||
AccessoryID: accessoryID,
|
||||
Exp: 0,
|
||||
}); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品添加失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := session.Commit(); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品添加失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 0,
|
||||
Message: "添加成功,请重新进入游戏查看!",
|
||||
})
|
||||
}
|
||||
|
||||
func accessoryInventoryData(ctx *gin.Context) {
|
||||
userID := ctx.GetInt("userid")
|
||||
|
||||
accessories := []usermodel.UserAccessory{}
|
||||
if err := db.UserEng.Table(new(usermodel.UserAccessory)).
|
||||
Where("user_id = ?", userID).
|
||||
OrderBy("accessory_id ASC, accessory_owning_user_id ASC").
|
||||
Find(&accessories); err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品库存读取失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wears := []usermodel.UserAccessoryWear{}
|
||||
if err := db.UserEng.Table(new(usermodel.UserAccessoryWear)).
|
||||
Where("user_id = ?", userID).
|
||||
Find(&wears); err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品佩戴信息读取失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
owningIDToAccessoryID := make(map[int]int, len(accessories))
|
||||
inventoryMap := make(map[int]*accessoryInventoryItem)
|
||||
for _, row := range accessories {
|
||||
owningIDToAccessoryID[row.AccessoryOwningUserID] = row.AccessoryID
|
||||
if inventoryMap[row.AccessoryID] == nil {
|
||||
inventoryMap[row.AccessoryID] = &accessoryInventoryItem{
|
||||
AccessoryID: row.AccessoryID,
|
||||
}
|
||||
}
|
||||
inventoryMap[row.AccessoryID].Count++
|
||||
}
|
||||
|
||||
for _, wear := range wears {
|
||||
accessoryID, ok := owningIDToAccessoryID[wear.AccessoryOwningUserID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if inventoryMap[accessoryID] == nil {
|
||||
inventoryMap[accessoryID] = &accessoryInventoryItem{
|
||||
AccessoryID: accessoryID,
|
||||
}
|
||||
}
|
||||
inventoryMap[accessoryID].WornCount++
|
||||
}
|
||||
|
||||
result := make([]accessoryInventoryItem, 0, len(inventoryMap))
|
||||
for _, item := range inventoryMap {
|
||||
result = append(result, *item)
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].AccessoryID < result[j].AccessoryID
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func deleteAccessory(ctx *gin.Context) {
|
||||
userID := ctx.GetInt("userid")
|
||||
|
||||
accessoryID, err := strconv.Atoi(strings.TrimSpace(ctx.PostForm("accessory_id")))
|
||||
if err != nil || accessoryID <= 0 {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品 ID 格式有误!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
quantity, err := strconv.Atoi(strings.TrimSpace(ctx.PostForm("quantity")))
|
||||
if err != nil || quantity <= 0 || quantity > 99 {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "数量格式有误!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
session := db.UserEng.NewSession()
|
||||
defer session.Close()
|
||||
if err := session.Begin(); err != nil {
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "数据库事务开启失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
accessories := []usermodel.UserAccessory{}
|
||||
if err := session.Table(new(usermodel.UserAccessory)).
|
||||
Where("user_id = ? AND accessory_id = ?", userID, accessoryID).
|
||||
OrderBy("accessory_owning_user_id ASC").
|
||||
Find(&accessories); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品库存读取失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(accessories) < quantity {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "删除数量超过持有数量!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wears := []usermodel.UserAccessoryWear{}
|
||||
if err := session.Table(new(usermodel.UserAccessoryWear)).
|
||||
Where("user_id = ?", userID).
|
||||
Find(&wears); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品佩戴信息读取失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wornSet := make(map[int]struct{}, len(wears))
|
||||
for _, wear := range wears {
|
||||
wornSet[wear.AccessoryOwningUserID] = struct{}{}
|
||||
}
|
||||
|
||||
deleteIDs := make([]int, 0, quantity)
|
||||
for _, row := range accessories {
|
||||
if _, worn := wornSet[row.AccessoryOwningUserID]; worn {
|
||||
continue
|
||||
}
|
||||
deleteIDs = append(deleteIDs, row.AccessoryOwningUserID)
|
||||
if len(deleteIDs) == quantity {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(deleteIDs) < quantity {
|
||||
for _, row := range accessories {
|
||||
if _, worn := wornSet[row.AccessoryOwningUserID]; !worn {
|
||||
continue
|
||||
}
|
||||
deleteIDs = append(deleteIDs, row.AccessoryOwningUserID)
|
||||
if len(deleteIDs) == quantity {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(deleteIDs) != quantity {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品删除目标计算失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := session.Table(new(usermodel.UserAccessoryWear)).
|
||||
In("accessory_owning_user_id", deleteIDs).
|
||||
Where("user_id = ?", userID).
|
||||
Delete(new(usermodel.UserAccessoryWear)); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品卸下失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := session.Table(new(usermodel.UserAccessory)).
|
||||
In("accessory_owning_user_id", deleteIDs).
|
||||
Where("user_id = ? AND accessory_id = ?", userID, accessoryID).
|
||||
Delete(new(usermodel.UserAccessory)); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品删除失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := session.Commit(); err != nil {
|
||||
session.Rollback()
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 1,
|
||||
Message: "饰品删除失败!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, webuischema.Msg{
|
||||
Code: 0,
|
||||
Message: "删除成功,请重新登录游戏生效!",
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
router.AddHandler("admin", "GET", "/accessory", middleware.WebAuth, accessoryPage)
|
||||
router.AddHandler("admin", "GET", "/accessory/list", middleware.WebAuth, accessoryListPage)
|
||||
router.AddHandler("admin", "GET", "/accessory/data", middleware.WebAuth, accessoryData)
|
||||
router.AddHandler("admin", "GET", "/accessory/list/data", middleware.WebAuth, accessoryInventoryData)
|
||||
router.AddHandler("admin", "POST", "/accessory/add", middleware.WebAuth, addAccessory)
|
||||
router.AddHandler("admin", "POST", "/accessory/delete", middleware.WebAuth, deleteAccessory)
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 19 KiB |