feat: 新增设备登录功能,包括API接口、RPC服务、逻辑处理和相关数据类型及错误码。
All checks were successful
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.queue image_name:ppanel-queue name:queue]) (push) Successful in 3m55s
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
All checks were successful
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.queue image_name:ppanel-queue name:queue]) (push) Successful in 3m55s
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
This commit is contained in:
parent
be4cc669d2
commit
3b4429bdd9
1
.serena/.gitignore
vendored
Normal file
1
.serena/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/cache
|
||||||
126
.serena/project.yml
Normal file
126
.serena/project.yml
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# the name by which the project can be referenced within Serena
|
||||||
|
project_name: "zero-ppanel"
|
||||||
|
|
||||||
|
|
||||||
|
# list of languages for which language servers are started; choose from:
|
||||||
|
# al bash clojure cpp csharp
|
||||||
|
# csharp_omnisharp dart elixir elm erlang
|
||||||
|
# fortran fsharp go groovy haskell
|
||||||
|
# java julia kotlin lua markdown
|
||||||
|
# matlab nix pascal perl php
|
||||||
|
# php_phpactor powershell python python_jedi r
|
||||||
|
# rego ruby ruby_solargraph rust scala
|
||||||
|
# swift terraform toml typescript typescript_vts
|
||||||
|
# vue yaml zig
|
||||||
|
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||||
|
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||||
|
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||||
|
# Note:
|
||||||
|
# - For C, use cpp
|
||||||
|
# - For JavaScript, use typescript
|
||||||
|
# - For Free Pascal/Lazarus, use pascal
|
||||||
|
# Special requirements:
|
||||||
|
# Some languages require additional setup/installations.
|
||||||
|
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||||
|
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||||
|
# The first language is the default language and the respective language server will be used as a fallback.
|
||||||
|
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||||
|
languages:
|
||||||
|
- go
|
||||||
|
|
||||||
|
# the encoding used by text files in the project
|
||||||
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
|
encoding: "utf-8"
|
||||||
|
|
||||||
|
# The language backend to use for this project.
|
||||||
|
# If not set, the global setting from serena_config.yml is used.
|
||||||
|
# Valid values: LSP, JetBrains
|
||||||
|
# Note: the backend is fixed at startup. If a project with a different backend
|
||||||
|
# is activated post-init, an error will be returned.
|
||||||
|
language_backend:
|
||||||
|
|
||||||
|
# whether to use project's .gitignore files to ignore files
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
|
||||||
|
# list of additional paths to ignore in this project.
|
||||||
|
# Same syntax as gitignore, so you can use * and **.
|
||||||
|
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project by name.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_lines`: Deletes a range of lines within a file.
|
||||||
|
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||||
|
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||||
|
# Should only be used in settings where the system prompt cannot be set,
|
||||||
|
# e.g. in clients you have no control over, like Claude Desktop.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||||
|
# * `remove_project`: Removes a project from the Serena configuration.
|
||||||
|
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||||
|
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||||
|
# * `switch_modes`: Activates modes by providing a list of their names
|
||||||
|
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||||
|
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||||
|
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||||
|
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
|
||||||
|
included_optional_tools: []
|
||||||
|
|
||||||
|
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||||
|
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||||
|
fixed_tools: []
|
||||||
|
|
||||||
|
# list of mode names to that are always to be included in the set of active modes
|
||||||
|
# The full set of modes to be activated is base_modes + default_modes.
|
||||||
|
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this setting overrides the global configuration.
|
||||||
|
# Set this to [] to disable base modes for this project.
|
||||||
|
# Set this to a list of mode names to always include the respective modes for this project.
|
||||||
|
base_modes:
|
||||||
|
|
||||||
|
# list of mode names that are to be activated by default.
|
||||||
|
# The full set of modes to be activated is base_modes + default_modes.
|
||||||
|
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||||
|
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||||
|
default_modes:
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||||
|
# such as docstrings or parameter information.
|
||||||
|
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||||
|
# If null or missing, use the setting from the global configuration.
|
||||||
|
symbol_info_budget:
|
||||||
@ -19,6 +19,14 @@ type (
|
|||||||
Token string `json:"token"`
|
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 {
|
UserRegisterReq {
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
@ -58,3 +66,14 @@ service ppanel-api {
|
|||||||
post /reset (ResetPasswordReq) returns (LoginResp)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
28
apps/api/internal/handler/auth/deviceLoginHandler.go
Normal file
28
apps/api/internal/handler/auth/deviceLoginHandler.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,6 +38,21 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithPrefix("/api/v1/auth"),
|
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(
|
server.AddRoutes(
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
|
|||||||
87
apps/api/internal/logic/auth/deviceLoginLogic.go
Normal file
87
apps/api/internal/logic/auth/deviceLoginLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -9,14 +9,16 @@ import (
|
|||||||
coreClient "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/coreClient"
|
coreClient "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/coreClient"
|
||||||
"github.com/zero-ppanel/zero-ppanel/pkg/signature"
|
"github.com/zero-ppanel/zero-ppanel/pkg/signature"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
"github.com/zeromicro/go-zero/zrpc"
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
CoreRpc coreClient.Core
|
CoreRpc coreClient.Core
|
||||||
|
Redis *redis.Redis
|
||||||
SignatureMiddleware *middleware.SignatureMiddleware
|
SignatureMiddleware *middleware.SignatureMiddleware
|
||||||
DecryptMiddleware *middleware.DecryptMiddleware
|
DecryptMiddleware rest.Middleware
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
@ -26,7 +28,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
return &ServiceContext{
|
return &ServiceContext{
|
||||||
Config: c,
|
Config: c,
|
||||||
CoreRpc: coreClient.NewCore(zrpc.MustNewClient(c.CoreRpc)),
|
CoreRpc: coreClient.NewCore(zrpc.MustNewClient(c.CoreRpc)),
|
||||||
|
Redis: rds,
|
||||||
SignatureMiddleware: middleware.NewSignatureMiddleware(c, nonceStore),
|
SignatureMiddleware: middleware.NewSignatureMiddleware(c, nonceStore),
|
||||||
DecryptMiddleware: middleware.NewDecryptMiddleware(c),
|
DecryptMiddleware: middleware.NewDecryptMiddleware(c).Handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,14 @@ type CreateTicketReq struct {
|
|||||||
Content string `json:"content"`
|
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 {
|
type DocumentDetailReq struct {
|
||||||
Id int64 `path:"id"`
|
Id int64 `path:"id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,6 @@ func main() {
|
|||||||
|
|
||||||
ctx := svc.NewServiceContext(c)
|
ctx := svc.NewServiceContext(c)
|
||||||
server.Use(ctx.SignatureMiddleware.Handle)
|
server.Use(ctx.SignatureMiddleware.Handle)
|
||||||
server.Use(ctx.DecryptMiddleware.Handle)
|
|
||||||
handler.RegisterHandlers(server, ctx)
|
handler.RegisterHandlers(server, ctx)
|
||||||
|
|
||||||
// Registe global http error handler
|
// Registe global http error handler
|
||||||
|
|||||||
@ -45,6 +45,23 @@ message GetNodeInfoResp {
|
|||||||
string status = 4;
|
string status = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Device 服务定义
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
message DeviceLoginReq {
|
||||||
|
string identifier = 1;
|
||||||
|
string user_agent = 2;
|
||||||
|
string ip = 3;
|
||||||
|
string short_code = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeviceLoginResp {
|
||||||
|
int64 user_id = 1;
|
||||||
|
bool is_admin = 2;
|
||||||
|
bool is_disabled = 3;
|
||||||
|
bool is_new_user = 4;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// 核心 RPC 服务接口
|
// 核心 RPC 服务接口
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -57,4 +74,7 @@ service Core {
|
|||||||
|
|
||||||
// 节点相关
|
// 节点相关
|
||||||
rpc GetNodeInfo(GetNodeInfoReq) returns(GetNodeInfoResp);
|
rpc GetNodeInfo(GetNodeInfoReq) returns(GetNodeInfoResp);
|
||||||
|
|
||||||
|
// 设备登录
|
||||||
|
rpc DeviceLogin(DeviceLoginReq) returns(DeviceLoginResp);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -374,6 +374,145 @@ func (x *GetNodeInfoResp) GetStatus() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Device 服务定义
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
type DeviceLoginReq struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||||
|
UserAgent string `protobuf:"bytes,2,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
|
||||||
|
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
ShortCode string `protobuf:"bytes,4,opt,name=short_code,json=shortCode,proto3" json:"short_code,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) Reset() {
|
||||||
|
*x = DeviceLoginReq{}
|
||||||
|
mi := &file_core_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeviceLoginReq) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_core_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeviceLoginReq.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeviceLoginReq) Descriptor() ([]byte, []int) {
|
||||||
|
return file_core_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) GetIdentifier() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Identifier
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) GetUserAgent() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserAgent
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) GetIp() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginReq) GetShortCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ShortCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceLoginResp struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
|
IsAdmin bool `protobuf:"varint,2,opt,name=is_admin,json=isAdmin,proto3" json:"is_admin,omitempty"`
|
||||||
|
IsDisabled bool `protobuf:"varint,3,opt,name=is_disabled,json=isDisabled,proto3" json:"is_disabled,omitempty"`
|
||||||
|
IsNewUser bool `protobuf:"varint,4,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) Reset() {
|
||||||
|
*x = DeviceLoginResp{}
|
||||||
|
mi := &file_core_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeviceLoginResp) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_core_proto_msgTypes[7]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeviceLoginResp.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeviceLoginResp) Descriptor() ([]byte, []int) {
|
||||||
|
return file_core_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) GetUserId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) GetIsAdmin() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsAdmin
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) GetIsDisabled() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsDisabled
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeviceLoginResp) GetIsNewUser() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsNewUser
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var File_core_proto protoreflect.FileDescriptor
|
var File_core_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_core_proto_rawDesc = "" +
|
const file_core_proto_rawDesc = "" +
|
||||||
@ -403,11 +542,27 @@ const file_core_proto_rawDesc = "" +
|
|||||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
|
||||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" +
|
"\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" +
|
||||||
"\x06server\x18\x03 \x01(\tR\x06server\x12\x16\n" +
|
"\x06server\x18\x03 \x01(\tR\x06server\x12\x16\n" +
|
||||||
"\x06status\x18\x04 \x01(\tR\x06status2\xa8\x01\n" +
|
"\x06status\x18\x04 \x01(\tR\x06status\"~\n" +
|
||||||
|
"\x0eDeviceLoginReq\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"identifier\x18\x01 \x01(\tR\n" +
|
||||||
|
"identifier\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"user_agent\x18\x02 \x01(\tR\tuserAgent\x12\x0e\n" +
|
||||||
|
"\x02ip\x18\x03 \x01(\tR\x02ip\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"short_code\x18\x04 \x01(\tR\tshortCode\"\x86\x01\n" +
|
||||||
|
"\x0fDeviceLoginResp\x12\x17\n" +
|
||||||
|
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x19\n" +
|
||||||
|
"\bis_admin\x18\x02 \x01(\bR\aisAdmin\x12\x1f\n" +
|
||||||
|
"\vis_disabled\x18\x03 \x01(\bR\n" +
|
||||||
|
"isDisabled\x12\x1e\n" +
|
||||||
|
"\vis_new_user\x18\x04 \x01(\bR\tisNewUser2\xe4\x01\n" +
|
||||||
"\x04Core\x12(\n" +
|
"\x04Core\x12(\n" +
|
||||||
"\x04Ping\x12\v.core.Empty\x1a\x13.core.BasicResponse\x12:\n" +
|
"\x04Ping\x12\v.core.Empty\x1a\x13.core.BasicResponse\x12:\n" +
|
||||||
"\vGetUserInfo\x12\x14.core.GetUserInfoReq\x1a\x15.core.GetUserInfoResp\x12:\n" +
|
"\vGetUserInfo\x12\x14.core.GetUserInfoReq\x1a\x15.core.GetUserInfoResp\x12:\n" +
|
||||||
"\vGetNodeInfo\x12\x14.core.GetNodeInfoReq\x1a\x15.core.GetNodeInfoRespB\bZ\x06./coreb\x06proto3"
|
"\vGetNodeInfo\x12\x14.core.GetNodeInfoReq\x1a\x15.core.GetNodeInfoResp\x12:\n" +
|
||||||
|
"\vDeviceLogin\x12\x14.core.DeviceLoginReq\x1a\x15.core.DeviceLoginRespB\bZ\x06./coreb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_core_proto_rawDescOnce sync.Once
|
file_core_proto_rawDescOnce sync.Once
|
||||||
@ -421,7 +576,7 @@ func file_core_proto_rawDescGZIP() []byte {
|
|||||||
return file_core_proto_rawDescData
|
return file_core_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_core_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
var file_core_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
var file_core_proto_goTypes = []any{
|
var file_core_proto_goTypes = []any{
|
||||||
(*Empty)(nil), // 0: core.Empty
|
(*Empty)(nil), // 0: core.Empty
|
||||||
(*BasicResponse)(nil), // 1: core.BasicResponse
|
(*BasicResponse)(nil), // 1: core.BasicResponse
|
||||||
@ -429,16 +584,20 @@ var file_core_proto_goTypes = []any{
|
|||||||
(*GetUserInfoResp)(nil), // 3: core.GetUserInfoResp
|
(*GetUserInfoResp)(nil), // 3: core.GetUserInfoResp
|
||||||
(*GetNodeInfoReq)(nil), // 4: core.GetNodeInfoReq
|
(*GetNodeInfoReq)(nil), // 4: core.GetNodeInfoReq
|
||||||
(*GetNodeInfoResp)(nil), // 5: core.GetNodeInfoResp
|
(*GetNodeInfoResp)(nil), // 5: core.GetNodeInfoResp
|
||||||
|
(*DeviceLoginReq)(nil), // 6: core.DeviceLoginReq
|
||||||
|
(*DeviceLoginResp)(nil), // 7: core.DeviceLoginResp
|
||||||
}
|
}
|
||||||
var file_core_proto_depIdxs = []int32{
|
var file_core_proto_depIdxs = []int32{
|
||||||
0, // 0: core.Core.Ping:input_type -> core.Empty
|
0, // 0: core.Core.Ping:input_type -> core.Empty
|
||||||
2, // 1: core.Core.GetUserInfo:input_type -> core.GetUserInfoReq
|
2, // 1: core.Core.GetUserInfo:input_type -> core.GetUserInfoReq
|
||||||
4, // 2: core.Core.GetNodeInfo:input_type -> core.GetNodeInfoReq
|
4, // 2: core.Core.GetNodeInfo:input_type -> core.GetNodeInfoReq
|
||||||
1, // 3: core.Core.Ping:output_type -> core.BasicResponse
|
6, // 3: core.Core.DeviceLogin:input_type -> core.DeviceLoginReq
|
||||||
3, // 4: core.Core.GetUserInfo:output_type -> core.GetUserInfoResp
|
1, // 4: core.Core.Ping:output_type -> core.BasicResponse
|
||||||
5, // 5: core.Core.GetNodeInfo:output_type -> core.GetNodeInfoResp
|
3, // 5: core.Core.GetUserInfo:output_type -> core.GetUserInfoResp
|
||||||
3, // [3:6] is the sub-list for method output_type
|
5, // 6: core.Core.GetNodeInfo:output_type -> core.GetNodeInfoResp
|
||||||
0, // [0:3] is the sub-list for method input_type
|
7, // 7: core.Core.DeviceLogin:output_type -> core.DeviceLoginResp
|
||||||
|
4, // [4:8] is the sub-list for method output_type
|
||||||
|
0, // [0:4] is the sub-list for method input_type
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
0, // [0:0] is the sub-list for field type_name
|
0, // [0:0] is the sub-list for field type_name
|
||||||
@ -455,7 +614,7 @@ func file_core_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_core_proto_rawDesc), len(file_core_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_core_proto_rawDesc), len(file_core_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 6,
|
NumMessages: 8,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,7 @@ const (
|
|||||||
Core_Ping_FullMethodName = "/core.Core/Ping"
|
Core_Ping_FullMethodName = "/core.Core/Ping"
|
||||||
Core_GetUserInfo_FullMethodName = "/core.Core/GetUserInfo"
|
Core_GetUserInfo_FullMethodName = "/core.Core/GetUserInfo"
|
||||||
Core_GetNodeInfo_FullMethodName = "/core.Core/GetNodeInfo"
|
Core_GetNodeInfo_FullMethodName = "/core.Core/GetNodeInfo"
|
||||||
|
Core_DeviceLogin_FullMethodName = "/core.Core/DeviceLogin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CoreClient is the client API for Core service.
|
// CoreClient is the client API for Core service.
|
||||||
@ -38,6 +39,8 @@ type CoreClient interface {
|
|||||||
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
|
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
|
||||||
// 节点相关
|
// 节点相关
|
||||||
GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts ...grpc.CallOption) (*GetNodeInfoResp, error)
|
GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts ...grpc.CallOption) (*GetNodeInfoResp, error)
|
||||||
|
// 设备登录
|
||||||
|
DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type coreClient struct {
|
type coreClient struct {
|
||||||
@ -78,6 +81,16 @@ func (c *coreClient) GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts .
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *coreClient) DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(DeviceLoginResp)
|
||||||
|
err := c.cc.Invoke(ctx, Core_DeviceLogin_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CoreServer is the server API for Core service.
|
// CoreServer is the server API for Core service.
|
||||||
// All implementations must embed UnimplementedCoreServer
|
// All implementations must embed UnimplementedCoreServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@ -92,6 +105,8 @@ type CoreServer interface {
|
|||||||
GetUserInfo(context.Context, *GetUserInfoReq) (*GetUserInfoResp, error)
|
GetUserInfo(context.Context, *GetUserInfoReq) (*GetUserInfoResp, error)
|
||||||
// 节点相关
|
// 节点相关
|
||||||
GetNodeInfo(context.Context, *GetNodeInfoReq) (*GetNodeInfoResp, error)
|
GetNodeInfo(context.Context, *GetNodeInfoReq) (*GetNodeInfoResp, error)
|
||||||
|
// 设备登录
|
||||||
|
DeviceLogin(context.Context, *DeviceLoginReq) (*DeviceLoginResp, error)
|
||||||
mustEmbedUnimplementedCoreServer()
|
mustEmbedUnimplementedCoreServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +126,9 @@ func (UnimplementedCoreServer) GetUserInfo(context.Context, *GetUserInfoReq) (*G
|
|||||||
func (UnimplementedCoreServer) GetNodeInfo(context.Context, *GetNodeInfoReq) (*GetNodeInfoResp, error) {
|
func (UnimplementedCoreServer) GetNodeInfo(context.Context, *GetNodeInfoReq) (*GetNodeInfoResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetNodeInfo not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetNodeInfo not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedCoreServer) DeviceLogin(context.Context, *DeviceLoginReq) (*DeviceLoginResp, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DeviceLogin not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedCoreServer) mustEmbedUnimplementedCoreServer() {}
|
func (UnimplementedCoreServer) mustEmbedUnimplementedCoreServer() {}
|
||||||
func (UnimplementedCoreServer) testEmbeddedByValue() {}
|
func (UnimplementedCoreServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@ -186,6 +204,24 @@ func _Core_GetNodeInfo_Handler(srv interface{}, ctx context.Context, dec func(in
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Core_DeviceLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeviceLoginReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CoreServer).DeviceLogin(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Core_DeviceLogin_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CoreServer).DeviceLogin(ctx, req.(*DeviceLoginReq))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// Core_ServiceDesc is the grpc.ServiceDesc for Core service.
|
// Core_ServiceDesc is the grpc.ServiceDesc for Core service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -205,6 +241,10 @@ var Core_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetNodeInfo",
|
MethodName: "GetNodeInfo",
|
||||||
Handler: _Core_GetNodeInfo_Handler,
|
Handler: _Core_GetNodeInfo_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeviceLogin",
|
||||||
|
Handler: _Core_DeviceLogin_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "core.proto",
|
Metadata: "core.proto",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// goctl 1.9.2
|
// goctl 1.9.2
|
||||||
// Source: core.proto
|
// Source: core.proto
|
||||||
|
|
||||||
package coreclient
|
package coreClient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -14,12 +14,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
BasicResponse = core.BasicResponse
|
BasicResponse = core.BasicResponse
|
||||||
Empty = core.Empty
|
DeviceLoginReq = core.DeviceLoginReq
|
||||||
GetNodeInfoReq = core.GetNodeInfoReq
|
DeviceLoginResp = core.DeviceLoginResp
|
||||||
GetNodeInfoResp = core.GetNodeInfoResp
|
Empty = core.Empty
|
||||||
GetUserInfoReq = core.GetUserInfoReq
|
GetNodeInfoReq = core.GetNodeInfoReq
|
||||||
GetUserInfoResp = core.GetUserInfoResp
|
GetNodeInfoResp = core.GetNodeInfoResp
|
||||||
|
GetUserInfoReq = core.GetUserInfoReq
|
||||||
|
GetUserInfoResp = core.GetUserInfoResp
|
||||||
|
|
||||||
Core interface {
|
Core interface {
|
||||||
// Ping 检查服务健康状态
|
// Ping 检查服务健康状态
|
||||||
@ -28,6 +30,8 @@ type (
|
|||||||
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
|
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
|
||||||
// 节点相关
|
// 节点相关
|
||||||
GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts ...grpc.CallOption) (*GetNodeInfoResp, error)
|
GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts ...grpc.CallOption) (*GetNodeInfoResp, error)
|
||||||
|
// 设备登录
|
||||||
|
DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultCore struct {
|
defaultCore struct {
|
||||||
@ -58,3 +62,9 @@ func (m *defaultCore) GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts
|
|||||||
client := core.NewCoreClient(m.cli.Conn())
|
client := core.NewCoreClient(m.cli.Conn())
|
||||||
return client.GetNodeInfo(ctx, in, opts...)
|
return client.GetNodeInfo(ctx, in, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设备登录
|
||||||
|
func (m *defaultCore) DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error) {
|
||||||
|
client := core.NewCoreClient(m.cli.Conn())
|
||||||
|
return client.DeviceLogin(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
|||||||
104
apps/rpc/core/internal/logic/deviceLoginLogic.go
Normal file
104
apps/rpc/core/internal/logic/deviceLoginLogic.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/core"
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/repo"
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/svc"
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/pkg/xerr"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeviceLoginLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
|
||||||
|
return &DeviceLoginLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceLogin 设备登录:查设备 → 不存在则注册 → 返回用户信息
|
||||||
|
func (l *DeviceLoginLogic) DeviceLogin(in *core.DeviceLoginReq) (*core.DeviceLoginResp, error) {
|
||||||
|
// 1. 查询设备
|
||||||
|
device, err := l.svcCtx.DeviceRepo.FindDeviceByIdentifier(l.ctx, in.Identifier)
|
||||||
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
l.Errorf("FindDeviceByIdentifier error: %v", err)
|
||||||
|
return nil, status.Error(codes.Code(xerr.DatabaseQueryError), "查询设备失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
var userID int64
|
||||||
|
var isNewUser bool
|
||||||
|
var isAdmin bool
|
||||||
|
|
||||||
|
if errors.Is(err, sql.ErrNoRows) || device == nil {
|
||||||
|
// 2. 设备不存在,创建用户+设备
|
||||||
|
userID, err = l.svcCtx.DeviceRepo.CreateUserWithDevice(l.ctx, repo.CreateUserWithDeviceParams{
|
||||||
|
Identifier: in.Identifier,
|
||||||
|
UserAgent: in.UserAgent,
|
||||||
|
IP: in.Ip,
|
||||||
|
ShortCode: in.ShortCode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("CreateUserWithDevice error: %v", err)
|
||||||
|
return nil, status.Error(codes.Code(xerr.DatabaseInsertError), "创建用户设备失败")
|
||||||
|
}
|
||||||
|
isNewUser = true
|
||||||
|
|
||||||
|
// 记录注册日志
|
||||||
|
_ = l.svcCtx.DeviceRepo.InsertLoginLog(l.ctx, repo.LoginLogParams{
|
||||||
|
UserID: userID,
|
||||||
|
Method: "device",
|
||||||
|
IP: in.Ip,
|
||||||
|
UserAgent: in.UserAgent,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 3. 设备存在,检查是否启用
|
||||||
|
if !device.Enabled {
|
||||||
|
return nil, status.Error(codes.Code(xerr.DeviceNotEnabled), "设备已禁用")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID = device.UserID
|
||||||
|
|
||||||
|
// 查用户信息
|
||||||
|
user, err := l.svcCtx.DeviceRepo.FindUserByID(l.ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("FindUserByID error: %v", err)
|
||||||
|
return nil, status.Error(codes.Code(xerr.DatabaseQueryError), "查询用户失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.Enable {
|
||||||
|
return nil, status.Error(codes.Code(xerr.UserDisabled), "用户已禁用")
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin = user.IsAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 记录登录日志
|
||||||
|
_ = l.svcCtx.DeviceRepo.InsertLoginLog(l.ctx, repo.LoginLogParams{
|
||||||
|
UserID: userID,
|
||||||
|
Method: "device",
|
||||||
|
IP: in.Ip,
|
||||||
|
UserAgent: in.UserAgent,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &core.DeviceLoginResp{
|
||||||
|
UserId: userID,
|
||||||
|
IsAdmin: isAdmin,
|
||||||
|
IsDisabled: false,
|
||||||
|
IsNewUser: isNewUser,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
155
apps/rpc/core/internal/repo/device_repo.go
Normal file
155
apps/rpc/core/internal/repo/device_repo.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/pkg/cryptox"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeviceInfo represents a row from user_device table.
|
||||||
|
type DeviceInfo struct {
|
||||||
|
ID int64 `db:"id"`
|
||||||
|
UserID int64 `db:"user_id"`
|
||||||
|
Identifier string `db:"Identifier"`
|
||||||
|
Enabled bool `db:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo represents minimal user fields needed for login.
|
||||||
|
type UserInfo struct {
|
||||||
|
ID int64 `db:"id"`
|
||||||
|
Enable bool `db:"enable"`
|
||||||
|
IsAdmin bool `db:"is_admin"`
|
||||||
|
IsDeleted bool `db:"is_del"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUserWithDeviceParams holds parameters for creating a user and device in one transaction.
|
||||||
|
type CreateUserWithDeviceParams struct {
|
||||||
|
Identifier string
|
||||||
|
UserAgent string
|
||||||
|
IP string
|
||||||
|
ShortCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginLogParams holds parameters for inserting a login log.
|
||||||
|
type LoginLogParams struct {
|
||||||
|
UserID int64
|
||||||
|
Method string
|
||||||
|
IP string
|
||||||
|
UserAgent string
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceRepo defines device-related data access methods.
|
||||||
|
type DeviceRepo interface {
|
||||||
|
FindDeviceByIdentifier(ctx context.Context, identifier string) (*DeviceInfo, error)
|
||||||
|
FindUserByID(ctx context.Context, userID int64) (*UserInfo, error)
|
||||||
|
CreateUserWithDevice(ctx context.Context, params CreateUserWithDeviceParams) (int64, error)
|
||||||
|
InsertLoginLog(ctx context.Context, params LoginLogParams) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type deviceRepo struct {
|
||||||
|
conn sqlx.SqlConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeviceRepo(conn sqlx.SqlConn) DeviceRepo {
|
||||||
|
return &deviceRepo{conn: conn}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *deviceRepo) FindDeviceByIdentifier(ctx context.Context, identifier string) (*DeviceInfo, error) {
|
||||||
|
var d DeviceInfo
|
||||||
|
err := r.conn.QueryRowCtx(ctx, &d,
|
||||||
|
"SELECT `id`, `user_id`, `Identifier`, `enabled` FROM `user_device` WHERE `Identifier` = ? LIMIT 1",
|
||||||
|
identifier,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *deviceRepo) FindUserByID(ctx context.Context, userID int64) (*UserInfo, error) {
|
||||||
|
var u UserInfo
|
||||||
|
err := r.conn.QueryRowCtx(ctx, &u,
|
||||||
|
"SELECT `id`, `enable`, `is_admin`, COALESCE(`is_del`, 0) AS `is_del` FROM `user` WHERE `id` = ? LIMIT 1",
|
||||||
|
userID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *deviceRepo) CreateUserWithDevice(ctx context.Context, params CreateUserWithDeviceParams) (int64, error) {
|
||||||
|
// Generate a random password hash for device-only users
|
||||||
|
randomPwd, err := cryptox.GeneratePasswordHash("device-placeholder-" + params.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("hash password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userID int64
|
||||||
|
err = r.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||||
|
// 1. Create user
|
||||||
|
result, err := session.ExecCtx(ctx,
|
||||||
|
"INSERT INTO `user` (`password`, `algo`, `salt`, `enable`, `is_admin`, `created_at`, `updated_at`) VALUES (?, 'bcrypt', 'default', 1, 0, NOW(3), NOW(3))",
|
||||||
|
randomPwd,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert user: %w", err)
|
||||||
|
}
|
||||||
|
userID, err = result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("last insert id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create auth method
|
||||||
|
_, err = session.ExecCtx(ctx,
|
||||||
|
"INSERT INTO `user_auth_methods` (`user_id`, `auth_type`, `auth_identifier`, `verified`, `created_at`, `updated_at`) VALUES (?, 'device', ?, 1, NOW(3), NOW(3))",
|
||||||
|
userID, params.Identifier,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert auth method: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Create device
|
||||||
|
_, err = session.ExecCtx(ctx,
|
||||||
|
"INSERT INTO `user_device` (`user_id`, `ip`, `Identifier`, `short_code`, `user_agent`, `online`, `enabled`, `created_at`, `updated_at`) VALUES (?, ?, ?, ?, ?, 0, 1, NOW(3), NOW(3))",
|
||||||
|
userID, params.IP, params.Identifier, params.ShortCode, params.UserAgent,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert device: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return userID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *deviceRepo) InsertLoginLog(ctx context.Context, params LoginLogParams) error {
|
||||||
|
content, _ := json.Marshal(map[string]interface{}{
|
||||||
|
"method": params.Method,
|
||||||
|
"login_ip": params.IP,
|
||||||
|
"user_agent": params.UserAgent,
|
||||||
|
"success": params.Success,
|
||||||
|
"timestamp": time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := r.conn.ExecCtx(ctx,
|
||||||
|
"INSERT INTO `system_logs` (`type`, `date`, `object_id`, `content`, `created_at`) VALUES (?, ?, ?, ?, NOW(3))",
|
||||||
|
6, // type=6 is Login log
|
||||||
|
time.Now().Format("2006-01-02"),
|
||||||
|
params.UserID,
|
||||||
|
string(content),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert login log: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNotFound is re-exported for convenience.
|
||||||
|
var ErrNotFound = sql.ErrNoRows
|
||||||
@ -40,3 +40,9 @@ func (s *CoreServer) GetNodeInfo(ctx context.Context, in *core.GetNodeInfoReq) (
|
|||||||
l := logic.NewGetNodeInfoLogic(ctx, s.svcCtx)
|
l := logic.NewGetNodeInfoLogic(ctx, s.svcCtx)
|
||||||
return l.GetNodeInfo(in)
|
return l.GetNodeInfo(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设备登录
|
||||||
|
func (s *CoreServer) DeviceLogin(ctx context.Context, in *core.DeviceLoginReq) (*core.DeviceLoginResp, error) {
|
||||||
|
l := logic.NewDeviceLoginLogic(ctx, s.svcCtx)
|
||||||
|
return l.DeviceLogin(in)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,13 +1,27 @@
|
|||||||
package svc
|
package svc
|
||||||
|
|
||||||
import "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/config"
|
import (
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/config"
|
||||||
|
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/repo"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
|
DB sqlx.SqlConn
|
||||||
|
Redis *redis.Redis
|
||||||
|
DeviceRepo repo.DeviceRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
|
db := sqlx.NewMysql(c.MySQL.DataSource)
|
||||||
|
rds := redis.MustNewRedis(c.CacheRedis)
|
||||||
|
|
||||||
return &ServiceContext{
|
return &ServiceContext{
|
||||||
Config: c,
|
Config: c,
|
||||||
|
DB: db,
|
||||||
|
Redis: rds,
|
||||||
|
DeviceRepo: repo.NewDeviceRepo(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,11 @@ type Claims struct {
|
|||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateToken(secret string, userId int64, isAdmin bool, expire int64) (string, error) {
|
func GenerateToken(secret string, userId int64, loginType string, isAdmin bool, expire int64) (string, error) {
|
||||||
claims := Claims{
|
claims := Claims{
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
IsAdmin: isAdmin,
|
LoginType: loginType,
|
||||||
|
IsAdmin: isAdmin,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
|||||||
@ -24,4 +24,6 @@ const (
|
|||||||
SignatureInvalid = 7003
|
SignatureInvalid = 7003
|
||||||
SignatureReplay = 7004
|
SignatureReplay = 7004
|
||||||
DecryptFailed = 7005
|
DecryptFailed = 7005
|
||||||
|
DeviceLoginDisabled = 8001
|
||||||
|
DeviceNotEnabled = 8002
|
||||||
)
|
)
|
||||||
|
|||||||
@ -24,6 +24,8 @@ var codeText = map[int]string{
|
|||||||
SignatureInvalid: "签名错误",
|
SignatureInvalid: "签名错误",
|
||||||
SignatureReplay: "重放攻击",
|
SignatureReplay: "重放攻击",
|
||||||
DecryptFailed: "解密失败",
|
DecryptFailed: "解密失败",
|
||||||
|
DeviceLoginDisabled: "设备登录未启用",
|
||||||
|
DeviceNotEnabled: "设备已禁用",
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCodeErr(errcode uint32) bool {
|
func IsCodeErr(errcode uint32) bool {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user