热土用户介绍

最后更新:2026-05-23

什么是热土用户?

热土用户(RTUser)是由热土工作室开发的一套统一用户认证与授权系统,旨在为开发者提供便捷、安全、可靠的用户登录解决方案。通过热土用户,您的软件可以快速接入统一的用户账号体系,无需自行搭建复杂的用户管理系统。

🔐

安全认证

支持state参数防CSRF攻击,Token有效期管理完善

快速接入

提供完善的SDK和详细的接入文档,30分钟内完成基础接入

💾

数据存储

支持用户数据段存储,轻松实现跨设备数据同步

🌐

多端支持

支持桌面应用、Web应用等多种应用类型接入

系统架构

您的应用
API代理层
热土用户核心

所有API请求通过代理层转发,确保安全性和稳定性。代理地址:https://appapi.rtstudio.top/

核心流程

1

用户发起登录

应用打开授权登录页面,用户输入账号密码

2

授权确认

用户确认授权,系统生成访问Token

3

Token回调

Token通过回调返回给应用

4

获取用户信息

应用使用Token调用API获取用户信息

产品优势

最后更新:2026-05-23

为什么选择热土用户?

🎯

统一账号体系

用户只需一个热土账号,即可登录所有接入热土用户的软件,无需在每个软件单独注册账号。

🛡️

安全可靠

采用OAuth-Like授权流程,支持state参数防CSRF攻击,完善的Token有效期管理机制。

📦

开箱即用

提供完整的SDK、详细的接入文档和示例代码,开发者可快速完成接入,无需搭建复杂的用户系统。

🔄

数据同步

通过数据段功能,用户数据可在不同设备间同步,提升用户体验。

📊

可视化管理

开发者可通过仪表盘管理软件Token、查看授权用户、发布Beta测试等。

已验证与未验证软件

特性 已验证软件 未验证软件
登录提示 显示软件名称和开发者信息 显示风险警告
数据段功能 支持(最多10个) 不支持
用户信任度 较低
仪表盘显示 已授权应用 未授权应用
提示:推荐申请已验证软件认证,提升用户信任度和功能权限。

适用场景

最后更新:2026-05-23

典型的应用场景

🖥️

桌面应用

各类Windows/Mac桌面软件,通过本地HTTP服务器接收登录回调,实现用户认证。

示例:工具软件、游戏客户端、编辑器插件
🌐

Web应用

网站和Web应用,通过URL重定向接收Token回调,快速实现用户登录。

示例:在线工具、管理后台、SaaS平台
🔧

开发工具

IDE插件、命令行工具等开发者工具,快速接入用户体系。

示例:VS Code插件、自动化脚本工具
🎮

游戏与娱乐

游戏、娱乐类软件,利用数据段实现设置保存。

示例:独立游戏、音乐播放器

不适合的场景

注意:以下场景不建议使用热土用户:
  • 需要高度定制化认证流程的应用
  • 对用户数据有特殊合规要求的金融、医疗类应用
  • 需要完全自主控制用户数据的应用

接入指南

最后更新:2026-05-23

快速接入流程

1

准备阶段

确定软件类型(桌面/Web),获取软件Token或准备软件名称

  • 已验证软件:使用软件Token(申请见联系我们
  • 未验证软件:使用软件名称
2

开发阶段

集成SDK或自行实现登录流程

  • 桌面应用:启动本地HTTP服务器监听回调
  • Web应用:配置回调URL
  • 调用API获取用户信息
3

测试阶段

使用测试账号验证登录流程

  • 验证Token有效性
  • 测试用户信息获取
  • 测试数据段功能(如需要)
4

上线阶段

发布软件,用户可使用热土账号登录

登录URL参数说明

参数 必填 说明
port 桌面应用:本地端口;Web应用:回调网址
type token(已验证)或 name(未验证)
v 软件Token或软件名称
state 防CSRF的随机字符串(建议16字节)
env 可选 app(桌面)或 web(Web应用)
桌面应用示例
https://apilogin.rtstudio.top/?port=3456&type=token&v=your_token&state=random_state&env=app
Web应用示例
https://apilogin.rtstudio.top/?port=yourapp.com/callback&type=token&v=your_token&state=random_state&env=web

登录URL详解

最后更新:2026-05-23

URL基本结构

热土用户的登录授权URL遵循以下基本结构:

https://apilogin.rtstudio.top/?[参数列表]

所有参数通过URL查询字符串(Query String)传递,参数之间使用 & 符号分隔。

完整参数说明

参数名 必填 类型 说明
port 必填 string 回调接收地址
桌面应用:本地HTTP服务器端口(如 3456
Web应用:完整的回调URL路径(如 yourapp.com/callback
type 必填 string 软件标识类型
loginkey - 推荐方式,使用登录Key标识(保护软件Token)
token - 直接使用软件Token标识(已弃用
name - 未验证软件,使用软件名称标识
v 必填 string 软件标识值
type=loginkey 时:填写通过API获取的登录Key
type=token 时:填写软件Token(已弃用
type=name 时:填写软件名称
state 必填 string 防CSRF攻击的随机字符串
建议使用16字节以上的随机值
回调时会原样返回,用于验证请求来源
env 可选 string 应用环境类型
app - 桌面应用(默认)
web - Web应用
影响登录页面的UI展示和回调处理方式

完整URL示例

推荐方式:使用登录Key(loginkey)

推荐使用 loginkey 方式!

通过 loginkey 方式,您的软件Token不会暴露在URL中,有效防止Token泄露。登录Key有效期仅10分钟,过期自动失效,安全性更高。

完整示例
https://apilogin.rtstudio.top/?port=3456&type=loginkey&v=YOUR_LOGIN_KEY&state=a1b2c3d4e5f6g7h8&env=app
port=3456 本地HTTP服务器监听端口
type=loginkey 使用登录Key标识软件(推荐)
v=YOUR_LOGIN_KEY 通过API获取的登录Key(10分钟有效)
state=a1b2c3d4... 随机生成的防CSRF字符串

桌面应用(直接传入Token - 已弃用)

⚠️ 此方式已弃用!

直接在URL中传入软件Token(type=token)的方式已弃用。URL可能被日志、浏览器历史记录等记录,导致Token泄露。请使用上方的 loginkey 方式。

已弃用示例(仅供参考)
https://apilogin.rtstudio.top/?port=3456&type=token&v=abc123def456&state=a1b2c3d4e5f6g7h8&env=app

桌面应用(未验证软件)

完整示例
https://apilogin.rtstudio.top/?port=3456&type=name&v=我的测试软件&state=random_state_123&env=app
注意:未验证软件使用 type=name 时,登录页面会显示风险警告提示,建议申请已验证认证。

Web应用

完整示例
https://apilogin.rtstudio.top/?port=https://yourapp.com/auth/callback&type=token&v=abc123def456&state=random_state&env=web
提示:Web应用的 port 参数需要填写完整的回调URL,用户登录成功后会重定向到此地址。

回调URL详解

用户完成登录授权后,系统会将结果通过回调URL返回给您的应用。

桌面应用回调

系统会向您的本地HTTP服务器发送GET请求:

成功回调
http://127.0.0.1:3456/?type=success&v=USER_ACCESS_TOKEN&state=YOUR_STATE
失败回调
http://127.0.0.1:3456/?type=error&v=错误信息&state=YOUR_STATE
回调参数 说明
type success 表示成功,error 表示失败
v 成功时为用户访问Token,失败时为错误信息
state 您在登录URL中传入的state值,用于验证

Web应用回调

系统会将用户重定向到您指定的回调URL:

成功回调
https://yourapp.com/auth/callback?type=success&v=USER_ACCESS_TOKEN&state=YOUR_STATE

您需要在回调页面解析URL参数,获取Token并验证state一致性。

获取登录Key API

为了保护您的软件Token不被暴露在URL中,我们提供了登录Key机制。登录Key是一个10分钟有效的临时凭证,用于替代软件Token出现在登录URL中。

工作流程

1. 使用软件Token获取登录Key

在您的后端服务器中,调用API用软件Token换取登录Key。

2. 构建登录URL

使用 type=loginkey 和获取到的登录Key构建URL,跳转用户到登录页面。

3. 登录Key自动验证

登录页面会自动通过登录Key查询到对应的软件信息,10分钟后登录Key自动失效。

API详情

请求方式:GET
GET https://appapi.rtstudio.top/login-token?token=YOUR_SOFTWARE_TOKEN
参数 类型 必填 说明
token string 必填 您的软件Token
成功响应
{
    "success": true,
    "data": {
        "login_key": "a1b2c3d4e5f6...",
        "expires_at": "2026-05-23T12:10:00.000Z"
    }
}

代码示例

Go语言
// 获取登录Key
func getLoginKey(softwareToken string) (string, error) {
    url := fmt.Sprintf("https://appapi.rtstudio.top/login-token?token=%s", softwareToken)
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    var result struct {
        Success bool `json:"success"`
        Data    struct {
            LoginKey  string `json:"login_key"`
            ExpiresAt string `json:"expires_at"`
        } `json:"data"`
        Message string `json:"message"`
    }
    
    json.NewDecoder(resp.Body).Decode(&result)
    if !result.Success {
        return "", fmt.Errorf(result.Message)
    }
    
    return result.Data.LoginKey, nil
}

// 使用登录Key构建登录URL
func buildLoginURL(softwareToken, port, state string) (string, error) {
    loginKey, err := getLoginKey(softwareToken)
    if err != nil {
        return "", err
    }
    
    return fmt.Sprintf("https://apilogin.rtstudio.top/?port=%s&type=loginkey&v=%s&state=%s", port, loginKey, state), nil
}
Python
import requests

def get_login_key(software_token):
    """获取登录Key"""
    resp = requests.get(
        'https://appapi.rtstudio.top/login-token',
        params={'token': software_token}
    )
    data = resp.json()
    if data['success']:
        return data['data']['login_key']
    raise Exception(data['message'])

def build_login_url(software_token, port, state):
    """构建安全的登录URL"""
    login_key = get_login_key(software_token)
    return f'https://apilogin.rtstudio.top/?port={port}&type=loginkey&v={login_key}&state={state}'
安全优势:
  • 软件Token仅在服务器端使用,不会出现在浏览器URL中
  • 登录Key 10分钟后自动失效,即使泄露也无法长期使用
  • 每次登录使用不同的登录Key,防止重放攻击

State参数安全说明

⚠️ 重要安全提示

state 参数是防止CSRF(跨站请求伪造)攻击的关键机制,请务必正确使用:

  • 每次登录请求生成新的随机state值
  • 建议使用16字节以上的随机字符串
  • 在接收回调时,验证返回的state与发送时一致
  • 验证失败时应拒绝此次登录

State生成示例

Go语言
func generateState() (string, error) {
    b := make([]byte, 16)
    _, err := rand.Read(b)
    if err != nil {
        return "", err
    }
    return hex.EncodeToString(b), nil
}
Python
import secrets

def generate_state():
    return secrets.token_hex(16)  # 生成32字符的随机字符串

Token使用说明

获取到用户访问Token后,您可以:

  • 调用 /verify-access-token 验证Token有效性
  • 调用 /get-user-by-token 获取用户基本信息
  • 调用数据段相关API(仅已验证软件)
Token有效期:用户访问Token有效期为1年,过期后用户需要重新登录授权。

URL编码注意事项

当参数值包含特殊字符时,需要进行URL编码:

场景 原始值 编码后
软件名称含中文 我的测试软件 %E6%88%91%E7%9A%84%E6%B5%8B%E8%AF%95%E8%BD%AF%E4%BD
回调URL含特殊字符 app.com/auth?type=login app.com%2Fauth%3Ftype%3Dlogin
JavaScript URL编码
const softwareName = '我的测试软件';
const encodedName = encodeURIComponent(softwareName);
const loginUrl = `https://apilogin.rtstudio.top/?port=3456&type=name&v=${encodedName}&state=random_state`;

开发准备

2026-05-23

获取软件Token

步骤一:准备材料
  • 软件名称
  • 开发者信息
  • 软件功能描述
  • 软件截图(如有)
  • 下载链接(如有)
步骤二:提交申请

发送邮件至 miles@rtstu.com

邮件主题:软件认证申请 - [软件名称]

步骤三:等待审核

审核周期:1-14个工作日

审核通过后将收到包含软件Token的邮件

环境要求

应用类型 要求
桌面应用 能够启动本地HTTP服务器、打开浏览器
Web应用 能够处理URL重定向和查询参数

API地址

API代理地址
https://appapi.rtstudio.top/
说明:所有API请求都需要通过此代理地址进行调用。

API概览

2026-05-23

API列表

接口 方法 说明 权限要求
/verify-software-token POST 验证软件Token
/verify-access-token POST 验证用户Token
/get-user-by-token POST 获取用户信息 有效Token
/data-segments POST 创建数据段 已验证软件
/data-segments PUT 修改数据段 已验证软件
/data-segments DELETE 删除数据段 已验证软件
/data-segments/read POST 读取数据段 已验证软件

通用响应格式

{
    "success": true,
    "message": "操作成功",
    "data": {
        // 具体数据
    }
}
失败响应
{
    "success": false,
    "message": "错误描述"
}

认证相关API

2026-05-23

验证软件Token

POST /verify-software-token
验证软件登录Token的有效性

请求参数

参数名 类型 必填 说明
token string 软件Token

响应示例

{
    "success": true,
    "data": {
        "id": 1,
        "name": "测试软件",
        "developer": "测试开发者"
    }
}

验证用户Token

POST /verify-access-token
验证用户授权Token的有效性

请求参数

参数名 类型 必填 说明
token string 用户授权Token

响应示例

{
    "success": true,
    "data": {
        "id": 1,
        "user_id": 1,
        "software_id": 1,
        "expires_at": "2027-03-22T12:00:00Z"
    }
}

获取用户信息

POST /get-user-by-token
根据Token获取用户基本信息

请求参数

参数名 类型 必填 说明
token string 用户授权Token

响应示例

{
    "success": true,
    "data": {
        "id": 1,
        "uuid": "...",
        "username": "testuser",
        "email": "test@example.com",
        "email_verified": true,
        "is_beta_user": false
    }
}

数据段相关API

2026-05-23
⚠️ 重要安全提示

数据段功能设计用于存储用户偏好设置、软件配置、非关键业务数据等非敏感信息。

严禁使用数据段存储以下类型的数据:

  • VIP/会员状态、订阅权限等核心业务判定数据
  • 付费信息、充值记录、消费数据
  • 用户等级、积分、特权等可影响业务逻辑的数据
  • 密码、密钥、证书等安全凭证
  • 个人身份信息(身份证、银行卡等)

原因:数据段存储在用户可访问的系统中,用户可能查看或修改这些数据。将VIP状态等关键数据存储在数据段中,将导致用户可以自行"解锁"VIP功能,造成业务风险。

正确做法:VIP状态、付费信息等核心业务数据应由您的后端服务器独立管理,通过您自己的API进行验证,不应依赖热土用户系统的数据段功能。

数据段说明

📋 每个用户最多10个数据段(已验证软件)
📏 单个数据段内容限制1MB
🔒 仅已验证软件可使用

创建数据段

POST /data-segments
为用户创建新的数据段

请求参数

参数名 类型 必填 说明
email string 用户邮箱
token string 用户授权Token
name string 数据段名称(唯一标识)
content string 数据段内容(JSON字符串)

响应示例

{
    "success": true,
    "message": "数据段创建成功",
    "data": {
        "id": 1,
        "name": "user_settings",
        "content": "{\"theme\": \"dark\"}"
    }
}

读取数据段

POST /data-segments/read
读取指定数据段的内容

请求参数

参数名 类型 必填 说明
email string 用户邮箱
token string 用户授权Token
name string 数据段名称

响应示例

{
    "success": true,
    "data": {
        "id": 1,
        "name": "user_settings",
        "content": "{\"theme\": \"dark\", \"language\": \"zh-CN\"}",
        "created_at": "2026-03-22T12:00:00Z",
        "updated_at": "2026-03-22T12:00:00Z"
    }
}

修改数据段

PUT /data-segments
更新已存在的数据段内容

请求参数

参数名 类型 必填 说明
email string 用户邮箱
token string 用户授权Token
name string 数据段名称
content string 新的数据段内容

删除数据段

DELETE /data-segments
删除指定的数据段

请求参数

参数名 类型 必填 说明
email string 用户邮箱
token string 用户授权Token
name string 数据段名称

适合存储的数据示例

✅ 用户偏好设置

{"theme": "dark", "language": "zh-CN", "fontSize": 14}

✅ 软件窗口布局

{"width": 800, "height": 600, "x": 100, "y": 100}

✅ 游戏非关键数据

{"ImageQuality": 5, "controls": "default"}

不适合存储的数据示例

❌ VIP状态(错误示例)

{"isVIP": true, "vipLevel": 3, "vipExpires": "2027-12-31"}

⚠️ 用户可以自行修改为VIP状态,绕过付费验证

❌ 用户积分(错误示例)

{"points": 9999, "level": 10, "achievements": ["all"]}

⚠️ 用户可以自行修改积分和等级

Go SDK

2026-05-23

完整登录流程实现

以下是完整的Go语言SDK实现代码,包含登录Key获取、登录流程、Token验证、用户信息获取等功能。

推荐使用 loginkey 方式!

此SDK默认使用 loginkey 方式保护软件Token。传入软件Token时,SDK会自动获取登录Key,确保Token不出现在浏览器URL中。

完整示例代码(与 login_mod/main.go 同步)
// main.go
// This program implements a login flow for "Retu Studio" users.
// It starts a local HTTP server, opens a browser for authentication,
// receives an access token via callback, and verifies the token.
package main

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"os/exec"
	"runtime"
	"time"
)

// 配置常量 / Configuration constants
const apiBaseURL = "https://appapi.rtstudio.top"
const loginBaseURL = "https://apilogin.rtstudio.top"

// 命令行参数 / Command line arguments
var (
	loginType string // loginkey, token 或 name
	value     string // 登录key, 软件token或软件名称
)

// init 解析命令行参数 / Parse command line flags
func init() {
	flag.StringVar(&loginType, "type", "", "登录类型: loginkey (推荐), token (已弃用), name 或 get_login_token (仅获取登录Key) / Login type: loginkey (recommended), token (deprecated), name or get_login_token (only get login key)")
	flag.StringVar(&value, "v", "", "登录key/软件token/软件名称 / Login key, software token or software name")
}

// loginResponse 登录接口返回的结构 / Structure of login API response
type loginResponse struct {
	Type  string `json:"type"`
	V     string `json:"v"`
	State string `json:"state"`
}

// verifyResponse token验证接口返回的结构 / Structure of token verification API response
type verifyResponse struct {
	Success bool   `json:"success"`
	Data    any    `json:"data"`
	Message string `json:"message"`
}

// loginKeyResponse 获取登录Key接口返回的结构 / Structure of login key API response
type loginKeyResponse struct {
	Success bool `json:"success"`
	Data    struct {
		LoginKey  string `json:"login_key"`
		ExpiresAt string `json:"expires_at"`
	} `json:"data"`
	Message string `json:"message"`
}

func main() {
	flag.Parse()

	// 验证参数 / Validate arguments
	if loginType != "loginkey" && loginType != "token" && loginType != "name" && loginType != "get_login_token" {
		log.Fatal("错误: -type 必须是 'loginkey', 'token', 'name' 或 'get_login_token' / Error: -type must be 'loginkey', 'token', 'name' or 'get_login_token'")
	}
	if value == "" {
		log.Fatal("错误: -v 参数不能为空 / Error: -v argument cannot be empty")
	}

	// 如果是get_login_token模式,仅获取并输出登录Key
	if loginType == "get_login_token" {
		loginKey, err := getLoginKey(value)
		if err != nil {
			log.Fatalf("获取登录Key失败: %v / Failed to get login key: %v", err)
		}
		// 输出登录Key到stdout
		fmt.Print(loginKey)
		return
	}

	// 如果是token类型,提示已弃用 / Warn if using deprecated token type
	if loginType == "token" {
		log.Println("⚠️ 警告: type=token 已弃用,建议使用 type=loginkey 以保护软件Token安全")
		log.Println("⚠️ Warning: type=token is deprecated, use type=loginkey to protect your software token")
	}

	// loginkey模式直接使用传入的值,不再自动获取
	// loginkey mode uses the passed value directly, no auto-fetch
	actualLoginType := loginType
	actualValue := value

	// 启动登录流程 / Start the login flow
	token, err := startLoginFlow(actualLoginType, actualValue)
	if err != nil {
		log.Fatalf("登录失败: %v / Login failed: %v", err)
	}

	// 验证token有效性 / Verify the obtained token
	if err := verifyToken(token); err != nil {
		log.Fatalf("token验证失败: %v / Token verification failed: %v", err)
	}

	// 获取用户信息 / Get user info
	userResult, err := getUserByToken(token)
	if err != nil {
		log.Fatalf("获取用户信息失败: %v / Failed to get user info: %v", err)
	}

	if userResult.Success {
		log.Printf("登录成功,用户: %+v / Login successful, user: %+v", userResult.Data)
	}

	// 输出token到stdout (供父进程通过管道读取) / Output token to stdout for parent process to read
	fmt.Print(token)
}

// isLikelySoftwareToken 判断是否可能是软件Token(64位hex字符串)
// Checks if the value looks like a software token (64-char hex string)
func isLikelySoftwareToken(v string) bool {
	if len(v) != 64 {
		return false
	}
	_, err := hex.DecodeString(v)
	return err == nil
}

// getLoginKey 通过软件Token获取登录Key
// Get a login key by exchanging a software token (10-minute validity)
func getLoginKey(softwareToken string) (string, error) {
	reqURL := fmt.Sprintf("%s/login-token?token=%s", apiBaseURL, url.QueryEscape(softwareToken))
	resp, err := http.Get(reqURL)
	if err != nil {
		return "", fmt.Errorf("请求失败: %v / Request failed: %v", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("读取响应失败: %v / Failed to read response: %v", err)
	}

	// 输出原始响应用于调试 / Output raw response for debugging
	log.Printf("API响应 (status %d): %s", resp.StatusCode, string(body))

	var result loginKeyResponse
	if err := json.Unmarshal(body, &result); err != nil {
		return "", fmt.Errorf("解析响应失败: %v / Failed to parse response: %v", err)
	}

	if !result.Success {
		return "", fmt.Errorf("%s", result.Message)
	}

	return result.Data.LoginKey, nil
}

// startLoginFlow 启动完整的登录流程 / Starts the complete login flow
// It generates a random state, finds an available port, starts a local HTTP server,
// opens the browser with the login URL, and waits for the callback.
func startLoginFlow(loginType, value string) (string, error) {
	// 1. 生成state参数 (16字节随机数,防CSRF) / Generate state parameter (16 random bytes for CSRF protection)
	state, err := generateState()
	if err != nil {
		return "", fmt.Errorf("生成state失败: %v / Failed to generate state: %v", err)
	}

	// 2. 寻找可用端口并启动本地服务器 / Find an available port and start the local server
	port, err := findAvailablePort()
	if err != nil {
		return "", fmt.Errorf("无法找到可用端口: %v / Cannot find available port: %v", err)
	}

	// 创建channel用于接收token或错误 / Create channels to receive token or error
	tokenChan := make(chan string, 1)
	errChan := make(chan error, 1)

	// 启动HTTP服务器 / Start HTTP server
	server := &http.Server{
		Addr:         fmt.Sprintf(":%d", port),
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	// 注册根路由处理器 / Register root route handler
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 只处理GET请求 / Handle only GET requests
		if r.Method != http.MethodGet {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}

		query := r.URL.Query()
		callbackType := query.Get("type")
		callbackValue := query.Get("v")
		callbackState := query.Get("state")

		if callbackType == "success" {
			// 验证state参数,防止CSRF攻击 / Verify state parameter to prevent CSRF
			if callbackState != state {
				errChan <- fmt.Errorf("state参数验证失败,可能是CSRF攻击 / State parameter mismatch, possible CSRF attack")
				http.Error(w, "state验证失败 / State verification failed", http.StatusBadRequest)
				return
			}
			tokenChan <- callbackValue
			// 返回成功页面 / Return success page
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(`登录成功

✅ 登录完成

请关闭此窗口

`)) // 延迟关闭服务器,确保响应发送完成 / Delay server shutdown to ensure response is sent go func() { time.Sleep(500 * time.Millisecond) server.Close() }() } else if callbackType == "error" { errChan <- fmt.Errorf("登录失败: %s / Login failed: %s", callbackValue) http.Error(w, "登录失败 / Login failed", http.StatusBadRequest) go func() { time.Sleep(500 * time.Millisecond) server.Close() }() } else { http.Error(w, "无效的回调类型 / Invalid callback type", http.StatusBadRequest) } }) // 在goroutine中启动服务器 / Start server in a goroutine go func() { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { errChan <- fmt.Errorf("HTTP服务器错误: %v / HTTP server error: %v", err) } }() // 等待服务器启动完成 / Wait for server to start time.Sleep(100 * time.Millisecond) // 3. 构造登录URL并打开浏览器 / Build login URL and open browser loginURL := fmt.Sprintf("%s/?port=%d&type=%s&v=%s&state=%s&env=app", loginBaseURL, port, loginType, url.QueryEscape(value), state) log.Printf("构造的登录URL: %s", loginURL) if err := openBrowser(loginURL); err != nil { return "", fmt.Errorf("打开浏览器失败: %v / Failed to open browser: %v", err) } log.Printf("已打开浏览器,请登录 / Browser opened, please log in") log.Printf("等待登录回调... / Waiting for login callback...") // 等待登录结果 / Wait for login result select { case token := <-tokenChan: return token, nil case err := <-errChan: return "", err case <-time.After(5 * time.Minute): server.Close() return "", fmt.Errorf("登录超时(5分钟) / Login timeout (5 minutes)") } } // generateState 生成16字节随机数作为state参数 / Generate 16 random bytes as state parameter func generateState() (string, error) { bytes := make([]byte, 16) if _, err := rand.Read(bytes); err != nil { return "", err } return hex.EncodeToString(bytes), nil } // findAvailablePort 寻找一个可用的TCP端口 / Find an available TCP port // It binds to port 0 which lets the OS choose a free port, then returns that port number. func findAvailablePort() (int, error) { listener, err := net.Listen("tcp", ":0") if err != nil { return 0, err } defer listener.Close() return listener.Addr().(*net.TCPAddr).Port, nil } // openBrowser 打开系统默认浏览器 / Open the system default browser func openBrowser(url string) error { switch runtime.GOOS { case "windows": return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() case "darwin": return exec.Command("open", url).Start() default: return exec.Command("xdg-open", url).Start() } } // verifyToken 验证token有效性 / Verify the validity of the token by calling the API func verifyToken(token string) error { reqBody := map[string]string{"token": token} jsonBody, err := json.Marshal(reqBody) if err != nil { return err } resp, err := http.Post(apiBaseURL+"/verify-access-token", "application/json", bytes.NewBuffer(jsonBody)) if err != nil { return fmt.Errorf("API请求失败: %v / API request failed: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return err } var result verifyResponse if err := json.Unmarshal(body, &result); err != nil { return err } if !result.Success { return fmt.Errorf("token无效: %s / Invalid token: %s", result.Message) } return nil } // getUserByToken 通过token获取用户信息 / Get user info by access token func getUserByToken(token string) (*verifyResponse, error) { reqBody := map[string]string{"token": token} jsonBody, _ := json.Marshal(reqBody) resp, err := http.Post(apiBaseURL+"/get-user-by-token", "application/json", bytes.NewBuffer(jsonBody)) if err != nil { return nil, fmt.Errorf("API请求失败: %v / API request failed: %v", err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result verifyResponse json.Unmarshal(body, &result) return &result, nil }

使用方法

# 已验证软件
go run main.go -type token -v your_software_token

# 未验证软件
go run main.go -type name -v your_software_name

其他语言

2026-05-23

Python示例

import requests
import webbrowser
import http.server
import socketserver
import threading
import secrets
import time

API_BASE_URL = "https://appapi.rtstudio.top"

def get_available_port():
    with socketserver.TCPServer(("localhost", 0), None) as s:
        return s.server_address[1]

def login(login_type, value):
    state = secrets.token_hex(16)
    port = get_available_port()
    
    token = None
    class Handler(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            query = parse_qs(urlparse(self.path).query)
            if query.get('type') == ['success'] and query.get('state') == [state]:
                token = query.get('v', [None])[0]
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'

登录完成

') server = socketserver.TCPServer(("localhost", port), Handler) thread = threading.Thread(target=server.serve_forever) thread.start() url = f"https://apilogin.rtstudio.top/?port={port}&type={login_type}&v={value}&state={state}&env=app" webbrowser.open(url) time.sleep(30) # 等待登录 server.shutdown() return token def get_user_by_token(token): resp = requests.post(f"{API_BASE_URL}/get-user-by-token", json={"token": token}) return resp.json()

更多SDK

更多语言的SDK正在开发中,如有需要请联系我们获取。

常见问题

2026-05-23
Q 软件Token和用户Token有什么区别?

软件Token用于标识软件身份,由热土工作室颁发;用户Token是用户登录后生成的访问令牌,用于获取用户信息。

Q 用户Token有效期是多久?

用户Token有效期为1年,过期后需重新登录授权。

Q 如何申请已验证软件?

发送邮件至 miles@rtstu.com,包含软件名称、开发者信息、功能描述等,审核周期1-14个工作日。

Q 数据段可以存储VIP状态吗?

不可以。数据段不适合存储VIP状态、付费信息等核心业务数据,因为这些数据用户可能查看或修改,会导致业务风险。VIP状态等应由您的后端独立管理。

Q state参数有什么作用?

state参数用于防止CSRF攻击,建议使用16字节以上的随机字符串,回调时验证state一致性。

联系我们

2026-05-23

成为开发者

成为开发者可获得以下权限:
  • 自主创建和管理软件Token
  • 发布Beta测试软件
  • 查看授权用户数据统计
  • 使用开发者控制台(id.rtstu.com)

软件认证申请(邮箱方式)

注意:此方式仅用于申请单个软件的已验证状态。如需自主管理多个软件、发布Beta测试等,请通过上方「成为开发者」流程获取完整权限。
邮件主题
软件认证申请 - [您的软件名称]
邮件内容需包含
  • 软件名称
  • 开发者/团队名称
  • 软件功能描述(简要)
  • 软件截图(如有)
  • 下载链接(如有)
发送邮箱

miles@rtstu.com

审核周期

1-3个工作日,审核通过后将收到包含软件Token的邮件回复。

联系方式汇总

💬

QQ群

922594184

加入群聊
📧

邮箱

miles@rtstu.com

发送邮件
🌐

开发者控制台

id.rtstu.com

访问控制台