feat: 新增设备登录功能,包括API接口、RPC服务、逻辑处理和相关数据类型及错误码。
Build docker and publish / prepare (20.15.1) (push) Successful in 11s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m32s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 8m6s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Successful in 4m26s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Successful in 8m23s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Successful in 4m1s
Build docker and publish / deploy (push) Successful in 45s
Build docker and publish / notify (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Successful in 3m55s

This commit is contained in:
2026-03-01 18:51:12 -08:00
parent be4cc669d2
commit 3b4429bdd9
20 changed files with 824 additions and 25 deletions
+19
View File
@@ -19,6 +19,14 @@ type (
Token string `json:"token"`
}
DeviceLoginReq {
Identifier string `json:"identifier" validate:"required"`
UserAgent string `json:"user_agent" validate:"required"`
ShortCode string `json:"short_code,optional"`
CfToken string `json:"cf_token,optional"`
IP string `header:"X-Original-Forwarded-For,optional"`
}
UserRegisterReq {
Identifier string `json:"identifier"`
Email string `json:"email" validate:"required"`
@@ -58,3 +66,14 @@ service ppanel-api {
post /reset (ResetPasswordReq) returns (LoginResp)
}
@server (
prefix: /api/v1/auth
group: auth
middleware: DecryptMiddleware
)
service ppanel-api {
@doc "设备登录"
@handler DeviceLoginHandler
post /login/device (DeviceLoginReq) returns (LoginResp)
}
@@ -0,0 +1,28 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.2
package auth
import (
"net/http"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/logic/auth"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/svc"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/types"
"github.com/zero-ppanel/zero-ppanel/pkg/result"
)
// 设备登录
func DeviceLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeviceLoginReq
if err := result.Parse(r, &req); err != nil {
result.HttpResult(r, w, nil, err)
return
}
l := auth.NewDeviceLoginLogic(r.Context(), svcCtx)
resp, err := l.DeviceLogin(&req)
result.HttpResult(r, w, resp, err)
}
}
+15
View File
@@ -38,6 +38,21 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/auth"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.DecryptMiddleware},
[]rest.Route{
{
// 设备登录
Method: http.MethodPost,
Path: "/login/device",
Handler: auth.DeviceLoginHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/v1/auth"),
)
server.AddRoutes(
[]rest.Route{
{
@@ -0,0 +1,87 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.2
package auth
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/svc"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/types"
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/core"
"github.com/zero-ppanel/zero-ppanel/pkg/jwtx"
"github.com/zero-ppanel/zero-ppanel/pkg/xerr"
"github.com/zeromicro/go-zero/core/logx"
)
type DeviceLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// 设备登录
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
return &DeviceLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginReq) (resp *types.LoginResp, err error) {
// 1. 调用 Core RPC 设备登录
rpcResp, err := l.svcCtx.CoreRpc.DeviceLogin(l.ctx, &core.DeviceLoginReq{
Identifier: req.Identifier,
UserAgent: req.UserAgent,
Ip: req.IP,
ShortCode: req.ShortCode,
})
if err != nil {
return nil, err
}
// 2. 检查用户是否禁用
if rpcResp.IsDisabled {
return nil, xerr.NewErrCode(xerr.UserDisabled)
}
// 3. 生成 session ID
sessionId := uuid.New().String()
// 4. 生成 JWT Token
token, err := jwtx.GenerateToken(
l.svcCtx.Config.JwtAuth.AccessSecret,
rpcResp.UserId,
"device",
rpcResp.IsAdmin,
l.svcCtx.Config.JwtAuth.AccessExpire,
)
if err != nil {
l.Errorf("GenerateToken error: %v", err)
return nil, xerr.NewErrCode(xerr.ServerError)
}
// 5. 写入 Redis session
expireDuration := time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire) * time.Second
// session:{sessionId} → userId
sessionKey := fmt.Sprintf("session:%s", sessionId)
if err := l.svcCtx.Redis.SetexCtx(l.ctx, sessionKey, fmt.Sprintf("%d", rpcResp.UserId), int(expireDuration.Seconds())); err != nil {
l.Errorf("Redis set session error: %v", err)
}
// device:{identifier} → sessionId
deviceKey := fmt.Sprintf("device:%s", req.Identifier)
if err := l.svcCtx.Redis.SetexCtx(l.ctx, deviceKey, sessionId, int(expireDuration.Seconds())); err != nil {
l.Errorf("Redis set device session error: %v", err)
}
return &types.LoginResp{
Token: token,
}, nil
}
+5 -2
View File
@@ -9,14 +9,16 @@ import (
coreClient "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/coreClient"
"github.com/zero-ppanel/zero-ppanel/pkg/signature"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)
type ServiceContext struct {
Config config.Config
CoreRpc coreClient.Core
Redis *redis.Redis
SignatureMiddleware *middleware.SignatureMiddleware
DecryptMiddleware *middleware.DecryptMiddleware
DecryptMiddleware rest.Middleware
}
func NewServiceContext(c config.Config) *ServiceContext {
@@ -26,7 +28,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
CoreRpc: coreClient.NewCore(zrpc.MustNewClient(c.CoreRpc)),
Redis: rds,
SignatureMiddleware: middleware.NewSignatureMiddleware(c, nonceStore),
DecryptMiddleware: middleware.NewDecryptMiddleware(c),
DecryptMiddleware: middleware.NewDecryptMiddleware(c).Handle,
}
}
+8
View File
@@ -31,6 +31,14 @@ type CreateTicketReq struct {
Content string `json:"content"`
}
type DeviceLoginReq struct {
Identifier string `json:"identifier" validate:"required"`
UserAgent string `json:"user_agent" validate:"required"`
ShortCode string `json:"short_code,optional"`
CfToken string `json:"cf_token,optional"`
IP string `header:"X-Original-Forwarded-For,optional"`
}
type DocumentDetailReq struct {
Id int64 `path:"id"`
}
-1
View File
@@ -30,7 +30,6 @@ func main() {
ctx := svc.NewServiceContext(c)
server.Use(ctx.SignatureMiddleware.Handle)
server.Use(ctx.DecryptMiddleware.Handle)
handler.RegisterHandlers(server, ctx)
// Registe global http error handler