DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

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

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

No files matched your search

@@ -0,0 +1,194 @@
package logic
import (
"errors"
"net/http"
"strings"
"longheng.io/server/internal/platform/httpx"
"longheng.io/server/internal/reference/player"
"longheng.io/server/pkg/contracts"
)
func (s *Service) registerRoutes(mux *http.ServeMux) {
prefix := s.adminPrefix
mux.HandleFunc(prefix+"/players", s.admin(s.handlePlayers))
mux.HandleFunc(prefix+"/player_summaries", s.admin(s.handleSummaries))
mux.HandleFunc(prefix+"/player_summaries/rebuild", s.admin(s.handleSummaryRebuild))
mux.HandleFunc(prefix+"/readmodels/player_summaries", s.admin(s.handleViewSearch("player_summaries")))
mux.HandleFunc(prefix+"/readmodels/player_summaries/rebuild", s.admin(s.handleViewRebuild("player_summaries")))
mux.HandleFunc(prefix+"/accounts", s.admin(s.handleAccounts))
mux.HandleFunc(prefix+"/playerstore", s.admin(s.handlePlayerStore))
mux.HandleFunc(prefix+"/playerstore/deadletters", s.admin(s.handleDeadLetters))
mux.HandleFunc(prefix+"/playerstore/deadletters/requeue", s.adminDangerous(httpx.AdminOperationID(http.MethodPost, prefix+"/playerstore/deadletters/requeue"), s.handleDLRequeue))
mux.HandleFunc(prefix+"/world", s.admin(s.handleWorld))
mux.HandleFunc(prefix+"/playerloops", s.admin(s.handlePlayerLoops))
mux.HandleFunc(prefix+"/idempotency", s.admin(s.handleIdempotency))
mux.HandleFunc(prefix+"/handlers", s.admin(s.handleHandlers))
mux.HandleFunc(prefix+"/profiledb", s.admin(s.handleProfileDB))
mux.HandleFunc(prefix+"/mock/player", s.adminDangerous(httpx.AdminOperationID(http.MethodPost, prefix+"/mock/player"), s.handlePlayerMutation))
s.registerAdminRoutes(mux)
s.registerOpsRoutes(mux, prefix)
}
func (s *Service) handlePlayers(w http.ResponseWriter, _ *http.Request) {
if s.players == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "player module not configured"})
return
}
httpx.WriteJSON(w, http.StatusOK, s.players.Snapshot())
}
func (s *Service) handleAccounts(w http.ResponseWriter, _ *http.Request) {
if s.accounts == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "account runtime not configured"})
return
}
httpx.WriteJSON(w, http.StatusOK, s.accounts.Snapshot())
}
func (s *Service) handlePlayerStore(w http.ResponseWriter, _ *http.Request) {
if s.players == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "player module not configured"})
return
}
httpx.WriteJSON(w, http.StatusOK, s.players.StoreStatus())
}
func (s *Service) handleDeadLetters(w http.ResponseWriter, r *http.Request) {
if s.players == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "player module not configured"})
return
}
if r.Method != http.MethodGet {
w.Header().Set("Allow", http.MethodGet)
httpx.WriteJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"dead_letters": s.players.DeadLetters(),
})
}
func (s *Service) handleDLRequeue(w http.ResponseWriter, r *http.Request) {
if s.players == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "player module not configured"})
return
}
if r.Method != http.MethodPost {
w.Header().Set("Allow", http.MethodPost)
httpx.WriteJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
accountID := strings.TrimSpace(r.URL.Query().Get("account_id"))
if accountID == "" {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "account_id is required"})
return
}
receipt, duplicate, submitted, err := s.submitOpsCmd(r, "logic.playerstore.deadletter_requeue", accountID, "deadletter requeue", map[string]any{
"account_id": accountID,
})
if writeOpsCmdErr(w, err) {
return
}
if submitted && duplicate {
writeOpsDup(w, receipt)
return
}
if !s.players.RequeueDeadLetter(accountID) {
if submitted {
s.markOpsCmdFail(r.Context(), receipt, errors.New("dead letter not found"))
}
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "dead letter not found"})
return
}
response := map[string]any{
"status": "requeued",
"account_id": accountID,
}
if submitted {
receipt = s.markOpsAdminOK(r.Context(), receipt)
writeOpsAdminReceipt(w, http.StatusOK, response, receipt)
return
}
httpx.WriteJSON(w, http.StatusOK, response)
}
func (s *Service) handleWorld(w http.ResponseWriter, _ *http.Request) {
if s.world == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "world module not configured"})
return
}
httpx.WriteJSON(w, http.StatusOK, s.world.Snapshot())
}
func (s *Service) handlePlayerLoops(w http.ResponseWriter, _ *http.Request) {
httpx.WriteJSON(w, http.StatusOK, s.playerLoops.Snapshot())
}
func (s *Service) handleIdempotency(w http.ResponseWriter, _ *http.Request) {
responseCache := map[string]any{}
if s.responses != nil {
responseCache = s.responses.Snapshot()
}
if s.idempotency == nil {
httpx.WriteJSON(w, http.StatusOK, map[string]any{"response_cache": responseCache})
return
}
snapshot := s.idempotency.Snapshot()
snapshot["response_cache"] = responseCache
httpx.WriteJSON(w, http.StatusOK, snapshot)
}
func (s *Service) handleHandlers(w http.ResponseWriter, _ *http.Request) {
httpx.WriteJSON(w, http.StatusOK, s.dispatcher.Snapshot())
}
func (s *Service) handleProfileDB(w http.ResponseWriter, _ *http.Request) {
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"fields": player.ProfileFieldDescriptors(),
"operations": player.ProfileOperationDescriptors(),
})
}
func (s *Service) handlePlayerMutation(w http.ResponseWriter, r *http.Request) {
if s.players == nil {
httpx.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "player module not configured"})
return
}
accountID := strings.TrimSpace(r.URL.Query().Get("account_id"))
state := strings.TrimSpace(r.URL.Query().Get("state"))
if accountID == "" {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "account_id is required"})
return
}
if state == "" {
state = "online"
}
receipt, duplicate, submitted, err := s.submitOpsCmd(r, "logic.mock.player_mutation", accountID, "mock player mutation", map[string]any{
"account_id": accountID,
"state": state,
})
if writeOpsCmdErr(w, err) {
return
}
if submitted && duplicate {
writeOpsDup(w, receipt)
return
}
record := s.players.Upsert(accountID, state)
event := contracts.PlayerStateChanged{
AccountID: record.AccountID,
State: record.State,
UpdatedAt: record.UpdatedAt,
}
s.publishAsync(contracts.TopicPlayerStateChange, event)
if submitted {
receipt = s.markOpsAdminOK(r.Context(), receipt)
writeOpsAdminReceipt(w, http.StatusOK, map[string]any{"player": record}, receipt)
return
}
httpx.WriteJSON(w, http.StatusOK, record)
}