netplay-lobby-server-go/controller/sessioncontroller.go
2024-12-24 22:09:17 +08:00

205 lines
5.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"errors"
"fmt"
"html/template"
"io"
"net"
"net/http"
"strconv"
"time"
"github.com/labstack/echo/v4"
"github.com/libretro/netplay-lobby-server-go/domain"
"github.com/libretro/netplay-lobby-server-go/model/entity"
)
// Template 抽象模板渲染。
type Template struct {
templates *template.Template
}
// Render 实现 echo 模板渲染接口
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
// SessionDomain 接口,用于将控制器逻辑与领域代码解耦。
type SessionDomain interface {
Add(request *domain.AddSessionRequest, ip net.IP) (*entity.Session, error)
Get(roomID int32) (*entity.Session, error)
List() ([]entity.Session, error)
GetTunnel(tunnelName string) *domain.MitmInfo
PurgeOld() error
}
// ListSessionsResponse 是一个自定义的 DTO用于向后兼容。
type SessionsResponse struct {
Fields entity.Session `json:"fields"`
}
// SessionController 处理所有与会话相关的请求
type SessionController struct {
sessionDomain SessionDomain
}
// NewSessionController 返回一个新的会话控制器
func NewSessionController(sessionDomain SessionDomain) *SessionController {
return &SessionController{sessionDomain}
}
// RegisterRoutes 在 echo 框架实例中注册所有控制器路由。
func (c *SessionController) RegisterRoutes(server *echo.Echo) {
server.POST("/add", c.Add)
server.POST("/add/", c.Add) // 旧路径
server.GET("/list", c.List)
server.GET("/list/", c.List) // 旧路径
server.GET("/tunnel", c.Tunnel)
server.GET("/tunnel/", c.Tunnel) // 旧路径
server.GET("/", c.Index)
server.GET("/:roomID", c.Get)
server.GET("/:roomID/", c.Get) // 旧路径
}
// PrerenderTemplates 预渲染所有模板
func (c *SessionController) PrerenderTemplates(server *echo.Echo, filePattern string) error {
templates, err := template.New("").Funcs(
template.FuncMap{
"prettyBool": func(b bool) string {
if b {
return "Yes"
}
return "No"
},
"prettyDate": func(d time.Time) string {
utc, _ := time.LoadLocation("UTC")
return d.In(utc).Format(time.RFC822)
},
},
).ParseGlob(filePattern)
if err != nil {
return fmt.Errorf("无法解析模板: %w", err)
}
t := &Template{
templates: templates,
}
server.Renderer = t
return nil
}
// Index 处理器
// GET /
func (c *SessionController) Index(ctx echo.Context) error {
logger := ctx.Logger()
sessions, err := c.sessionDomain.List()
if err != nil {
logger.Errorf("无法渲染会话列表: %v", err)
return ctx.NoContent(http.StatusInternalServerError)
}
return ctx.Render(http.StatusOK, "index.html", sessions)
}
// Get 处理器
// GET /:roomID
func (c *SessionController) Get(ctx echo.Context) error {
logger := ctx.Logger()
roomIDString := ctx.Param("roomID")
roomID, err := strconv.ParseInt(roomIDString, 10, 32)
if err != nil {
logger.Errorf("无法通过 roomID 获取会话。RoomID 不是有效的 int32: %v", err)
return ctx.NoContent(http.StatusBadRequest)
}
session, err := c.sessionDomain.Get(int32(roomID))
if err != nil || session == nil {
logger.Errorf("无法获取会话: %v", err)
return ctx.NoContent(http.StatusNotFound)
}
// 出于兼容旧实现的原因,我们需要将会话放在一个包装对象中,
// 该对象的会话可以通过键 "fields" 访问。旧实现还返回一个条目的列表...
response := make([]SessionsResponse, 1)
response[0] = SessionsResponse{*session}
return ctx.JSONPretty(http.StatusOK, response, " ")
}
// List 处理器
// GET /list
func (c *SessionController) List(ctx echo.Context) error {
logger := ctx.Logger()
sessions, err := c.sessionDomain.List()
if err != nil {
logger.Errorf("无法渲染会话列表: %v", err)
return ctx.NoContent(http.StatusInternalServerError)
}
// 出于兼容旧实现的原因,我们需要将会话放在一个包装对象中,
// 该对象的会话可以通过键 "fields" 访问
response := make([]SessionsResponse, len(sessions))
for i, session := range sessions {
response[i].Fields = session
}
return ctx.JSONPretty(http.StatusOK, response, " ")
}
// Add 处理器
// POST /add
func (c *SessionController) Add(ctx echo.Context) error {
logger := ctx.Logger()
var err error
var session *entity.Session
var req domain.AddSessionRequest
if err := ctx.Bind(&req); err != nil {
logger.Errorf("无法解析传入的会话: %v", err)
return ctx.NoContent(http.StatusBadRequest)
}
ip := net.ParseIP(ctx.RealIP())
if session, err = c.sessionDomain.Add(&req, ip); err != nil {
logger.Errorf("不会添加会话: %v", err)
if errors.Is(err, domain.ErrSessionRejected) {
logger.Errorf("会话被拒绝: %v", session)
return ctx.NoContent(http.StatusBadRequest)
} else if errors.Is(err, domain.ErrRateLimited) {
return ctx.NoContent(http.StatusTooManyRequests)
}
return ctx.NoContent(http.StatusBadRequest)
}
result := "status=OK\n"
result += session.PrintForRetroarch()
return ctx.String(http.StatusOK, result)
}
// Tunnel 处理器
// GET /tunnel
func (c *SessionController) Tunnel(ctx echo.Context) error {
logger := ctx.Logger()
tunnelName := ctx.QueryParam("name")
if tunnelName == "" {
return ctx.NoContent(http.StatusBadRequest)
}
tunnel := c.sessionDomain.GetTunnel(tunnelName)
if tunnel == nil {
logger.Errorf("找不到隧道服务器: '%s'", tunnelName)
return ctx.NoContent(http.StatusNotFound)
}
result := "status=OK\n"
result += tunnel.PrintForRetroarch()
return ctx.String(http.StatusOK, result)
}