Files
90CNServer/server/internal/platform/datatable/page.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

152 lines
3.6 KiB
Go

package datatable
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"strings"
)
const DefaultPageLimit = 100
type PageOptions struct {
Offset int `json:"offset,omitempty"`
Limit int `json:"limit,omitempty"`
Cursor string `json:"cursor,omitempty"`
}
type Page[T any] struct {
Table Table `json:"table"`
Manifest Manifest `json:"manifest"`
Rows []T `json:"rows,omitempty"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Total int `json:"total"`
NextCursor string `json:"next_cursor,omitempty"`
HasMore bool `json:"has_more"`
}
func LoadRowsPage[T any](ctx context.Context, source RowSource, name string, options PageOptions) (Page[T], error) {
if err := contextErr(ctx); err != nil {
return Page[T]{}, err
}
rows, err := LoadRows[T](source, name)
if err != nil {
return Page[T]{}, err
}
return PageRows(rows, options)
}
func PageRows[T any](set RowSet[T], options PageOptions) (Page[T], error) {
offset, limit, err := normalizePageOptions(options, len(set.Rows))
if err != nil {
return Page[T]{}, err
}
end := offset + limit
if end > len(set.Rows) {
end = len(set.Rows)
}
pageRows := append([]T(nil), set.Rows[offset:end]...)
next := ""
hasMore := end < len(set.Rows)
if hasMore {
next = EncodePageCursor(end)
}
return Page[T]{
Table: set.Table,
Manifest: set.Manifest,
Rows: pageRows,
Offset: offset,
Limit: limit,
Total: len(set.Rows),
NextCursor: next,
HasMore: hasMore,
}, nil
}
func (s RowSet[T]) Page(options PageOptions) (Page[T], error) {
return PageRows(s, options)
}
func (v *TableView[T]) Page(options PageOptions) (Page[T], error) {
if v == nil {
return Page[T]{}, ErrTableRequired
}
return v.Snapshot().Page(options)
}
func EncodePageCursor(offset int) string {
if offset <= 0 {
return ""
}
payload, _ := json.Marshal(map[string]int{"offset": offset})
return base64.RawURLEncoding.EncodeToString(payload)
}
func DecodePageCursor(cursor string) (int, error) {
cursor = strings.TrimSpace(cursor)
if cursor == "" {
return 0, nil
}
data, err := base64.RawURLEncoding.DecodeString(cursor)
if err != nil {
return 0, fmt.Errorf("decode page cursor: %w", err)
}
var payload struct {
Offset int `json:"offset"`
}
if err := json.Unmarshal(data, &payload); err != nil {
return 0, fmt.Errorf("parse page cursor: %w", err)
}
if payload.Offset < 0 {
return 0, fmt.Errorf("page cursor offset must be non-negative")
}
return payload.Offset, nil
}
func normalizePageOptions(options PageOptions, total int) (int, int, error) {
offset := options.Offset
if strings.TrimSpace(options.Cursor) != "" {
decoded, err := DecodePageCursor(options.Cursor)
if err != nil {
return 0, 0, err
}
offset = decoded
}
if offset < 0 {
return 0, 0, fmt.Errorf("page offset must be non-negative")
}
if offset > total {
offset = total
}
limit := options.Limit
if limit < 0 {
return 0, 0, fmt.Errorf("page limit must be positive")
}
if limit == 0 {
limit = DefaultPageLimit
}
return offset, limit, nil
}
func ParsePageOptions(offsetText, limitText, cursor string) (PageOptions, error) {
options := PageOptions{Cursor: strings.TrimSpace(cursor)}
if strings.TrimSpace(offsetText) != "" {
offset, err := strconv.Atoi(strings.TrimSpace(offsetText))
if err != nil {
return PageOptions{}, fmt.Errorf("parse page offset: %w", err)
}
options.Offset = offset
}
if strings.TrimSpace(limitText) != "" {
limit, err := strconv.Atoi(strings.TrimSpace(limitText))
if err != nil {
return PageOptions{}, fmt.Errorf("parse page limit: %w", err)
}
options.Limit = limit
}
return options, nil
}