Files
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

225 lines
5.6 KiB
Go

package storageobject
import (
"context"
"errors"
"strings"
)
var ErrPermissionDenied = errors.New("storage object permission denied")
type Subject struct {
UserID string `json:"user_id,omitempty"`
Admin bool `json:"admin,omitempty"`
System bool `json:"system,omitempty"`
}
type Service struct {
store Store
}
func NewService(store Store) *Service {
return &Service{store: store}
}
func (s *Service) Close() error {
if s == nil || s.store == nil {
return nil
}
if closer, ok := s.store.(interface{ Close() error }); ok {
return closer.Close()
}
return nil
}
func (s *Service) Write(ctx context.Context, subject Subject, request WriteRequest) (Object, error) {
if s == nil || s.store == nil {
return Object{}, ErrObjectNotFound
}
key, err := normalizeKey(request.Object.Key)
if err != nil {
return Object{}, err
}
current, ok, err := s.store.Read(ctx, key)
if err != nil {
return Object{}, err
}
guard := request.Object
if ok {
guard = current
}
if err := AuthorizeWrite(subject, guard); err != nil {
return Object{}, err
}
return s.store.Write(ctx, request)
}
func (s *Service) BatchWrite(ctx context.Context, subject Subject, request BatchWriteRequest) BatchWriteResult {
result := BatchWriteResult{Results: make([]BatchObjectResult, 0, len(request.Writes))}
for _, write := range request.Writes {
object, err := s.Write(ctx, subject, write)
item := BatchObjectResult{Object: object, Found: err == nil}
if err != nil {
item.Error = err.Error()
}
result.Results = append(result.Results, item)
}
return result
}
func (s *Service) Read(ctx context.Context, subject Subject, key Key) (Object, bool, error) {
if s == nil || s.store == nil {
return Object{}, false, nil
}
object, ok, err := s.store.Read(ctx, key)
if err != nil || !ok {
return object, ok, err
}
if err := AuthorizeRead(subject, object); err != nil {
return Object{}, false, err
}
return object, true, nil
}
func (s *Service) BatchRead(ctx context.Context, subject Subject, request BatchReadRequest) BatchReadResult {
result := BatchReadResult{Results: make([]BatchObjectResult, 0, len(request.Keys))}
for _, key := range request.Keys {
object, ok, err := s.Read(ctx, subject, key)
item := BatchObjectResult{Object: object, Found: ok}
if err != nil {
item.Error = err.Error()
}
result.Results = append(result.Results, item)
}
return result
}
func (s *Service) Delete(ctx context.Context, subject Subject, key Key, ifMatchVersion string) error {
if s == nil || s.store == nil {
return ErrObjectNotFound
}
object, ok, err := s.store.Read(ctx, key)
if err != nil {
return err
}
if !ok {
return ErrObjectNotFound
}
if err := AuthorizeWrite(subject, object); err != nil {
return err
}
return s.store.Delete(ctx, key, ifMatchVersion)
}
func (s *Service) BatchDelete(ctx context.Context, subject Subject, request BatchDeleteRequest) BatchDeleteResults {
result := BatchDeleteResults{Results: make([]BatchDeleteResult, 0, len(request.Deletes))}
for _, deletion := range request.Deletes {
err := s.Delete(ctx, subject, deletion.Key, deletion.IfMatchVersion)
item := BatchDeleteResult{Key: deletion.Key, Deleted: err == nil}
if err != nil {
item.Error = err.Error()
}
result.Results = append(result.Results, item)
}
return result
}
func (s *Service) List(ctx context.Context, subject Subject, request ListRequest) (ListResult, error) {
if s == nil || s.store == nil {
return ListResult{}, nil
}
if normalizeSubject(subject).Admin {
result, err := s.store.List(ctx, request)
if err != nil {
return ListResult{}, err
}
return result, nil
}
limit := normalizeListLimit(request.Limit)
result := ListResult{Objects: make([]Object, 0, limit)}
scan := request
scan.Limit = limit
cursor := strings.TrimSpace(request.Cursor)
for {
scan.Cursor = cursor
page, err := s.store.List(ctx, scan)
if err != nil {
return ListResult{}, err
}
for _, object := range page.Objects {
if !CanRead(subject, object) {
continue
}
result.Objects = append(result.Objects, object)
if len(result.Objects) >= limit {
result.NextCursor = objectID(object.Key)
return result, nil
}
}
if strings.TrimSpace(page.NextCursor) == "" {
return result, nil
}
cursor = page.NextCursor
}
}
func AuthorizeRead(subject Subject, object Object) error {
if CanRead(subject, object) {
return nil
}
return ErrPermissionDenied
}
func AuthorizeWrite(subject Subject, object Object) error {
if CanWrite(subject, object) {
return nil
}
return ErrPermissionDenied
}
func CanRead(subject Subject, object Object) bool {
subject = normalizeSubject(subject)
if subject.Admin {
return true
}
switch normalizePermission(object.PermissionRead) {
case PermissionPublic:
return true
case PermissionOwner:
if subject.System {
return object.OwnerKind() == OwnerKindSystem
}
return ownsObject(subject, object)
default:
return false
}
}
func CanWrite(subject Subject, object Object) bool {
subject = normalizeSubject(subject)
if subject.Admin {
return true
}
switch normalizePermission(object.PermissionWrite) {
case PermissionPublic:
return subject.UserID != "" || subject.System
case PermissionOwner:
if subject.System {
return object.OwnerKind() == OwnerKindSystem
}
return ownsObject(subject, object)
default:
return false
}
}
func ownsObject(subject Subject, object Object) bool {
owner := strings.TrimSpace(object.Key.UserID)
return subject.UserID != "" && owner != "" && subject.UserID == owner
}
func normalizeSubject(subject Subject) Subject {
subject.UserID = strings.TrimSpace(subject.UserID)
return subject
}