Initial commit: 网络验证平台
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
models := []struct {
|
||||
name string
|
||||
query interface{}
|
||||
}{
|
||||
{"User", &[]model.User{}},
|
||||
{"AppUser", &[]model.AppUser{}},
|
||||
{"DynamicCode", &[]model.DynamicCode{}},
|
||||
{"Application", &[]model.Application{}},
|
||||
{"Card", &[]model.Card{}},
|
||||
{"Order", &[]model.Order{}},
|
||||
{"RechargeRecord", &[]model.RechargeRecord{}},
|
||||
{"ConsumptionRecord", &[]model.ConsumptionRecord{}},
|
||||
{"DocCategory", &[]model.DocCategory{}},
|
||||
{"Doc", &[]model.Doc{}},
|
||||
{"Setting", &[]model.Setting{}},
|
||||
}
|
||||
|
||||
for _, m := range models {
|
||||
var allCount int64
|
||||
var activeCount int64
|
||||
|
||||
database.DB.Unscoped().Model(m.query).Count(&allCount)
|
||||
database.DB.Model(m.query).Count(&activeCount)
|
||||
|
||||
deletedCount := allCount - activeCount
|
||||
|
||||
if deletedCount > 0 {
|
||||
fmt.Printf("%s: 总数=%d, 活跃=%d, 已删除=%d\n", m.name, allCount, activeCount, deletedCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
var allDynamicCodes []model.DynamicCode
|
||||
if err := database.DB.Unscoped().Find(&allDynamicCodes).Error; err != nil {
|
||||
log.Printf("查询失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("=== 所有动态代码(包括已删除) ===")
|
||||
for _, dc := range allDynamicCodes {
|
||||
deletedAt := ""
|
||||
if dc.DeletedAt.Valid {
|
||||
deletedAt = dc.DeletedAt.Time.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
fmt.Printf("ID: %d, Name: %s, Key: %s, DeletedAt: %s\n", dc.ID, dc.Name, dc.Key, deletedAt)
|
||||
}
|
||||
|
||||
var activeDynamicCodes []model.DynamicCode
|
||||
if err := database.DB.Find(&activeDynamicCodes).Error; err != nil {
|
||||
log.Printf("查询失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("\n=== 未删除的动态代码 ===")
|
||||
for _, dc := range activeDynamicCodes {
|
||||
fmt.Printf("ID: %d, Name: %s, Key: %s\n", dc.ID, dc.Name, dc.Key)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
var dynamicCodes []model.DynamicCode
|
||||
if err := database.DB.Preload("Creator").Preload("Application").Find(&dynamicCodes).Error; err != nil {
|
||||
log.Printf("查询失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("=== 动态代码列表 ===")
|
||||
for _, dc := range dynamicCodes {
|
||||
fmt.Printf("ID: %d\n", dc.ID)
|
||||
fmt.Printf(" Name: %s\n", dc.Name)
|
||||
fmt.Printf(" ApplicationID: %d\n", dc.ApplicationID)
|
||||
fmt.Printf(" UserID: %d\n", dc.UserID)
|
||||
fmt.Printf(" Application Name: %s\n", dc.Application.Name)
|
||||
if dc.Creator.ID != 0 {
|
||||
fmt.Printf(" Creator ID: %d\n", dc.Creator.ID)
|
||||
fmt.Printf(" Creator Username: %s\n", dc.Creator.Username)
|
||||
} else {
|
||||
fmt.Printf(" Creator: (empty)\n")
|
||||
}
|
||||
fmt.Printf(" Status: %s\n", dc.Status)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
db := database.DB
|
||||
|
||||
var packages []model.Package
|
||||
if err := db.Find(&packages).Error; err != nil {
|
||||
log.Fatal("Failed to get packages:", err)
|
||||
}
|
||||
|
||||
fmt.Println("=== Packages ===")
|
||||
for _, pkg := range packages {
|
||||
fmt.Printf("ID: %d, Name: %s, NameEn: %s, DescriptionEn: %s\n", pkg.ID, pkg.Name, pkg.NameEn, pkg.DescriptionEn)
|
||||
}
|
||||
|
||||
var categories []model.DocCategory
|
||||
if err := db.Find(&categories).Error; err != nil {
|
||||
log.Fatal("Failed to get categories:", err)
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Doc Categories ===")
|
||||
for _, cat := range categories {
|
||||
fmt.Printf("ID: %d, Name: %s, NameEn: %s, DescriptionEn: %s\n", cat.ID, cat.Name, cat.NameEn, cat.DescriptionEn)
|
||||
}
|
||||
|
||||
var docs []model.Doc
|
||||
if err := db.Find(&docs).Error; err != nil {
|
||||
log.Fatal("Failed to get docs:", err)
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Docs ===")
|
||||
for _, doc := range docs {
|
||||
fmt.Printf("ID: %d, Title: %s, TitleEn: %s, ContentEn: %d chars\n", doc.ID, doc.Title, doc.TitleEn, len(doc.ContentEn))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
log.Println("=== 查看套餐数据 ===")
|
||||
|
||||
var packages []model.Package
|
||||
if err := database.DB.Find(&packages).Error; err != nil {
|
||||
log.Printf("查询套餐失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
fmt.Printf("ID: %d, Name: '%s', Price: %.2f, Period: %s, Status: %s\n",
|
||||
pkg.ID, pkg.Name, pkg.Price, pkg.Period, pkg.Status)
|
||||
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", pkg.ID).First(&permission).Error; err != nil {
|
||||
fmt.Printf(" 权限: 未找到\n")
|
||||
} else {
|
||||
fmt.Printf(" 权限: MaxApps=%d, MaxStorage=%d, MaxApiCalls=%d\n",
|
||||
permission.MaxApplications, permission.MaxStorage, permission.MaxApiCalls)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,977 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
log.Println("=== 查看当前所有文档 ===")
|
||||
|
||||
var docs []model.Doc
|
||||
if err := database.DB.Find(&docs).Error; err != nil {
|
||||
log.Printf("查询文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
log.Printf("ID: %d, Slug: %s, Title: %s", doc.ID, doc.Slug, doc.Title)
|
||||
}
|
||||
|
||||
log.Println("\n=== 删除旧的云端函数文档 ===")
|
||||
|
||||
if err := database.DB.Where("slug = ?", "dynamic-code").Delete(&model.Doc{}).Error; err != nil {
|
||||
log.Printf("删除文档失败: %v", err)
|
||||
}
|
||||
|
||||
if err := database.DB.Where("slug = ?", "cloud-function").Delete(&model.Doc{}).Error; err != nil {
|
||||
log.Printf("删除文档失败: %v", err)
|
||||
}
|
||||
|
||||
log.Println("\n=== 创建云端函数文档 ===")
|
||||
|
||||
var apiCat model.DocCategory
|
||||
if err := database.DB.Where("slug = ?", "api").First(&apiCat).Error; err != nil {
|
||||
log.Printf("未找到API分类: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cloudFunctionContent := "# 云端函数接口\n\n" +
|
||||
"云端函数允许开发者为应用创建自定义的业务逻辑,通过JavaScript代码实现灵活的数据处理和业务规则。\n\n" +
|
||||
"---\n\n" +
|
||||
"## 概述\n\n" +
|
||||
"云端函数是基于JavaScript的自定义代码,可以在应用运行时动态执行。开发者可以通过云端函数实现:\n\n" +
|
||||
"- 动态定价计算\n" +
|
||||
"- 权限验证逻辑\n" +
|
||||
"- 业务规则判断\n" +
|
||||
"- 用户数据处理\n" +
|
||||
"- 自动化操作指令\n\n" +
|
||||
"---\n\n" +
|
||||
"## 获取云端函数列表\n\n" +
|
||||
"### 接口地址\n\n" +
|
||||
"`GET /api/v1/app/:appKey/cloud-function`\n\n" +
|
||||
"### 请求头\n\n" +
|
||||
"```\n" +
|
||||
"Authorization: Bearer {token}\n" +
|
||||
"```\n\n" +
|
||||
"### 路径参数\n\n" +
|
||||
"| 参数名 | 类型 | 必填 | 说明 |\n" +
|
||||
"|--------|------|------|------|\n" +
|
||||
"| appKey | string | 是 | 应用密钥 |\n\n" +
|
||||
"### 响应示例\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": [\n" +
|
||||
" {\n" +
|
||||
" \"id\": 1,\n" +
|
||||
" \"name\": \"加法函数\",\n" +
|
||||
" \"key\": \"add\",\n" +
|
||||
" \"description\": \"实现数字加法运算\",\n" +
|
||||
" \"status\": \"active\",\n" +
|
||||
" \"created_at\": \"2024-01-01T00:00:00Z\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"### 字段说明\n\n" +
|
||||
"| 字段名 | 类型 | 说明 |\n" +
|
||||
"|--------|------|------|\n" +
|
||||
"| id | number | 云端函数ID |\n" +
|
||||
"| name | string | 云端函数名称 |\n" +
|
||||
"| key | string | 云端函数唯一标识 |\n" +
|
||||
"| description | string | 描述 |\n" +
|
||||
"| status | string | 状态(active、inactive) |\n" +
|
||||
"| created_at | string | 创建时间 |\n\n" +
|
||||
"---\n\n" +
|
||||
"## 执行云端函数\n\n" +
|
||||
"### 接口地址\n\n" +
|
||||
"`POST /api/v1/app/:appKey/cloud-function/:key/execute`\n\n" +
|
||||
"### 请求头\n\n" +
|
||||
"```\n" +
|
||||
"Authorization: Bearer {token}\n" +
|
||||
"Content-Type: application/json\n" +
|
||||
"```\n\n" +
|
||||
"### 路径参数\n\n" +
|
||||
"| 参数名 | 类型 | 必填 | 说明 |\n" +
|
||||
"|--------|------|------|------|\n" +
|
||||
"| appKey | string | 是 | 应用密钥 |\n" +
|
||||
"| key | string | 是 | 云端函数标识 |\n\n" +
|
||||
"### 请求体\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"params\": {\n" +
|
||||
" \"param1\": \"value1\",\n" +
|
||||
" \"param2\": \"value2\"\n" +
|
||||
" },\n" +
|
||||
" \"user_id\": 123\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"### 参数说明\n\n" +
|
||||
"| 参数名 | 类型 | 必填 | 说明 |\n" +
|
||||
"|--------|------|------|------|\n" +
|
||||
"| params | object | 否 | 自定义参数对象,键值对形式传递 |\n" +
|
||||
"| user_id | number | 否 | 用户ID,提供后会注入当前用户信息 |\n\n" +
|
||||
"### 响应示例\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"result\": \"执行结果\",\n" +
|
||||
" \"execution_time\": 1\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"### 字段说明\n\n" +
|
||||
"| 字段名 | 类型 | 说明 |\n" +
|
||||
"|--------|------|------|\n" +
|
||||
"| result | any | 代码执行结果,可以是任意类型 |\n" +
|
||||
"| execution_time | number | 执行时间(毫秒) |\n\n" +
|
||||
"---\n\n" +
|
||||
"## 注入的数据\n\n" +
|
||||
"### 1. 应用配置数据 (app)\n\n" +
|
||||
"云端函数可以访问应用的完整配置信息:\n\n" +
|
||||
"```javascript\n" +
|
||||
"app.name\n" +
|
||||
"app.description\n" +
|
||||
"app.app_key\n" +
|
||||
"app.status\n" +
|
||||
"app.billing_type\n" +
|
||||
"app.point_price\n" +
|
||||
"app.points_per_cycle\n" +
|
||||
"app.point_deduction_cycle\n" +
|
||||
"app.enable_trial\n" +
|
||||
"app.trial_type\n" +
|
||||
"app.trial_duration\n" +
|
||||
"app.trial_points\n" +
|
||||
"app.enable_free_period\n" +
|
||||
"app.free_period_type\n" +
|
||||
"app.free_period_start\n" +
|
||||
"app.free_period_end\n" +
|
||||
"app.free_period_weekdays\n" +
|
||||
"app.free_period_start_time\n" +
|
||||
"app.free_period_end_time\n" +
|
||||
"app.max_devices\n" +
|
||||
"app.bind_type\n" +
|
||||
"app.multi_open\n" +
|
||||
"app.multi_open_mode\n" +
|
||||
"app.max_instances\n" +
|
||||
"app.login_policy\n" +
|
||||
"app.max_attempts\n" +
|
||||
"app.lock_duration\n" +
|
||||
"app.heartbeat_interval\n" +
|
||||
"app.heartbeat_timeout\n" +
|
||||
"app.change_limit\n" +
|
||||
"app.change_interval\n" +
|
||||
"app.change_exceed_action\n" +
|
||||
"app.change_deduct_amount\n" +
|
||||
"```\n\n" +
|
||||
"#### 应用配置字段说明\n\n" +
|
||||
"| 字段名 | 类型 | 说明 |\n" +
|
||||
"|--------|------|------|\n" +
|
||||
"| name | string | 应用名称 |\n" +
|
||||
"| description | string | 应用描述 |\n" +
|
||||
"| app_key | string | 应用密钥 |\n" +
|
||||
"| status | string | 应用状态 |\n" +
|
||||
"| billing_type | string | 计费方式 |\n" +
|
||||
"| point_price | number | 点数价格 |\n" +
|
||||
"| points_per_cycle | number | 每周期扣点数 |\n" +
|
||||
"| point_deduction_cycle | string | 扣点周期 |\n" +
|
||||
"| enable_trial | boolean | 是否启用试用 |\n" +
|
||||
"| trial_type | string | 试用类型 |\n" +
|
||||
"| trial_duration | number | 试用时长 |\n" +
|
||||
"| trial_points | number | 试用点数 |\n" +
|
||||
"| enable_free_period | boolean | 是否启用免费时段 |\n" +
|
||||
"| free_period_type | string | 免费时段类型 |\n" +
|
||||
"| free_period_start | string | 免费时段开始 |\n" +
|
||||
"| free_period_end | string | 免费时段结束 |\n" +
|
||||
"| free_period_weekdays | string | 免费时段星期 |\n" +
|
||||
"| free_period_start_time | string | 免费时段开始时间 |\n" +
|
||||
"| free_period_end_time | string | 免费时段结束时间 |\n" +
|
||||
"| max_devices | number | 最大设备数 |\n" +
|
||||
"| bind_type | string | 绑定方式 |\n" +
|
||||
"| multi_open | boolean | 是否允许多开 |\n" +
|
||||
"| multi_open_mode | string | 多开模式 |\n" +
|
||||
"| max_instances | number | 最大实例数 |\n" +
|
||||
"| login_policy | string | 登录策略 |\n" +
|
||||
"| max_attempts | number | 最大尝试次数 |\n" +
|
||||
"| lock_duration | number | 锁定时长 |\n" +
|
||||
"| heartbeat_interval | number | 心跳间隔 |\n" +
|
||||
"| heartbeat_timeout | number | 心跳超时 |\n" +
|
||||
"| change_limit | number | 变更限制 |\n" +
|
||||
"| change_interval | number | 变更间隔 |\n" +
|
||||
"| change_exceed_action | string | 超限操作 |\n" +
|
||||
"| change_deduct_amount | number | 超限扣点数 |\n\n" +
|
||||
"### 2. 用户信息 (user)\n\n" +
|
||||
"如果请求中提供了 `user_id`,会自动注入当前用户的信息:\n\n" +
|
||||
"```javascript\n" +
|
||||
"user.id\n" +
|
||||
"user.username\n" +
|
||||
"user.email\n" +
|
||||
"user.status\n" +
|
||||
"user.points\n" +
|
||||
"user.is_trial_user\n" +
|
||||
"user.expiry_at\n" +
|
||||
"user.last_login_at\n" +
|
||||
"```\n\n" +
|
||||
"#### 用户信息字段说明\n\n" +
|
||||
"| 字段名 | 类型 | 说明 |\n" +
|
||||
"|--------|------|------|\n" +
|
||||
"| id | number | 用户ID |\n" +
|
||||
"| username | string | 用户名 |\n" +
|
||||
"| email | string | 邮箱 |\n" +
|
||||
"| status | string | 用户状态 |\n" +
|
||||
"| points | number | 积分 |\n" +
|
||||
"| is_trial_user | boolean | 是否试用用户 |\n" +
|
||||
"| expiry_at | string | 到期时间 |\n" +
|
||||
"| last_login_at | string | 最后登录时间 |\n\n" +
|
||||
"### 3. 自定义参数\n\n" +
|
||||
"通过请求的 `params` 参数传递的自定义数据,可以直接在代码中使用:\n\n" +
|
||||
"```javascript\n" +
|
||||
"// 请求参数: {\"params\": {\"a\": 10, \"b\": 20, \"name\": \"张三\"}}\n\n" +
|
||||
"a + b\n" +
|
||||
"name.toUpperCase()\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"## JavaScript代码编写规范\n\n" +
|
||||
"### 1. 参数使用\n\n" +
|
||||
"云端函数可以直接使用请求参数中传递的变量,无需额外定义:\n\n" +
|
||||
"```javascript\n" +
|
||||
"a + b\n" +
|
||||
"```\n\n" +
|
||||
"**请求参数**:`{\"params\": {\"a\": 10, \"b\": 20}}`\n\n" +
|
||||
"**返回结果**:`30`\n\n" +
|
||||
"---\n\n" +
|
||||
"### 2. 函数定义与调用\n\n" +
|
||||
"可以定义函数并在最后一行调用:\n\n" +
|
||||
"```javascript\n" +
|
||||
"function calculate(x, y) {\n" +
|
||||
" return Math.sqrt(x * x + y * y);\n" +
|
||||
"}\n\n" +
|
||||
"calculate(x, y)\n" +
|
||||
"```\n\n" +
|
||||
"**请求参数**:`{\"params\": {\"x\": 3, \"y\": 4}}`\n\n" +
|
||||
"**返回结果**:`5`\n\n" +
|
||||
"---\n\n" +
|
||||
"### 3. 复杂逻辑\n\n" +
|
||||
"支持完整的JavaScript语法,包括控制流、循环、对象操作等:\n\n" +
|
||||
"```javascript\n" +
|
||||
"function processScore(score) {\n" +
|
||||
" if (score >= 90) {\n" +
|
||||
" return \"优秀\";\n" +
|
||||
" } else if (score >= 60) {\n" +
|
||||
" return \"及格\";\n" +
|
||||
" } else {\n" +
|
||||
" return \"不及格\";\n" +
|
||||
" }\n" +
|
||||
"}\n\n" +
|
||||
"processScore(score)\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 4. 返回值规则\n\n" +
|
||||
"- **最后一个表达式的值即为返回值**\n" +
|
||||
"- **不要在全局作用域使用return语句**\n" +
|
||||
"- 可以返回任意类型:数字、字符串、布尔值、对象、数组等\n\n" +
|
||||
"---\n\n" +
|
||||
"### 5. 使用注入数据\n\n" +
|
||||
"#### 使用应用配置\n\n" +
|
||||
"```javascript\n" +
|
||||
"function getAppInfo() {\n" +
|
||||
" return {\n" +
|
||||
" name: app.name,\n" +
|
||||
" status: app.status,\n" +
|
||||
" billing_type: app.billing_type,\n" +
|
||||
" max_devices: app.max_devices,\n" +
|
||||
" enable_trial: app.enable_trial\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"getAppInfo()\n" +
|
||||
"```\n\n" +
|
||||
"#### 使用用户信息\n\n" +
|
||||
"```javascript\n" +
|
||||
"function getUserInfo() {\n" +
|
||||
" if (!user) {\n" +
|
||||
" return { error: \"用户不存在\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" username: user.username,\n" +
|
||||
" points: user.points,\n" +
|
||||
" is_trial_user: user.is_trial_user,\n" +
|
||||
" status: user.status\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"getUserInfo()\n" +
|
||||
"```\n\n" +
|
||||
"#### 组合使用\n\n" +
|
||||
"```javascript\n" +
|
||||
"function checkUserPermission() {\n" +
|
||||
" if (!user) {\n" +
|
||||
" return { error: \"用户不存在\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.status !== \"active\") {\n" +
|
||||
" return { error: \"用户状态异常\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (app.max_devices > 0) {\n" +
|
||||
" return {\n" +
|
||||
" allowed: true,\n" +
|
||||
" max_devices: app.max_devices,\n" +
|
||||
" user_status: user.status\n" +
|
||||
" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return { allowed: false, reason: \"应用未设置设备限制\" };\n" +
|
||||
"}\n\n" +
|
||||
"checkUserPermission()\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 6. 常见示例\n\n" +
|
||||
"#### 简单计算\n\n" +
|
||||
"```javascript\n" +
|
||||
"price * quantity\n" +
|
||||
"```\n\n" +
|
||||
"#### 字符串拼接\n\n" +
|
||||
"```javascript\n" +
|
||||
"name + \"的年龄是\" + age + \"岁\"\n" +
|
||||
"```\n\n" +
|
||||
"#### 条件判断\n\n" +
|
||||
"```javascript\n" +
|
||||
"score >= 60 ? \"及格\" : \"不及格\"\n" +
|
||||
"```\n\n" +
|
||||
"#### 对象返回\n\n" +
|
||||
"```javascript\n" +
|
||||
"{\n" +
|
||||
" sum: a + b,\n" +
|
||||
" product: a * b,\n" +
|
||||
" difference: a - b\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 数组操作\n\n" +
|
||||
"```javascript\n" +
|
||||
"function sumArray(numbers) {\n" +
|
||||
" let total = 0;\n" +
|
||||
" for (let i = 0; i < numbers.length; i++) {\n" +
|
||||
" total += numbers[i];\n" +
|
||||
" }\n" +
|
||||
" return total;\n" +
|
||||
"}\n\n" +
|
||||
"sumArray([1, 2, 3, 4, 5])\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 7. 注意事项\n\n" +
|
||||
"1. **全局作用域不能使用return**:return只能在函数内部使用\n" +
|
||||
"2. **参数变量直接使用**:无需重新定义\n" +
|
||||
"3. **最后一行是返回值**:确保最后一行是需要返回的表达式\n" +
|
||||
"4. **支持ES6语法**:可以使用let、const、箭头函数等\n" +
|
||||
"5. **内置对象可用**:Math、Date、JSON等JavaScript内置对象都可以使用\n\n" +
|
||||
"---\n\n" +
|
||||
"## 操作指令\n\n" +
|
||||
"云端函数可以返回操作指令,后端会安全地执行这些指令。\n\n" +
|
||||
"### 1. 加时指令\n\n" +
|
||||
"为用户延长使用时间。\n\n" +
|
||||
"#### 指令格式\n\n" +
|
||||
"```javascript\n" +
|
||||
"{\n" +
|
||||
" action: \"extend_time\",\n" +
|
||||
" user_id: 123,\n" +
|
||||
" days: 7,\n" +
|
||||
" reason: \"用户申请延期\"\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 字段说明\n\n" +
|
||||
"| 字段名 | 类型 | 必填 | 说明 |\n" +
|
||||
"|--------|------|------|------|\n" +
|
||||
"| action | string | 是 | 固定值:\"extend_time\" |\n" +
|
||||
"| user_id | number | 是 | 用户ID |\n" +
|
||||
"| days | number | 是 | 延长天数 |\n" +
|
||||
"| reason | string | 否 | 延长原因 |\n\n" +
|
||||
"#### 示例\n\n" +
|
||||
"```javascript\n" +
|
||||
"function autoExtendTime(userId, userLevel) {\n" +
|
||||
" let days = 0;\n" +
|
||||
" \n" +
|
||||
" if (userLevel === \"vip\") {\n" +
|
||||
" days = 30;\n" +
|
||||
" } else if (userLevel === \"premium\") {\n" +
|
||||
" days = 15;\n" +
|
||||
" } else if (userLevel === \"normal\") {\n" +
|
||||
" days = 7;\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" action: \"extend_time\",\n" +
|
||||
" user_id: userId,\n" +
|
||||
" days: days,\n" +
|
||||
" reason: \"根据用户等级自动加时\"\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"autoExtendTime(123, \"vip\")\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 2. 扣点指令\n\n" +
|
||||
"扣除用户积分。\n\n" +
|
||||
"#### 指令格式\n\n" +
|
||||
"```javascript\n" +
|
||||
"{\n" +
|
||||
" action: \"deduct_points\",\n" +
|
||||
" user_id: 123,\n" +
|
||||
" points: 100,\n" +
|
||||
" reason: \"使用功能扣点\"\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 字段说明\n\n" +
|
||||
"| 字段名 | 类型 | 必填 | 说明 |\n" +
|
||||
"|--------|------|------|------|\n" +
|
||||
"| action | string | 是 | 固定值:\"deduct_points\" |\n" +
|
||||
"| user_id | number | 是 | 用户ID |\n" +
|
||||
"| points | number | 是 | 扣除点数 |\n" +
|
||||
"| reason | string | 否 | 扣点原因 |\n\n" +
|
||||
"#### 示例\n\n" +
|
||||
"```javascript\n" +
|
||||
"function autoDeductPoints(userId, usage) {\n" +
|
||||
" if (!user || user.points < usage) {\n" +
|
||||
" return { error: \"积分不足\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" action: \"deduct_points\",\n" +
|
||||
" user_id: userId,\n" +
|
||||
" points: usage,\n" +
|
||||
" reason: \"功能使用扣点\"\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"autoDeductPoints(123, 50)\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"## 完整示例\n\n" +
|
||||
"### 示例1:动态定价\n\n" +
|
||||
"#### 场景\n" +
|
||||
"根据用户等级和应用设置,动态计算价格。\n\n" +
|
||||
"#### 请求\n\n" +
|
||||
"```json\n" +
|
||||
"POST /api/v1/app/your-app-key/cloud-function/calculate-price/execute\n" +
|
||||
"{\n" +
|
||||
" \"params\": {\n" +
|
||||
" \"basePrice\": 100,\n" +
|
||||
" \"userType\": \"new\"\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 云端函数代码\n\n" +
|
||||
"```javascript\n" +
|
||||
"function calculatePrice(basePrice, userType) {\n" +
|
||||
" let finalPrice = basePrice;\n" +
|
||||
" \n" +
|
||||
" if (app.enable_trial && userType === \"new\") {\n" +
|
||||
" finalPrice = finalPrice * 0.5;\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (userType === \"vip\") {\n" +
|
||||
" finalPrice = finalPrice * 0.8;\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" original_price: basePrice,\n" +
|
||||
" user_type: userType,\n" +
|
||||
" enable_trial: app.enable_trial,\n" +
|
||||
" final_price: finalPrice,\n" +
|
||||
" discount: basePrice - finalPrice\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"calculatePrice(basePrice, userType)\n" +
|
||||
"```\n\n" +
|
||||
"#### 响应\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"original_price\": 100,\n" +
|
||||
" \"user_type\": \"new\",\n" +
|
||||
" \"enable_trial\": true,\n" +
|
||||
" \"final_price\": 50,\n" +
|
||||
" \"discount\": 50\n" +
|
||||
" },\n" +
|
||||
" \"execution_time\": 1\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 示例2:权限验证\n\n" +
|
||||
"#### 场景\n" +
|
||||
"检查用户设备数量是否超过限制。\n\n" +
|
||||
"#### 请求\n\n" +
|
||||
"```json\n" +
|
||||
"POST /api/v1/app/your-app-key/cloud-function/check-permission/execute\n" +
|
||||
"{\n" +
|
||||
" \"params\": {\n" +
|
||||
" \"deviceCount\": 2\n" +
|
||||
" },\n" +
|
||||
" \"user_id\": 123\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 云端函数代码\n\n" +
|
||||
"```javascript\n" +
|
||||
"function checkPermission(deviceCount) {\n" +
|
||||
" if (!user) {\n" +
|
||||
" return { allowed: false, reason: \"用户不存在\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.status !== \"active\") {\n" +
|
||||
" return { allowed: false, reason: \"用户状态异常\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (deviceCount > app.max_devices) {\n" +
|
||||
" return { \n" +
|
||||
" allowed: false, \n" +
|
||||
" reason: \"设备数量超过限制\",\n" +
|
||||
" current: deviceCount,\n" +
|
||||
" max: app.max_devices\n" +
|
||||
" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return { \n" +
|
||||
" allowed: true, \n" +
|
||||
" reason: \"权限验证通过\",\n" +
|
||||
" user: user.username\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"checkPermission(deviceCount)\n" +
|
||||
"```\n\n" +
|
||||
"#### 响应\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"allowed\": true,\n" +
|
||||
" \"reason\": \"权限验证通过\",\n" +
|
||||
" \"user\": \"testuser\"\n" +
|
||||
" },\n" +
|
||||
" \"execution_time\": 1\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 示例3:每日登录自动加时\n\n" +
|
||||
"#### 场景\n" +
|
||||
"试用用户每日登录自动延长1天。\n\n" +
|
||||
"#### 请求\n\n" +
|
||||
"```json\n" +
|
||||
"POST /api/v1/app/your-app-key/cloud-function/daily-login/execute\n" +
|
||||
"{\n" +
|
||||
" \"params\": {\n" +
|
||||
" \"userId\": 123\n" +
|
||||
" },\n" +
|
||||
" \"user_id\": 123\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 云端函数代码\n\n" +
|
||||
"```javascript\n" +
|
||||
"function processDailyLogin(userId) {\n" +
|
||||
" if (!user) {\n" +
|
||||
" return { error: \"用户不存在\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.status !== \"active\") {\n" +
|
||||
" return { error: \"用户状态异常\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.is_trial_user) {\n" +
|
||||
" return {\n" +
|
||||
" action: \"extend_time\",\n" +
|
||||
" user_id: userId,\n" +
|
||||
" days: 1,\n" +
|
||||
" reason: \"试用用户每日登录加时\",\n" +
|
||||
" timestamp: new Date().toISOString()\n" +
|
||||
" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" message: \"登录成功\",\n" +
|
||||
" user_points: user.points,\n" +
|
||||
" app_name: app.name\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"processDailyLogin(userId)\n" +
|
||||
"```\n\n" +
|
||||
"#### 响应\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"action\": \"extend_time\",\n" +
|
||||
" \"user_id\": 123,\n" +
|
||||
" \"days\": 1,\n" +
|
||||
" \"reason\": \"试用用户每日登录加时\",\n" +
|
||||
" \"timestamp\": \"2026-03-11T00:57:55.123Z\"\n" +
|
||||
" },\n" +
|
||||
" \"execution_time\": 2\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 示例4:功能使用自动扣点\n\n" +
|
||||
"#### 场景\n" +
|
||||
"用户使用功能时自动扣除相应积分。\n\n" +
|
||||
"#### 请求\n\n" +
|
||||
"```json\n" +
|
||||
"POST /api/v1/app/your-app-key/cloud-function/use-feature/execute\n" +
|
||||
"{\n" +
|
||||
" \"params\": {\n" +
|
||||
" \"userId\": 123,\n" +
|
||||
" \"featureType\": \"advanced\"\n" +
|
||||
" },\n" +
|
||||
" \"user_id\": 123\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 云端函数代码\n\n" +
|
||||
"```javascript\n" +
|
||||
"function processFeatureUsage(userId, featureType) {\n" +
|
||||
" if (!user) {\n" +
|
||||
" return { error: \"用户不存在\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.status !== \"active\") {\n" +
|
||||
" return { error: \"用户状态异常\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" let cost = app.points_per_cycle;\n" +
|
||||
" \n" +
|
||||
" if (featureType === \"advanced\") {\n" +
|
||||
" cost = cost * 2;\n" +
|
||||
" } else if (featureType === \"premium\") {\n" +
|
||||
" cost = cost * 5;\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.points < cost) {\n" +
|
||||
" return { \n" +
|
||||
" error: \"积分不足\",\n" +
|
||||
" required: cost,\n" +
|
||||
" current: user.points\n" +
|
||||
" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" action: \"deduct_points\",\n" +
|
||||
" user_id: userId,\n" +
|
||||
" points: cost,\n" +
|
||||
" reason: \"使用功能扣点: \" + featureType,\n" +
|
||||
" feature_type: featureType\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"processFeatureUsage(userId, featureType)\n" +
|
||||
"```\n\n" +
|
||||
"#### 响应\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"action\": \"deduct_points\",\n" +
|
||||
" \"user_id\": 123,\n" +
|
||||
" \"points\": 2,\n" +
|
||||
" \"reason\": \"使用功能扣点: advanced\",\n" +
|
||||
" \"feature_type\": \"advanced\"\n" +
|
||||
" },\n" +
|
||||
" \"execution_time\": 1\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 示例5:业务规则获取\n\n" +
|
||||
"#### 场景\n" +
|
||||
"获取应用的所有业务规则,用于前端展示。\n\n" +
|
||||
"#### 请求\n\n" +
|
||||
"```json\n" +
|
||||
"POST /api/v1/app/your-app-key/cloud-function/get-rules/execute\n" +
|
||||
"{\n" +
|
||||
" \"params\": {}\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"#### 云端函数代码\n\n" +
|
||||
"```javascript\n" +
|
||||
"function getBusinessRules() {\n" +
|
||||
" const rules = [];\n" +
|
||||
" \n" +
|
||||
" if (app.enable_trial) {\n" +
|
||||
" rules.push({\n" +
|
||||
" type: \"trial\",\n" +
|
||||
" title: \"试用功能\",\n" +
|
||||
" description: \"支持试用,试用时长:\" + app.trial_duration + \"天\"\n" +
|
||||
" });\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (app.max_devices > 1) {\n" +
|
||||
" rules.push({\n" +
|
||||
" type: \"multi_device\",\n" +
|
||||
" title: \"多设备支持\",\n" +
|
||||
" description: \"支持多设备,最多\" + app.max_devices + \"台\"\n" +
|
||||
" });\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (app.enable_free_period) {\n" +
|
||||
" rules.push({\n" +
|
||||
" type: \"free_period\",\n" +
|
||||
" title: \"免费时段\",\n" +
|
||||
" description: \"支持免费时段:\" + app.free_period_start + \" - \" + app.free_period_end\n" +
|
||||
" });\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (app.point_price > 0) {\n" +
|
||||
" rules.push({\n" +
|
||||
" type: \"points\",\n" +
|
||||
" title: \"积分系统\",\n" +
|
||||
" description: \"点数价格:\" + app.point_price + \",每周期扣点:\" + app.points_per_cycle\n" +
|
||||
" });\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" app_name: app.name,\n" +
|
||||
" total_rules: rules.length,\n" +
|
||||
" rules: rules\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"getBusinessRules()\n" +
|
||||
"```\n\n" +
|
||||
"#### 响应\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 200,\n" +
|
||||
" \"message\": \"操作成功\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"app_name\": \"我的应用\",\n" +
|
||||
" \"total_rules\": 4,\n" +
|
||||
" \"rules\": [\n" +
|
||||
" {\n" +
|
||||
" \"type\": \"trial\",\n" +
|
||||
" \"title\": \"试用功能\",\n" +
|
||||
" \"description\": \"支持试用,试用时长:7天\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"type\": \"multi_device\",\n" +
|
||||
" \"title\": \"多设备支持\",\n" +
|
||||
" \"description\": \"支持多设备,最多3台\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"type\": \"free_period\",\n" +
|
||||
" \"title\": \"免费时段\",\n" +
|
||||
" \"description\": \"支持免费时段:20:00 - 08:00\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"type\": \"points\",\n" +
|
||||
" \"title\": \"积分系统\",\n" +
|
||||
" \"description\": \"点数价格:0.1,每周期扣点:1\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" },\n" +
|
||||
" \"execution_time\": 1\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"## 错误处理\n\n" +
|
||||
"### 常见错误\n\n" +
|
||||
"#### 1. 语法错误\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 400,\n" +
|
||||
" \"message\": \"代码执行错误: SyntaxError: Unexpected token\"\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"**原因**:JavaScript代码语法错误\n\n" +
|
||||
"**解决**:检查代码语法,确保括号、引号等匹配正确\n\n" +
|
||||
"---\n\n" +
|
||||
"#### 2. 运行时错误\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 400,\n" +
|
||||
" \"message\": \"代码执行错误: ReferenceError: x is not defined\"\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"**原因**:使用了未定义的变量\n\n" +
|
||||
"**解决**:检查变量名是否正确,或确保已通过参数传入\n\n" +
|
||||
"---\n\n" +
|
||||
"#### 3. 全局return错误\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 400,\n" +
|
||||
" \"message\": \"代码执行错误: SyntaxError: Illegal return statement\"\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"**原因**:在全局作用域使用了return语句\n\n" +
|
||||
"**解决**:将return语句放在函数内部,或直接返回表达式\n\n" +
|
||||
"---\n\n" +
|
||||
"#### 4. 操作执行失败\n\n" +
|
||||
"```json\n" +
|
||||
"{\n" +
|
||||
" \"code\": 500,\n" +
|
||||
" \"message\": \"执行加时操作失败: user not found\"\n" +
|
||||
"}\n" +
|
||||
"```\n\n" +
|
||||
"**原因**:操作指令中的用户不存在\n\n" +
|
||||
"**解决**:检查user_id是否正确,或用户是否属于当前应用\n\n" +
|
||||
"---\n\n" +
|
||||
"## 性能建议\n\n" +
|
||||
"1. **避免死循环**:确保循环有明确的退出条件\n" +
|
||||
"2. **合理使用缓存**:对于重复计算,可以考虑缓存结果\n" +
|
||||
"3. **控制代码复杂度**:过复杂的代码可能影响执行效率\n" +
|
||||
"4. **使用内置函数**:优先使用JavaScript内置函数,性能更好\n" +
|
||||
"5. **避免频繁操作**:操作指令会修改数据库,避免频繁调用\n\n" +
|
||||
"---\n\n" +
|
||||
"## 安全建议\n\n" +
|
||||
"1. **不要执行危险操作**:避免访问文件系统、网络请求等\n" +
|
||||
"2. **参数验证**:在代码中对参数进行必要的验证\n" +
|
||||
"3. **避免敏感信息**:不要在代码中硬编码密钥、密码等敏感信息\n" +
|
||||
"4. **限制执行时间**:设置合理的超时时间,防止长时间运行\n" +
|
||||
"5. **操作指令谨慎**:操作指令会修改数据库,确保逻辑正确\n" +
|
||||
"6. **用户数据保护**:不要在代码中泄露用户敏感信息\n\n" +
|
||||
"---\n\n" +
|
||||
"## 最佳实践\n\n" +
|
||||
"### 1. 代码组织\n\n" +
|
||||
"将复杂逻辑封装成函数,提高代码可读性:\n\n" +
|
||||
"```javascript\n" +
|
||||
"function validateUser() {\n" +
|
||||
" if (!user) {\n" +
|
||||
" return { valid: false, reason: \"用户不存在\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" if (user.status !== \"active\") {\n" +
|
||||
" return { valid: false, reason: \"用户状态异常\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" return { valid: true };\n" +
|
||||
"}\n\n" +
|
||||
"function calculatePrice(basePrice, discount) {\n" +
|
||||
" return basePrice * (1 - discount / 100);\n" +
|
||||
"}\n\n" +
|
||||
"function mainProcess() {\n" +
|
||||
" const validation = validateUser();\n" +
|
||||
" if (!validation.valid) {\n" +
|
||||
" return validation;\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" const price = calculatePrice(100, 20);\n" +
|
||||
" return { success: true, price: price };\n" +
|
||||
"}\n\n" +
|
||||
"mainProcess()\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 2. 错误处理\n\n" +
|
||||
"在代码中添加适当的错误处理:\n\n" +
|
||||
"```javascript\n" +
|
||||
"function safeCalculate(a, b) {\n" +
|
||||
" if (typeof a !== \"number\" || typeof b !== \"number\") {\n" +
|
||||
" return { error: \"参数必须是数字\" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" try {\n" +
|
||||
" return { result: a + b };\n" +
|
||||
" } catch (e) {\n" +
|
||||
" return { error: \"计算失败: \" + e.message };\n" +
|
||||
" }\n" +
|
||||
"}\n\n" +
|
||||
"safeCalculate(a, b)\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 3. 使用现代JavaScript\n\n" +
|
||||
"利用ES6+特性提高代码质量:\n\n" +
|
||||
"```javascript\n" +
|
||||
"// 使用箭头函数\n" +
|
||||
"const calculate = (x, y) => x + y;\n\n" +
|
||||
"// 使用解构赋值\n" +
|
||||
"const { name, age } = userInfo;\n\n" +
|
||||
"// 使用模板字符串\n" +
|
||||
"const message = `用户${name}的年龄是${age}岁`;\n\n" +
|
||||
"// 使用数组方法\n" +
|
||||
"const sum = numbers.reduce((a, b) => a + b, 0);\n\n" +
|
||||
"// 使用对象简写\n" +
|
||||
"const result = { name, age, status };\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"### 4. 日志记录\n\n" +
|
||||
"在返回结果中包含调试信息:\n\n" +
|
||||
"```javascript\n" +
|
||||
"function processWithLog(userId) {\n" +
|
||||
" const startTime = new Date();\n" +
|
||||
" \n" +
|
||||
" if (!user) {\n" +
|
||||
" return { \n" +
|
||||
" error: \"用户不存在\",\n" +
|
||||
" timestamp: startTime.toISOString(),\n" +
|
||||
" debug: { userId }\n" +
|
||||
" };\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" const result = {\n" +
|
||||
" user_id: userId,\n" +
|
||||
" username: user.username,\n" +
|
||||
" status: user.status\n" +
|
||||
" };\n" +
|
||||
" \n" +
|
||||
" return {\n" +
|
||||
" success: true,\n" +
|
||||
" data: result,\n" +
|
||||
" timestamp: startTime.toISOString(),\n" +
|
||||
" execution_time: new Date() - startTime\n" +
|
||||
" };\n" +
|
||||
"}\n\n" +
|
||||
"processWithLog(userId)\n" +
|
||||
"```\n\n" +
|
||||
"---\n\n" +
|
||||
"## 常见问题\n\n" +
|
||||
"### Q1: 云端函数可以访问数据库吗?\n\n" +
|
||||
"**A**: 不能直接访问数据库。云端函数只能使用注入的数据(app、user)和请求参数。如果需要修改数据,应该返回操作指令(extend_time、deduct_points),由后端安全地执行。\n\n" +
|
||||
"---\n\n" +
|
||||
"### Q2: 云端函数可以调用外部API吗?\n\n" +
|
||||
"**A**: 不能。云端函数在沙箱环境中执行,不支持网络请求。如果需要调用外部API,应该通过后端接口实现。\n\n" +
|
||||
"---\n\n" +
|
||||
"### Q3: 云端函数的执行时间有限制吗?\n\n" +
|
||||
"**A**: 有。云端函数有执行时间限制,超时会被终止。建议避免死循环和长时间运行的操作。\n\n" +
|
||||
"---\n\n" +
|
||||
"### Q4: 如何调试云端函数?\n\n" +
|
||||
"**A**: 可以在返回结果中包含调试信息,或者使用简单的测试参数验证逻辑是否正确。\n\n" +
|
||||
"---\n\n" +
|
||||
"### Q5: 云端函数可以修改应用配置吗?\n\n" +
|
||||
"**A**: 不能。app对象是只读的,不能修改。如果需要修改应用配置,应该使用专门的管理接口。\n\n" +
|
||||
"---\n\n" +
|
||||
"## 更新日志\n\n" +
|
||||
"### v1.0.0 (2026-03-11)\n" +
|
||||
"- 初始版本发布\n" +
|
||||
"- 支持应用配置数据注入\n" +
|
||||
"- 支持用户信息注入\n" +
|
||||
"- 支持操作指令(加时、扣点)\n" +
|
||||
"- 支持完整的JavaScript语法\n" +
|
||||
"- 提供丰富的使用示例\n"
|
||||
|
||||
cloudFunctionDoc := model.Doc{
|
||||
Title: "云端函数接口",
|
||||
CategoryID: &apiCat.ID,
|
||||
Slug: "cloud-function",
|
||||
Content: cloudFunctionContent,
|
||||
Summary: "云端函数允许开发者为应用创建自定义的业务逻辑,通过JavaScript代码实现灵活的数据处理和业务规则",
|
||||
Icon: "⚡",
|
||||
Sort: 1,
|
||||
Status: "published",
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&cloudFunctionDoc).Error; err != nil {
|
||||
log.Printf("创建云端函数文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("云端函数文档创建成功")
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
docContent := `# 手动扣费 API
|
||||
|
||||
## 概述
|
||||
|
||||
手动扣费功能允许开发者在余额模式下,通过 API 自行控制扣费时机和金额。这为开发者提供了最大的灵活性,可以根据业务需求实现自定义的计费逻辑。
|
||||
|
||||
## 前提条件
|
||||
|
||||
1. 应用的运营模式必须设置为**余额模式**
|
||||
2. 扣费方式必须设置为**手动扣费**
|
||||
|
||||
## 扣费方式说明
|
||||
|
||||
扣费方式分为两级选择:
|
||||
|
||||
### 第一级:扣费方式
|
||||
|
||||
| 方式 | 说明 |
|
||||
|------|------|
|
||||
| 自动扣费 | 系统自动触发扣费 |
|
||||
| 手动扣费 | 通过 API 自行控制扣费 |
|
||||
|
||||
### 第二级:自动扣费类型(仅自动扣费时显示)
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| 登录扣费 | 每次登录验证时扣费 |
|
||||
| 计时扣费 | 按设定的时间间隔自动扣费 |
|
||||
|
||||
### 计时扣费配置
|
||||
|
||||
选择计时扣费后,可自定义扣费间隔:
|
||||
|
||||
- **间隔值**:1-9999 之间的整数
|
||||
- **时间单位**:分钟、小时、天
|
||||
|
||||
例如:设置为 "每 30 分钟扣费一次" 或 "每 2 小时扣费一次"
|
||||
|
||||
## 接口详情
|
||||
|
||||
### 请求地址
|
||||
|
||||
POST /api/v1/dev/applications/:id/deduct
|
||||
|
||||
### 请求头
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| Authorization | string | 是 | Bearer {token},开发者登录后获取的令牌 |
|
||||
| Content-Type | string | 是 | application/json |
|
||||
|
||||
### 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | integer | 是 | 应用ID |
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| user_id | integer | 是 | 要扣费的用户ID |
|
||||
| amount | number | 是 | 扣费金额,必须大于0 |
|
||||
| description | string | 否 | 扣费描述/原因 |
|
||||
|
||||
### 请求示例
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"user_id": 123,
|
||||
"amount": 10.5,
|
||||
"description": "使用高级功能扣费"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
### 响应示例
|
||||
|
||||
#### 成功响应
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"user_id": 123,
|
||||
"username": "testuser",
|
||||
"amount": 10.5,
|
||||
"balance_before": 100.5,
|
||||
"balance_after": 90.0,
|
||||
"description": "使用高级功能扣费",
|
||||
"message": "扣费成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
#### 错误响应
|
||||
|
||||
**应用不存在**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 404,
|
||||
"message": "应用不存在"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**非余额模式**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 400,
|
||||
"message": "只有余额模式的应用才支持手动扣费"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**未开启手动扣费**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 400,
|
||||
"message": "该应用未开启手动扣费模式,请先在设置中修改扣费频率为手动扣费"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**用户不存在**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 404,
|
||||
"message": "用户不存在"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**余额不足**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 400,
|
||||
"message": "用户余额不足,当前余额: 5.00,需扣除: 10.50"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**永久会员**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 400,
|
||||
"message": "该用户为永久会员,无法扣费"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 按次计费
|
||||
|
||||
用户每次使用特定功能时扣费:
|
||||
|
||||
` + "```javascript" + `
|
||||
// 用户使用高级分析功能
|
||||
async function useAdvancedAnalysis(userId) {
|
||||
const response = await fetch('/api/v1/dev/applications/1/deduct', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: userId,
|
||||
amount: 5.0,
|
||||
description: '使用高级分析功能'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.code === 200) {
|
||||
// 扣费成功,执行功能
|
||||
return true;
|
||||
} else {
|
||||
// 扣费失败,提示用户
|
||||
alert(result.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
### 2. 阶梯计费
|
||||
|
||||
根据使用量阶梯定价:
|
||||
|
||||
` + "```javascript" + `
|
||||
function calculatePrice(usage) {
|
||||
if (usage <= 100) {
|
||||
return 1.0; // 前100次,每次1元
|
||||
} else if (usage <= 500) {
|
||||
return 0.8; // 101-500次,每次0.8元
|
||||
} else {
|
||||
return 0.5; // 500次以上,每次0.5元
|
||||
}
|
||||
}
|
||||
|
||||
async function chargeUser(userId, currentUsage) {
|
||||
const price = calculatePrice(currentUsage);
|
||||
// 调用扣费API...
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
### 3. 动态定价
|
||||
|
||||
根据时间段或活动动态调整价格:
|
||||
|
||||
` + "```javascript" + `
|
||||
function getDynamicPrice() {
|
||||
const hour = new Date().getHours();
|
||||
const dayOfWeek = new Date().getDay();
|
||||
|
||||
// 周末打折
|
||||
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
||||
return 5.0 * 0.8;
|
||||
}
|
||||
|
||||
// 深夜时段打折
|
||||
if (hour >= 22 || hour < 6) {
|
||||
return 5.0 * 0.5;
|
||||
}
|
||||
|
||||
return 5.0;
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
### 4. 批量扣费
|
||||
|
||||
对多个用户批量扣费:
|
||||
|
||||
` + "```javascript" + `
|
||||
async function batchDeduct(users) {
|
||||
const results = [];
|
||||
|
||||
for (const user of users) {
|
||||
try {
|
||||
const response = await fetch('/api/v1/dev/applications/1/deduct', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: user.id,
|
||||
amount: user.amount,
|
||||
description: user.description
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
results.push({
|
||||
user_id: user.id,
|
||||
success: result.code === 200,
|
||||
message: result.message
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
user_id: user.id,
|
||||
success: false,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **余额检查**: 调用扣费接口前,建议先检查用户余额是否充足
|
||||
2. **幂等性**: 如果业务需要保证幂等性,请在业务层实现去重逻辑
|
||||
3. **并发控制**: 高并发场景下,建议使用锁机制防止余额扣成负数
|
||||
4. **记录保存**: 每次扣费都会自动创建消费记录,可在后台查看
|
||||
5. **错误处理**: 请妥善处理各种错误情况,给用户友好的提示
|
||||
|
||||
## 与自动扣费的区别
|
||||
|
||||
| 特性 | 手动扣费 | 自动扣费 |
|
||||
|------|----------|----------|
|
||||
| 扣费时机 | 开发者自行控制 | 系统自动执行 |
|
||||
| 扣费金额 | 每次可不同 | 固定金额 |
|
||||
| 灵活性 | 高 | 低 |
|
||||
| 实现复杂度 | 需要开发者实现 | 无需开发 |
|
||||
| 适用场景 | 复杂计费逻辑 | 简单按时/按次计费 |
|
||||
|
||||
## 相关接口
|
||||
|
||||
- [获取用户信息](/docs/app-api-docs#获取账户信息) - 查询用户当前余额
|
||||
- [用户充值](/docs/app-api-docs#卡密充值) - 用户通过卡密充值
|
||||
- [消费记录](/docs/finance) - 查看扣费记录`
|
||||
|
||||
var devCategory model.DocCategory
|
||||
if err := database.DB.Where("slug = ?", "dev-docs").First(&devCategory).Error; err != nil {
|
||||
devCategory = model.DocCategory{
|
||||
Name: "开发者文档",
|
||||
Slug: "dev-docs",
|
||||
Description: "开发者API接口文档",
|
||||
Sort: 90,
|
||||
}
|
||||
if err := database.DB.Create(&devCategory).Error; err != nil {
|
||||
log.Printf("创建开发者文档分类失败: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var existingDoc model.Doc
|
||||
if err := database.DB.Where("slug = ?", "manual-deduct-api").First(&existingDoc).Error; err == nil {
|
||||
existingDoc.Content = docContent
|
||||
if err := database.DB.Save(&existingDoc).Error; err != nil {
|
||||
log.Printf("更新手动扣费文档失败: %v", err)
|
||||
} else {
|
||||
log.Println("手动扣费文档更新成功")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
doc := model.Doc{
|
||||
Title: "手动扣费 API",
|
||||
CategoryID: &devCategory.ID,
|
||||
Slug: "manual-deduct-api",
|
||||
Content: docContent,
|
||||
Summary: "余额模式下通过API自行控制扣费时机和金额,支持按次计费、阶梯计费、动态定价等场景",
|
||||
Icon: "💳",
|
||||
Sort: 10,
|
||||
Status: "published",
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&doc).Error; err != nil {
|
||||
log.Printf("创建手动扣费文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("手动扣费文档创建成功")
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
log.Println("=== 修复套餐数据 ===")
|
||||
|
||||
var pkg model.Package
|
||||
if err := database.DB.First(&pkg, 1).Error; err != nil {
|
||||
log.Printf("查询套餐失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("修复前: ID=%d, Name='%s', Price=%.2f, Period='%s', Status='%s'\n",
|
||||
pkg.ID, pkg.Name, pkg.Price, pkg.Period, pkg.Status)
|
||||
|
||||
if pkg.Name == "" {
|
||||
pkg.Name = "免费版"
|
||||
pkg.Price = 0
|
||||
pkg.Period = "permanent"
|
||||
pkg.Status = "active"
|
||||
pkg.Description = "免费体验版本,功能有限"
|
||||
pkg.Sort = 0
|
||||
|
||||
if err := database.DB.Save(&pkg).Error; err != nil {
|
||||
log.Printf("更新套餐失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("修复后: ID=%d, Name='%s', Price=%.2f, Period='%s', Status='%s'\n",
|
||||
pkg.ID, pkg.Name, pkg.Price, pkg.Period, pkg.Status)
|
||||
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", pkg.ID).First(&permission).Error; err != nil {
|
||||
log.Printf("查询权限失败: %v\n", err)
|
||||
} else {
|
||||
permission.MaxApplications = 1
|
||||
permission.MaxStorage = 100
|
||||
permission.MaxApiCalls = 1000
|
||||
permission.AllowAgent = false
|
||||
permission.AllowCloudData = false
|
||||
permission.AllowDynamicCode = false
|
||||
permission.PrioritySupport = false
|
||||
|
||||
if err := database.DB.Save(&permission).Error; err != nil {
|
||||
log.Printf("更新权限失败: %v\n", err)
|
||||
} else {
|
||||
log.Printf("权限已更新: MaxApps=%d, MaxStorage=%d, MaxApiCalls=%d\n",
|
||||
permission.MaxApplications, permission.MaxStorage, permission.MaxApiCalls)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Println("套餐名称不为空,无需修复")
|
||||
}
|
||||
|
||||
log.Println("套餐数据修复完成")
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
log.Println("=== 初始化套餐数据 ===")
|
||||
|
||||
var count int64
|
||||
database.DB.Model(&model.Package{}).Count(&count)
|
||||
log.Printf("当前套餐数量: %d\n", count)
|
||||
|
||||
if count > 0 {
|
||||
log.Println("套餐数据已存在,跳过初始化")
|
||||
return
|
||||
}
|
||||
|
||||
packages := []model.Package{
|
||||
{
|
||||
Name: "基础版",
|
||||
Description: "适合个人开发者和小型项目",
|
||||
Price: 9.9,
|
||||
Period: "monthly",
|
||||
Status: "active",
|
||||
Sort: 1,
|
||||
IsRecommended: false,
|
||||
AllowUpgrade: true,
|
||||
},
|
||||
{
|
||||
Name: "专业版",
|
||||
Description: "适合中小型企业和成长型项目",
|
||||
Price: 29.9,
|
||||
Period: "monthly",
|
||||
Status: "active",
|
||||
Sort: 2,
|
||||
IsRecommended: true,
|
||||
AllowUpgrade: true,
|
||||
},
|
||||
{
|
||||
Name: "企业版",
|
||||
Description: "适合大型企业和高流量项目",
|
||||
Price: 99.9,
|
||||
Period: "monthly",
|
||||
Status: "active",
|
||||
Sort: 3,
|
||||
IsRecommended: false,
|
||||
AllowUpgrade: true,
|
||||
},
|
||||
{
|
||||
Name: "永久版",
|
||||
Description: "一次性购买,永久使用",
|
||||
Price: 999,
|
||||
Period: "permanent",
|
||||
Status: "active",
|
||||
Sort: 4,
|
||||
IsRecommended: false,
|
||||
AllowUpgrade: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if err := database.DB.Create(&pkg).Error; err != nil {
|
||||
log.Printf("创建套餐失败: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
permission := model.PackagePermission{
|
||||
PackageID: pkg.ID,
|
||||
MaxApplications: 5,
|
||||
MaxStorage: 1024,
|
||||
MaxApiCalls: 10000,
|
||||
AllowAgent: false,
|
||||
AllowCloudData: true,
|
||||
AllowDynamicCode: false,
|
||||
PrioritySupport: false,
|
||||
}
|
||||
|
||||
if pkg.Name == "专业版" {
|
||||
permission.MaxApplications = 20
|
||||
permission.MaxStorage = 5120
|
||||
permission.MaxApiCalls = 100000
|
||||
permission.AllowAgent = true
|
||||
permission.AllowDynamicCode = true
|
||||
} else if pkg.Name == "企业版" {
|
||||
permission.MaxApplications = -1
|
||||
permission.MaxStorage = -1
|
||||
permission.MaxApiCalls = -1
|
||||
permission.AllowAgent = true
|
||||
permission.AllowDynamicCode = true
|
||||
permission.PrioritySupport = true
|
||||
} else if pkg.Name == "永久版" {
|
||||
permission.MaxApplications = -1
|
||||
permission.MaxStorage = -1
|
||||
permission.MaxApiCalls = -1
|
||||
permission.AllowAgent = true
|
||||
permission.AllowDynamicCode = true
|
||||
permission.PrioritySupport = true
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&permission).Error; err != nil {
|
||||
log.Printf("创建套餐权限失败: %v\n", err)
|
||||
} else {
|
||||
log.Printf("创建套餐成功: %s (ID: %d)\n", pkg.Name, pkg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("套餐初始化完成")
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
log.Println("=== 查看当前所有文档 ===")
|
||||
|
||||
var docs []model.Doc
|
||||
if err := database.DB.Find(&docs).Error; err != nil {
|
||||
log.Printf("查询文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
log.Printf("ID: %d, Slug: %s, Title: %s", doc.ID, doc.Slug, doc.Title)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
db := database.DB
|
||||
|
||||
var categories []model.DocCategory
|
||||
if err := db.Find(&categories).Error; err != nil {
|
||||
log.Fatal("获取分类失败:", err)
|
||||
}
|
||||
|
||||
for _, cat := range categories {
|
||||
if cat.NameEn == "" && cat.Name != "" {
|
||||
cat.NameEn = cat.Name
|
||||
}
|
||||
if cat.DescriptionEn == "" && cat.Description != "" {
|
||||
cat.DescriptionEn = cat.Description
|
||||
}
|
||||
if err := db.Save(&cat).Error; err != nil {
|
||||
log.Printf("更新分类 %d 失败: %v", cat.ID, err)
|
||||
} else {
|
||||
fmt.Printf("已更新分类: %s\n", cat.Name)
|
||||
}
|
||||
}
|
||||
|
||||
var docs []model.Doc
|
||||
if err := db.Find(&docs).Error; err != nil {
|
||||
log.Fatal("获取文档失败:", err)
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
if doc.TitleEn == "" && doc.Title != "" {
|
||||
doc.TitleEn = doc.Title
|
||||
}
|
||||
if doc.ContentEn == "" && doc.Content != "" {
|
||||
doc.ContentEn = doc.Content
|
||||
}
|
||||
if err := db.Save(&doc).Error; err != nil {
|
||||
log.Printf("更新文档 %d 失败: %v", doc.ID, err)
|
||||
} else {
|
||||
fmt.Printf("已更新文档: %s\n", doc.Title)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n迁移完成!")
|
||||
fmt.Printf("更新了 %d 个分类\n", len(categories))
|
||||
fmt.Printf("更新了 %d 个文档\n", len(docs))
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
db := database.DB
|
||||
|
||||
categoryTranslations := map[string]struct {
|
||||
NameEn string
|
||||
DescriptionEn string
|
||||
}{
|
||||
"快速开始": {NameEn: "Quick Start", DescriptionEn: "Complete integration in 5 minutes"},
|
||||
"API文档": {NameEn: "API Documentation", DescriptionEn: "Complete API reference"},
|
||||
"示例代码": {NameEn: "Code Examples", DescriptionEn: "Common use case examples"},
|
||||
"常见问题": {NameEn: "FAQ", DescriptionEn: "Frequently asked questions"},
|
||||
}
|
||||
|
||||
var categories []model.DocCategory
|
||||
if err := db.Find(&categories).Error; err != nil {
|
||||
log.Fatal("Failed to get categories:", err)
|
||||
}
|
||||
|
||||
for _, cat := range categories {
|
||||
if trans, ok := categoryTranslations[cat.Name]; ok {
|
||||
cat.NameEn = trans.NameEn
|
||||
cat.DescriptionEn = trans.DescriptionEn
|
||||
if err := db.Save(&cat).Error; err != nil {
|
||||
log.Printf("Failed to update category %d: %v", cat.ID, err)
|
||||
} else {
|
||||
fmt.Printf("Translated category: %s -> %s\n", cat.Name, cat.NameEn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
docTranslations := map[string]struct {
|
||||
TitleEn string
|
||||
ContentEn string
|
||||
}{
|
||||
"用户注册": {
|
||||
TitleEn: "User Registration",
|
||||
ContentEn: "# User Registration\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/register\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| username | string | Yes | Username |\n| password | string | Yes | Password |\n| device_id | string | Yes | Device fingerprint, unique device identifier |\n| device_name | string | No | Device name, e.g. \"My Computer\", defaults to device_id |\n| email | string | Conditional | Email address, required when email verification is enabled |\n| email_code | string | Conditional | Email verification code, required when mandatory email verification is enabled |\n| device_type | string | No | Device type: android/ios/windows/mac/linux/web, auto-detected if not provided |\n| instance_id | string | No | Instance ID for multi-instance detection, defaults to device_id |\n\n### Request Example\n```json\n{\n \"username\": \"user123\",\n \"password\": \"password123\",\n \"device_id\": \"abc123def456\",\n \"device_name\": \"My Computer\",\n \"email\": \"user@example.com\",\n \"email_code\": \"123456\",\n \"device_type\": \"windows\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Registration successful\",\n \"data\": {\n \"user_id\": 123\n }\n}\n```\n\n### Notes\n- device_id is the device fingerprint used to uniquely identify a device\n- device_name is a friendly name for the device for display purposes\n- device_type is auto-detected based on User-Agent if not provided\n- If mandatory email verification is enabled for the application, email and email_code are required",
|
||||
},
|
||||
"发送邮箱验证码": {
|
||||
TitleEn: "Send Email Verification Code",
|
||||
ContentEn: "# Send Email Verification Code\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/send-email-code\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| email | string | Yes | Email address |\n| purpose | string | No | Purpose: register, reset_password, change_email, defaults to register |\n\n### Request Example\n```json\n{\n \"email\": \"user@example.com\",\n \"purpose\": \"register\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"success\",\n \"data\": {\n \"message\": \"Verification code sent\"\n }\n}\n```\n\n### Notes\n- Verification code is valid for 15 minutes\n- Returns an error if email verification is not enabled for the application",
|
||||
},
|
||||
"用户登录": {
|
||||
TitleEn: "User Login",
|
||||
ContentEn: "# User Login\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/login\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| username | string | Yes | Username |\n| password | string | Yes | Password |\n| device_id | string | Yes | Device fingerprint, unique device identifier |\n| device_name | string | No | Device name, e.g. \"My Computer\", defaults to device_id |\n| device_type | string | No | Device type: android/ios/windows/mac/linux/web, auto-detected if not provided |\n| instance_id | string | No | Instance ID for multi-instance detection, defaults to device_id |\n\n### Request Example\n```json\n{\n \"username\": \"user123\",\n \"password\": \"password123\",\n \"device_id\": \"abc123def456\",\n \"device_name\": \"My Computer\",\n \"device_type\": \"windows\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Login successful\",\n \"data\": {\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"user_id\": 123\n }\n}\n```\n\n### Notes\n- device_id is the device fingerprint used to uniquely identify a device\n- device_name is a friendly name for the device for display purposes\n- device_type is auto-detected based on User-Agent if not provided\n- Returns an error with bound device list if device binding limit is reached",
|
||||
},
|
||||
"心跳验证": {
|
||||
TitleEn: "Heartbeat Verification",
|
||||
ContentEn: "# Heartbeat Verification\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/heartbeat\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| device_id | string | Yes | Device ID |\n\n### Request Example\n```json\n{\n \"device_id\": \"device_001\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"success\"\n}\n```\n\n### Notes\n- Keeps user online status\n- Recommended to call every 30 seconds\n- User will be considered offline if heartbeat is not called for a certain period\n- JWT token must be included in the request header",
|
||||
},
|
||||
"卡密充值": {
|
||||
TitleEn: "Card Key Recharge",
|
||||
ContentEn: "# Card Key Recharge\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/recharge\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| username | string | Yes | User account |\n| card_key | string | Yes | Card key |\n\n### Request Example\n```json\n{\n \"username\": \"user123\",\n \"card_key\": \"VIP123456\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Recharge successful\",\n \"data\": {\n \"value\": 30\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|-------|------|-------------|\n| value | number | Recharge value (meaning varies by card type) |",
|
||||
},
|
||||
"获取绑定设备列表": {
|
||||
TitleEn: "Get Bound Devices List",
|
||||
ContentEn: "# Get Bound Devices List\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/devices\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"devices\": [\n {\n \"device_id\": \"device_001\",\n \"device_name\": \"My Computer\",\n \"bind_time\": \"2024-03-04T12:00:00Z\",\n \"last_active\": \"2024-03-04T13:00:00Z\"\n }\n ]\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|-------|------|-------------|\n| device_id | string | Device ID |\n| device_name | string | Device name |\n| bind_time | string | Binding time |\n| last_active | string | Last active time |",
|
||||
},
|
||||
"解绑设备": {
|
||||
TitleEn: "Unbind Device",
|
||||
ContentEn: "# Unbind Device\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/unbind-device\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| device_id | string | Yes | Device ID |\n\n### Request Example\n```json\n{\n \"device_id\": \"device_001\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Device unbound successfully\"\n}\n```",
|
||||
},
|
||||
"通过认证解绑设备": {
|
||||
TitleEn: "Unbind Device with Authentication",
|
||||
ContentEn: "# Unbind Device with Authentication\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/unbind-device-with-auth\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| device_id | string | Yes | Device ID to unbind |\n| password | string | Yes | User password for verification |\n\n### Request Example\n```json\n{\n \"device_id\": \"device_001\",\n \"password\": \"password123\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Device unbound successfully\"\n}\n```",
|
||||
},
|
||||
"重置密码(邮箱验证码)": {
|
||||
TitleEn: "Reset Password (Email Verification)",
|
||||
ContentEn: "# Reset Password (Email Verification)\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/reset-password\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| email | string | Yes | Email address |\n| email_code | string | Yes | Email verification code |\n| new_password | string | Yes | New password |\n\n### Request Example\n```json\n{\n \"email\": \"user@example.com\",\n \"email_code\": \"123456\",\n \"new_password\": \"newpassword123\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Password reset successful\"\n}\n```",
|
||||
},
|
||||
"修改密码": {
|
||||
TitleEn: "Change Password",
|
||||
ContentEn: "# Change Password\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/change-password\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| old_password | string | Yes | Current password |\n| new_password | string | Yes | New password |\n\n### Request Example\n```json\n{\n \"old_password\": \"oldpassword123\",\n \"new_password\": \"newpassword123\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Password changed successfully\"\n}\n```",
|
||||
},
|
||||
"获取设备数量": {
|
||||
TitleEn: "Get Device Count",
|
||||
ContentEn: "# Get Device Count\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/device-count\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"count\": 2,\n \"max_devices\": 3\n }\n}\n```",
|
||||
},
|
||||
"获取应用信息": {
|
||||
TitleEn: "Get Application Info",
|
||||
ContentEn: "# Get Application Info\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/info\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"name\": \"My Application\",\n \"description\": \"Application description\",\n \"version\": \"1.0.0\",\n \"status\": \"active\"\n }\n}\n```",
|
||||
},
|
||||
"获取用户账户信息": {
|
||||
TitleEn: "Get User Account Info",
|
||||
ContentEn: "# Get User Account Info\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/user/info\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"user_id\": 123,\n \"username\": \"user123\",\n \"email\": \"user@example.com\",\n \"balance\": 100,\n \"expiry_at\": \"2024-12-31T23:59:59Z\"\n }\n}\n```",
|
||||
},
|
||||
"获取应用常量": {
|
||||
TitleEn: "Get Application Constants",
|
||||
ContentEn: "# Get Application Constants\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/constants\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"constants\": [\n {\n \"key\": \"APP_VERSION\",\n \"value\": \"1.0.0\"\n }\n ]\n }\n}\n```",
|
||||
},
|
||||
"获取指定常量": {
|
||||
TitleEn: "Get Specific Constant",
|
||||
ContentEn: "# Get Specific Constant\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/constants/:key\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"key\": \"APP_VERSION\",\n \"value\": \"1.0.0\"\n }\n}\n```",
|
||||
},
|
||||
"获取应用变量": {
|
||||
TitleEn: "Get Application Variables",
|
||||
ContentEn: "# Get Application Variables\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/variables\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"variables\": [\n {\n \"key\": \"user_setting\",\n \"value\": \"default\"\n }\n ]\n }\n}\n```",
|
||||
},
|
||||
"获取指定变量": {
|
||||
TitleEn: "Get Specific Variable",
|
||||
ContentEn: "# Get Specific Variable\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/variables/:key\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"key\": \"user_setting\",\n \"value\": \"default\"\n }\n}\n```",
|
||||
},
|
||||
"更新用户变量": {
|
||||
TitleEn: "Update User Variable",
|
||||
ContentEn: "# Update User Variable\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/variables/:key\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| value | string | Yes | Variable value |\n\n### Request Example\n```json\n{\n \"value\": \"new_value\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Variable updated successfully\"\n}\n```",
|
||||
},
|
||||
"调用云端函数": {
|
||||
TitleEn: "Call Cloud Function",
|
||||
ContentEn: "# Call Cloud Function\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/functions/:name\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Example\n```json\n{\n \"param1\": \"value1\",\n \"param2\": \"value2\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"result\": \"function_result\"\n }\n}\n```",
|
||||
},
|
||||
"检查更新": {
|
||||
TitleEn: "Check for Updates",
|
||||
ContentEn: "# Check for Updates\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/check-update\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| current_version | string | Yes | Current application version |\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"has_update\": true,\n \"latest_version\": \"2.0.0\",\n \"download_url\": \"https://example.com/download\",\n \"update_log\": \"Bug fixes and improvements\"\n }\n}\n```",
|
||||
},
|
||||
"获取公告": {
|
||||
TitleEn: "Get Announcements",
|
||||
ContentEn: "# Get Announcements\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/announcements\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"announcements\": [\n {\n \"id\": 1,\n \"title\": \"System Maintenance\",\n \"content\": \"System maintenance scheduled for...\",\n \"type\": \"warning\",\n \"created_at\": \"2024-03-04T12:00:00Z\"\n }\n ]\n }\n}\n```",
|
||||
},
|
||||
"获取在线实例列表": {
|
||||
TitleEn: "Get Online Instances",
|
||||
ContentEn: "# Get Online Instances\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/online-instances\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"instances\": [\n {\n \"device_id\": \"device_001\",\n \"device_name\": \"My Computer\",\n \"login_time\": \"2024-03-04T12:00:00Z\",\n \"last_heartbeat\": \"2024-03-04T13:00:00Z\"\n }\n ]\n }\n}\n```",
|
||||
},
|
||||
"强制离线实例": {
|
||||
TitleEn: "Force Offline Instance",
|
||||
ContentEn: "# Force Offline Instance\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/force-offline\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| device_id | string | Yes | Device ID to force offline |\n\n### Request Example\n```json\n{\n \"device_id\": \"device_001\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Instance forced offline successfully\"\n}\n```",
|
||||
},
|
||||
"软件验证示例": {
|
||||
TitleEn: "Software Verification Example",
|
||||
ContentEn: "# Software Verification Example\n\n## Basic Verification Flow\n\n### 1. User Login\n```bash\ncurl -X POST https://api.example.com/api/v1/app/YOUR_APP_KEY/login \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"username\": \"user123\",\n \"password\": \"password123\",\n \"device_id\": \"unique_device_id\"\n }'\n```\n\n### 2. Save Token\nSave the returned token for subsequent API calls.\n\n### 3. Heartbeat\nCall heartbeat API periodically to maintain online status.\n\n## Complete Example (Python)\n```python\nimport requests\nimport time\n\nclass AppClient:\n def __init__(self, app_key):\n self.app_key = app_key\n self.base_url = \"https://api.example.com/api/v1/app\"\n self.token = None\n \n def login(self, username, password, device_id):\n url = f\"{self.base_url}/{self.app_key}/login\"\n response = requests.post(url, json={\n \"username\": username,\n \"password\": password,\n \"device_id\": device_id\n })\n data = response.json()\n if data[\"code\"] == 200:\n self.token = data[\"data\"][\"token\"]\n return True\n return False\n\n# Usage\nclient = AppClient(\"your_app_key\")\nif client.login(\"user123\", \"password\", \"device_001\"):\n while True:\n client.heartbeat(\"device_001\")\n time.sleep(30)\n```",
|
||||
},
|
||||
"游戏验证示例": {
|
||||
TitleEn: "Game Verification Example",
|
||||
ContentEn: "# Game Verification Example\n\n## Unity Integration\n\n### 1. Create API Client\n```csharp\nusing UnityEngine;\nusing UnityEngine.Networking;\nusing System.Collections;\n\npublic class VerificationClient : MonoBehaviour\n{\n private string appKey = \"your_app_key\";\n private string baseUrl = \"https://api.example.com/api/v1/app\";\n private string token;\n \n public IEnumerator Login(string username, string password, string deviceId)\n {\n string url = $\"{baseUrl}/{appKey}/login\";\n // ... implementation\n }\n}\n```\n\n### 2. Usage in Game\n```csharp\npublic class GameManager : MonoBehaviour\n{\n private VerificationClient client;\n private string deviceId;\n \n void Start()\n {\n deviceId = SystemInfo.deviceUniqueIdentifier;\n client = GetComponent<VerificationClient>();\n StartCoroutine(client.Login(\"user123\", \"password\", deviceId));\n }\n}\n```",
|
||||
},
|
||||
"如何获取API密钥?": {
|
||||
TitleEn: "How to Get API Key?",
|
||||
ContentEn: "# How to Get API Key?\n\n## Steps\n\n### 1. Register Account\nFirst, register a developer account on the platform.\n\n### 2. Create Application\n1. Go to Console > Applications\n2. Click \"Create Application\" button\n3. Fill in application name and description\n4. Click \"Create\"\n\n### 3. Get App Key\nAfter creating the application, you can find the App Key on the application details page.\n\n## App Key Format\nApp Key is a unique identifier in the format:\n```\napp_xxxxxxxxxxxxxxxx\n```\n\n## Security Notes\n- Do not share your App Key publicly\n- Regenerate App Key if it's compromised\n- Use environment variables to store App Key in production",
|
||||
},
|
||||
"卡密验证失败怎么办?": {
|
||||
TitleEn: "What to Do When Card Key Verification Fails?",
|
||||
ContentEn: "# What to Do When Card Key Verification Fails?\n\n## Common Issues\n\n### 1. Invalid Card Key\n**Symptom**: Returns \"Invalid or expired card key\"\n\n**Solutions**:\n- Check if the card key is entered correctly\n- Verify the card key hasn't been used\n- Confirm the card key hasn't expired\n\n### 2. Card Key Already Used\n**Symptom**: Returns \"Card key already used\"\n\n**Solutions**:\n- Each card key can only be used once\n- Purchase a new card key\n\n### 3. Card Key Expired\n**Symptom**: Returns \"Card key expired\"\n\n**Solutions**:\n- Check card key validity period\n- Contact support for assistance",
|
||||
},
|
||||
"如何实现代理授权?": {
|
||||
TitleEn: "How to Implement Agent Authorization?",
|
||||
ContentEn: "# How to Implement Agent Authorization?\n\n## Overview\nAgent authorization allows you to delegate application management to agents.\n\n## Setup Steps\n\n### 1. Create Agent Application\n1. Go to Console > Agent Applications\n2. Click \"Create Agent Application\"\n3. Select the application to authorize\n4. Set commission rate\n\n### 2. Generate Agent Link\nAfter creation, you'll get an agent link:\n```\nhttps://platform.com/agent/AGENT_CODE\n```\n\n### 3. Agent Dashboard\nAgents can access their dashboard to:\n- View sales statistics\n- Generate card keys\n- Manage users",
|
||||
},
|
||||
}
|
||||
|
||||
var docs []model.Doc
|
||||
if err := db.Find(&docs).Error; err != nil {
|
||||
log.Fatal("Failed to get docs:", err)
|
||||
}
|
||||
|
||||
for _, doc := range docs {
|
||||
if trans, ok := docTranslations[doc.Title]; ok {
|
||||
doc.TitleEn = trans.TitleEn
|
||||
doc.ContentEn = trans.ContentEn
|
||||
if err := db.Save(&doc).Error; err != nil {
|
||||
log.Printf("Failed to update doc %d: %v", doc.ID, err)
|
||||
} else {
|
||||
fmt.Printf("Translated doc: %s -> %s\n", doc.Title, doc.TitleEn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nTranslation completed!")
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
db := database.DB
|
||||
|
||||
packageTranslations := map[string]struct {
|
||||
NameEn string
|
||||
DescriptionEn string
|
||||
}{
|
||||
"免费版": {
|
||||
NameEn: "Free",
|
||||
DescriptionEn: "Basic features for individual developers",
|
||||
},
|
||||
"公益版": {
|
||||
NameEn: "Community",
|
||||
DescriptionEn: "Free for individual developers",
|
||||
},
|
||||
"专业版": {
|
||||
NameEn: "Professional",
|
||||
DescriptionEn: "Advanced features for professional developers",
|
||||
},
|
||||
"企业版": {
|
||||
NameEn: "Enterprise",
|
||||
DescriptionEn: "Full features for enterprise teams",
|
||||
},
|
||||
"基础版": {
|
||||
NameEn: "Basic",
|
||||
DescriptionEn: "Essential features for small projects",
|
||||
},
|
||||
"高级版": {
|
||||
NameEn: "Premium",
|
||||
DescriptionEn: "Premium features for growing businesses",
|
||||
},
|
||||
"旗舰版": {
|
||||
NameEn: "Flagship",
|
||||
DescriptionEn: "Top-tier features for large organizations",
|
||||
},
|
||||
"开发者版": {
|
||||
NameEn: "Developer",
|
||||
DescriptionEn: "Perfect for individual developers",
|
||||
},
|
||||
"团队版": {
|
||||
NameEn: "Team",
|
||||
DescriptionEn: "Collaborative features for teams",
|
||||
},
|
||||
"个人版": {
|
||||
NameEn: "Personal",
|
||||
DescriptionEn: "Personal use with essential features",
|
||||
},
|
||||
"标准版": {
|
||||
NameEn: "Standard",
|
||||
DescriptionEn: "Standard features for regular users",
|
||||
},
|
||||
}
|
||||
|
||||
var packages []model.Package
|
||||
if err := db.Find(&packages).Error; err != nil {
|
||||
log.Fatal("Failed to get packages:", err)
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if trans, ok := packageTranslations[pkg.Name]; ok {
|
||||
pkg.NameEn = trans.NameEn
|
||||
pkg.DescriptionEn = trans.DescriptionEn
|
||||
if err := db.Save(&pkg).Error; err != nil {
|
||||
log.Printf("Failed to update package %d: %v", pkg.ID, err)
|
||||
} else {
|
||||
fmt.Printf("Translated package: %s -> %s\n", pkg.Name, pkg.NameEn)
|
||||
}
|
||||
} else {
|
||||
pkg.NameEn = pkg.Name
|
||||
if pkg.Description != "" {
|
||||
pkg.DescriptionEn = pkg.Description
|
||||
}
|
||||
if err := db.Save(&pkg).Error; err != nil {
|
||||
log.Printf("Failed to update package %d: %v", pkg.ID, err)
|
||||
} else {
|
||||
fmt.Printf("Copied package: %s (no translation found)\n", pkg.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nPackage translation completed!")
|
||||
}
|
||||
@@ -0,0 +1,794 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
apiDocContent := `# 应用API
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了应用对接平台所需的所有API。每个应用都有独立的API地址,通过应用的密钥(appKey)进行访问。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: http://your-domain.com/api/v1/app/{appKey}
|
||||
- **请求方式**: GET/POST
|
||||
- **数据格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## 通信加密
|
||||
|
||||
所有API支持加密通信,根据应用的安全设置进行加密解密。
|
||||
|
||||
### 加密方式
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| none | 不加密,明文传输 |
|
||||
| aes | AES-GCM加密 |
|
||||
| rc4 | RC4流加密 |
|
||||
|
||||
### 加密请求格式
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"data": "加密后的数据(Base64编码)"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
### 加密响应格式
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"data": "加密后的数据(Base64编码)"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 公告管理
|
||||
|
||||
### 获取公告列表
|
||||
|
||||
获取应用的公告列表。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/announcements
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"announcements": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "系统维护通知",
|
||||
"content": "系统将于今晚进行维护",
|
||||
"type": "warning",
|
||||
"priority": "high",
|
||||
"status": "active",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**字段说明**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| type | string | 公告类型(info、warning、urgent) |
|
||||
| priority | string | 优先级(normal、medium、high) |
|
||||
| status | string | 状态(draft、active) |
|
||||
|
||||
---
|
||||
|
||||
## 版本更新
|
||||
|
||||
### 检测更新
|
||||
|
||||
检查应用是否有新版本可用。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/check-update?version=1.0.0
|
||||
` + "```" + `
|
||||
|
||||
**请求参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| version | string | 是 | 当前版本号 |
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"has_update": true,
|
||||
"force_update": false,
|
||||
"latest_version": "2.0.0",
|
||||
"download_url": "http://example.com/download/app-v2.0.0.zip",
|
||||
"file_size": 10240000,
|
||||
"description": "新版本修复了若干bug"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 应用信息
|
||||
|
||||
### 获取应用配置
|
||||
|
||||
获取应用的基本配置信息。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/info
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"app_id": 1,
|
||||
"app_name": "示例应用",
|
||||
"description": "这是一个示例应用",
|
||||
"billing_type": "subscription",
|
||||
"encrypt_type": "aes",
|
||||
"bind_type": "device",
|
||||
"max_devices": 1,
|
||||
"multi_open": false,
|
||||
"enable_trial": true,
|
||||
"trial_duration": 7
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**字段说明**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| billing_type | string | 计费类型(subscription订阅、time计时、point点数) |
|
||||
| encrypt_type | string | 加密类型(none、aes、rc4) |
|
||||
| bind_type | string | 绑定类型(none、device、ip) |
|
||||
| enable_trial | bool | 是否启用试用 |
|
||||
| trial_duration | int | 试用天数 |
|
||||
|
||||
---
|
||||
|
||||
## 用户认证
|
||||
|
||||
### 用户注册
|
||||
|
||||
注册新用户账号。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/register
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "password123",
|
||||
"email": "test@example.com",
|
||||
"device_id": "DEVICE-001"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"username": "testuser",
|
||||
"message": "注册成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
### 用户登录
|
||||
|
||||
用户登录获取账号信息。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/login
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "password123",
|
||||
"device_id": "DEVICE-001"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"username": "testuser",
|
||||
"balance": 100,
|
||||
"is_trial": false,
|
||||
"expiry_at": "2024-12-31T23:59:59Z",
|
||||
"message": "登录成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**错误响应**
|
||||
|
||||
| 错误信息 | 说明 |
|
||||
|----------|------|
|
||||
| 用户不存在 | 用户名未注册 |
|
||||
| 密码错误 | 密码不正确 |
|
||||
| 余额不足 | 账户余额为0且已过期 |
|
||||
| 设备绑定数量已达上限 | 超过最大设备数限制 |
|
||||
| 多开数量已达上限 | 同一设备登录用户数超限 |
|
||||
|
||||
---
|
||||
|
||||
## 卡密充值
|
||||
|
||||
### 使用卡密充值
|
||||
|
||||
使用卡密为当前用户充值。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/recharge
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"user_id": 1,
|
||||
"card_key": "CARD-1234567890"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"balance": 200,
|
||||
"expiry_at": "2024-12-31T23:59:59Z",
|
||||
"message": "充值成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 试用功能
|
||||
|
||||
### 申请试用
|
||||
|
||||
申请新用户试用。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/trial
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"user_id": 1,
|
||||
"device_id": "DEVICE-001"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"trial_start": "2024-01-01T00:00:00Z",
|
||||
"trial_end": "2024-01-08T00:00:00Z",
|
||||
"expiry_at": "2024-01-08T00:00:00Z",
|
||||
"message": "试用成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 设备管理
|
||||
|
||||
### 获取设备列表
|
||||
|
||||
获取用户绑定的设备列表。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/devices?user_id=1
|
||||
` + "```" + `
|
||||
|
||||
**请求参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| user_id | int | 是 | 用户ID |
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"devices": [
|
||||
{
|
||||
"device_id": "DEVICE-001",
|
||||
"device_name": "默认设备",
|
||||
"last_active": "2024-01-01T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
### 解绑设备
|
||||
|
||||
解绑用户的设备。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/unbind-device
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"user_id": 1,
|
||||
"device_id": "DEVICE-001"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "解绑成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 账户管理
|
||||
|
||||
### 获取账户信息
|
||||
|
||||
获取用户的详细账户信息。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/account?user_id=1
|
||||
` + "```" + `
|
||||
|
||||
**请求参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| user_id | int | 是 | 用户ID |
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"balance": 100,
|
||||
"is_trial": false,
|
||||
"trial_start": null,
|
||||
"trial_end": null,
|
||||
"expiry_at": "2024-12-31T23:59:59Z",
|
||||
"device_id": "DEVICE-001",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
### 心跳检测
|
||||
|
||||
发送心跳以保持在线状态。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/heartbeat
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"user_id": 1,
|
||||
"device_id": "DEVICE-001"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"status": "active",
|
||||
"message": "心跳成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 云端数据
|
||||
|
||||
### 获取云端常量
|
||||
|
||||
获取全局的云端常量配置。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/constants
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"constants": {
|
||||
"max_connections": "10",
|
||||
"timeout": "30",
|
||||
"retry_times": "3"
|
||||
}
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
### 获取云端变量
|
||||
|
||||
获取用户的云端变量。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
GET /api/v1/app/{appKey}/variables?user_id=1
|
||||
` + "```" + `
|
||||
|
||||
**请求参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| user_id | int | 是 | 用户ID |
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"variables": {
|
||||
"setting1": "value1",
|
||||
"setting2": "value2"
|
||||
}
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
### 修改云端变量
|
||||
|
||||
更新用户的云端变量。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/variables
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"user_id": 1,
|
||||
"variables": {
|
||||
"setting1": "new_value1",
|
||||
"setting2": "new_value2"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"message": "更新成功"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
### 调用云端函数
|
||||
|
||||
调用云端定义的动态函数。
|
||||
|
||||
**请求**
|
||||
|
||||
` + "```" + `
|
||||
POST /api/v1/app/{appKey}/call-function
|
||||
` + "```" + `
|
||||
|
||||
**请求体**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"function_name": "custom_function",
|
||||
"parameters": {
|
||||
"param1": "value1",
|
||||
"param2": "value2"
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
**响应示例**
|
||||
|
||||
` + "```json" + `
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"result": "动态函数调用成功",
|
||||
"data": {
|
||||
"param1": "value1",
|
||||
"param2": "value2"
|
||||
}
|
||||
}
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
---
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权或认证失败 |
|
||||
| 403 | 禁止访问(账号被禁用、已过期等) |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 试用和免费时段
|
||||
|
||||
### 试用功能
|
||||
|
||||
- 订阅模式应用可以启用试用功能
|
||||
- 新用户可以申请试用,试用时长由应用配置决定
|
||||
- 试用用户只能试用一次
|
||||
- 试用期间可以正常使用应用的所有功能
|
||||
|
||||
### 免费时段功能
|
||||
|
||||
- 订阅模式应用可以设置免费时间段
|
||||
- 在免费时间段内,所有用户可以免费使用应用
|
||||
- 免费时间段按每天设置,例如:00:00-06:00
|
||||
|
||||
---
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **使用HTTPS**: 生产环境建议使用HTTPS协议
|
||||
2. **启用加密**: 建议启用AES加密保护通信安全
|
||||
3. **定期更换密钥**: 定期更换应用的通信密钥
|
||||
4. **验证设备**: 启用设备绑定功能防止账号共享
|
||||
5. **限制并发**: 根据需要限制最大设备数和并发数
|
||||
|
||||
---
|
||||
|
||||
## 代码示例
|
||||
|
||||
### JavaScript
|
||||
|
||||
` + "```javascript" + `
|
||||
const appKey = 'your-app-key';
|
||||
const baseUrl = 'http://your-domain.com/api/v1/app/' + appKey;
|
||||
|
||||
// 获取公告
|
||||
async function getAnnouncements() {
|
||||
const response = await fetch(baseUrl + '/announcements');
|
||||
const result = await response.json();
|
||||
if (result.code === 200) {
|
||||
console.log('公告列表:', result.data.announcements);
|
||||
}
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
async function login(username, password, deviceId) {
|
||||
const response = await fetch(baseUrl + '/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password, device_id: deviceId })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.code === 200) {
|
||||
console.log('登录成功:', result.data);
|
||||
return result.data;
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
async function heartbeat(userId, deviceId) {
|
||||
const response = await fetch(baseUrl + '/heartbeat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_id: userId, device_id: deviceId })
|
||||
});
|
||||
const result = await response.json();
|
||||
return result.code === 200;
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
### Python
|
||||
|
||||
` + "```python" + `
|
||||
import requests
|
||||
|
||||
class AppClient:
|
||||
def __init__(self, app_key, base_url='http://your-domain.com/api/v1/app'):
|
||||
self.app_key = app_key
|
||||
self.base_url = base_url + '/' + app_key
|
||||
|
||||
def get_announcements(self):
|
||||
response = requests.get(self.base_url + '/announcements')
|
||||
result = response.json()
|
||||
if result['code'] == 200:
|
||||
return result['data']['announcements']
|
||||
return None
|
||||
|
||||
def login(self, username, password, device_id):
|
||||
data = {'username': username, 'password': password, 'device_id': device_id}
|
||||
response = requests.post(self.base_url + '/login', json=data)
|
||||
result = response.json()
|
||||
if result['code'] == 200:
|
||||
return result['data']
|
||||
return None
|
||||
|
||||
def heartbeat(self, user_id, device_id):
|
||||
data = {'user_id': user_id, 'device_id': device_id}
|
||||
response = requests.post(self.base_url + '/heartbeat', json=data)
|
||||
result = response.json()
|
||||
return result['code'] == 200
|
||||
|
||||
# 使用示例
|
||||
client = AppClient('your-app-key')
|
||||
announcements = client.get_announcements()
|
||||
print('公告列表:', announcements)
|
||||
` + "```" + `
|
||||
`
|
||||
|
||||
var apiCategory model.DocCategory
|
||||
if err := database.DB.Where("slug = ?", "api-docs").First(&apiCategory).Error; err != nil {
|
||||
apiCategory = model.DocCategory{
|
||||
Name: "API文档",
|
||||
Slug: "api-docs",
|
||||
Description: "应用对接API文档",
|
||||
Sort: 100,
|
||||
}
|
||||
if err := database.DB.Create(&apiCategory).Error; err != nil {
|
||||
log.Printf("创建API文档分类失败: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var existingDoc model.Doc
|
||||
if err := database.DB.Where("slug = ?", "app-api-docs").First(&existingDoc).Error; err == nil {
|
||||
existingDoc.Title = "应用API"
|
||||
existingDoc.Content = apiDocContent
|
||||
existingDoc.Summary = "应用对接平台所需的所有API文档,包含用户认证、设备管理、云端数据等核心功能"
|
||||
if err := database.DB.Save(&existingDoc).Error; err != nil {
|
||||
log.Printf("更新API文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
log.Println("API文档更新成功")
|
||||
return
|
||||
}
|
||||
|
||||
apiDoc := model.Doc{
|
||||
Title: "应用API",
|
||||
CategoryID: &apiCategory.ID,
|
||||
Slug: "app-api-docs",
|
||||
Content: apiDocContent,
|
||||
Summary: "应用对接平台所需的所有API文档,包含用户认证、设备管理、云端数据等核心功能",
|
||||
Icon: "code",
|
||||
Sort: 1,
|
||||
Status: "published",
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&apiDoc).Error; err != nil {
|
||||
log.Printf("创建API文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("API文档创建成功")
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
db := database.DB
|
||||
|
||||
docTranslations := map[string]struct {
|
||||
TitleEn string
|
||||
ContentEn string
|
||||
SummaryEn string
|
||||
}{
|
||||
"api-heartbeat": {
|
||||
TitleEn: "Heartbeat Verification",
|
||||
ContentEn: "# Heartbeat Verification\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/heartbeat\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| device_id | string | Yes | Device ID |\n\n### Request Example\n```json\n{\n \"device_id\": \"device_001\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"success\"\n}\n```\n\n### Notes\n- Keeps user online status\n- Recommended to call every 30 seconds\n- User will be considered offline if heartbeat is not called for a certain period\n- JWT token must be included in the request header",
|
||||
SummaryEn: "Heartbeat verification instructions",
|
||||
},
|
||||
"api-recharge": {
|
||||
TitleEn: "Card Key Recharge",
|
||||
ContentEn: "# Card Key Recharge\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/recharge\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| username | string | Yes | User account |\n| card_key | string | Yes | Card key |\n\n### Request Example\n```json\n{\n \"username\": \"user123\",\n \"card_key\": \"VIP123456\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Recharge successful\",\n \"data\": {\n \"value\": 30\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|--------|------|------|\n| value | number | Recharge value (meaning varies by card type) |\n\n### Error Response\n```json\n{\n \"code\": 400,\n \"message\": \"Invalid or expired card key\"\n}\n```",
|
||||
SummaryEn: "Card key recharge instructions",
|
||||
},
|
||||
"api-devices": {
|
||||
TitleEn: "Get Bound Device List",
|
||||
ContentEn: "# Get Bound Device List\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/devices\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"devices\": [\n {\n \"device_id\": \"device_001\",\n \"device_name\": \"My Computer\",\n \"bind_time\": \"2024-03-04T12:00:00Z\",\n \"last_active\": \"2024-03-04T13:00:00Z\"\n }\n ]\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|--------|------|------|\n| device_id | string | Device ID |\n| device_name | string | Device name |\n| bind_time | string | Bind time |\n| last_active | string | Last active time |",
|
||||
SummaryEn: "Get bound device list instructions",
|
||||
},
|
||||
"api-unbind-device": {
|
||||
TitleEn: "Unbind Device",
|
||||
ContentEn: "# Unbind Device\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/unbind-device\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| device_id | string | Yes | Device ID |\n\n### Request Example\n```json\n{\n \"device_id\": \"device_001\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Unbind successful\"\n}\n```\n\n### Error Response\n```json\n{\n \"code\": 400,\n \"message\": \"Device not found or not bound\"\n}\n```",
|
||||
SummaryEn: "Unbind device instructions",
|
||||
},
|
||||
"api-unbind-device-with-auth": {
|
||||
TitleEn: "Unbind Device with Authentication",
|
||||
ContentEn: "# Unbind Device with Authentication\n\nWhen users cannot login due to device limit, they can use this endpoint to unbind a device with username and password.\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/unbind-device-with-auth\n```\n\n### Note\nThis endpoint does not require Authorization header, uses username and password for authentication.\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| username | string | Yes | Username |\n| password | string | Yes | Password |\n| device_id | string | Yes | Device ID to unbind |\n\n### Request Example\n```json\n{\n \"username\": \"user123\",\n \"password\": \"password123\",\n \"device_id\": \"device_001\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"message\": \"Unbind successful\"\n }\n}\n```",
|
||||
SummaryEn: "Unbind device with authentication instructions",
|
||||
},
|
||||
"api-reset-password": {
|
||||
TitleEn: "Reset Password (Email Verification)",
|
||||
ContentEn: "# Reset Password (Email Verification)\n\nWhen users forget their password, they can reset it via email verification code.\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/reset-password\n```\n\n### Note\nThis endpoint does not require Authorization header, must call send email verification code endpoint first.\n\n### Prerequisites\n- Application has password reset enabled\n- User's package supports password reset\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| email | string | Yes | Email address |\n| code | string | Yes | Email verification code |\n| password | string | Yes | New password (at least 6 characters) |\n\n### Request Example\n```json\n{\n \"email\": \"user@example.com\",\n \"code\": \"123456\",\n \"password\": \"newpassword123\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"message\": \"Password reset successful\"\n }\n}\n```",
|
||||
SummaryEn: "Reset password via email verification code",
|
||||
},
|
||||
"api-change-password": {
|
||||
TitleEn: "Change Password",
|
||||
ContentEn: "# Change Password\n\nChange password by verifying username and original password.\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/change-password\n```\n\n### Note\nThis endpoint does not require Authorization header, uses username and original password for authentication.\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| username | string | Yes | Username |\n| old_password | string | Yes | Original password |\n| new_password | string | Yes | New password (at least 6 characters) |\n\n### Request Example\n```json\n{\n \"username\": \"user123\",\n \"old_password\": \"oldpassword123\",\n \"new_password\": \"newpassword123\"\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"message\": \"Password changed successfully\"\n }\n}\n```",
|
||||
SummaryEn: "Change password",
|
||||
},
|
||||
"api-device-count": {
|
||||
TitleEn: "Get Device Count",
|
||||
ContentEn: "# Get Device Count\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/device-count\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"count\": 3,\n \"max_devices\": 5\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|--------|------|------|\n| count | number | Current bound device count |\n| max_devices | number | Maximum allowed devices |",
|
||||
SummaryEn: "Get device count instructions",
|
||||
},
|
||||
"api-app-info": {
|
||||
TitleEn: "Get Application Info",
|
||||
ContentEn: "# Get Application Info\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/info\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"app_id\": 1,\n \"name\": \"My App\",\n \"description\": \"Game verification app\",\n \"status\": \"active\"\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|--------|------|------|\n| app_id | number | Application ID |\n| name | string | Application name |\n| description | string | Application description |\n| status | string | Application status |",
|
||||
SummaryEn: "Get application info instructions",
|
||||
},
|
||||
"api-account": {
|
||||
TitleEn: "Get User Account Info",
|
||||
ContentEn: "# Get User Account Info\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/account\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"user_id\": 123,\n \"username\": \"user123\",\n \"expire_time\": \"2024-04-04T00:00:00Z\",\n \"remaining_days\": 30\n }\n}\n```\n\n### Response Fields\n| Field | Type | Description |\n|--------|------|------|\n| user_id | number | User ID |\n| username | string | Username |\n| expire_time | string | Expiration time |\n| remaining_days | number | Remaining days |",
|
||||
SummaryEn: "Get user account info instructions",
|
||||
},
|
||||
"api-constants": {
|
||||
TitleEn: "Get Application Constants",
|
||||
ContentEn: "# Get Application Constants\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/constants\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Constant Name\",\n \"key\": \"CONSTANT_KEY\",\n \"value\": \"Constant Value\",\n \"var_type\": \"string\",\n \"description\": \"Constant description\"\n }\n ]\n}\n```",
|
||||
SummaryEn: "Get application constants instructions",
|
||||
},
|
||||
"api-constant": {
|
||||
TitleEn: "Get Specific Constant",
|
||||
ContentEn: "# Get Specific Constant\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/constants/:key\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Path Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| key | string | Yes | Constant key |\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"id\": 1,\n \"name\": \"Constant Name\",\n \"key\": \"CONSTANT_KEY\",\n \"value\": \"Constant Value\",\n \"var_type\": \"string\",\n \"description\": \"Constant description\"\n }\n}\n```",
|
||||
SummaryEn: "Get specific constant instructions",
|
||||
},
|
||||
"api-variables": {
|
||||
TitleEn: "Get Application Variables",
|
||||
ContentEn: "# Get Application Variables\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/variables\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Variable Name\",\n \"key\": \"VARIABLE_KEY\",\n \"default_value\": \"Default Value\",\n \"var_type\": \"string\",\n \"scope\": \"user\",\n \"description\": \"Variable description\"\n }\n ]\n}\n```",
|
||||
SummaryEn: "Get application variables instructions",
|
||||
},
|
||||
"api-variable": {
|
||||
TitleEn: "Get Specific Variable",
|
||||
ContentEn: "# Get Specific Variable\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/variables/:key\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Path Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| key | string | Yes | Variable key |\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"id\": 1,\n \"name\": \"Variable Name\",\n \"key\": \"VARIABLE_KEY\",\n \"default_value\": \"Default Value\",\n \"var_type\": \"string\",\n \"scope\": \"user\",\n \"description\": \"Variable description\"\n }\n}\n```",
|
||||
SummaryEn: "Get specific variable instructions",
|
||||
},
|
||||
"api-update-variables": {
|
||||
TitleEn: "Update User Variables",
|
||||
ContentEn: "# Update User Variables\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/variables\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| variables | object | Yes | Variable key-value pairs |\n\n### Request Example\n```json\n{\n \"variables\": {\n \"nickname\": \"New Nickname\",\n \"level\": 10\n }\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Update successful\"\n}\n```\n\n### Notes\n- Only user-scoped variables can be updated\n- Variable value types must match the defined types",
|
||||
SummaryEn: "Update user variables instructions",
|
||||
},
|
||||
"api-call-function": {
|
||||
TitleEn: "Call Cloud Function",
|
||||
ContentEn: "# Call Cloud Function\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/call-function\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| function_name | string | Yes | Cloud function name |\n| params | object | No | Parameters to pass to the function |\n\n### Request Example\n```json\n{\n \"function_name\": \"calculatePrice\",\n \"params\": {\n \"x\": 10,\n \"y\": 20\n }\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Success\",\n \"data\": {\n \"result\": 30,\n \"execution_time\": 0.001234\n }\n}\n```",
|
||||
SummaryEn: "Call cloud function instructions",
|
||||
},
|
||||
"api-check-update": {
|
||||
TitleEn: "Check for Updates",
|
||||
ContentEn: "# Check for Updates\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/check-update\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| version | string | No | Current client version |\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": {\n \"has_update\": true,\n \"latest_version\": \"2.0.0\",\n \"download_url\": \"https://example.com/download/v2.0.0\",\n \"update_notes\": \"Fixed several bugs\",\n \"update_strategy\": \"optional\",\n \"update_method\": \"manual\"\n }\n}\n```",
|
||||
SummaryEn: "Check for updates instructions",
|
||||
},
|
||||
"api-announcements": {
|
||||
TitleEn: "Get Announcements",
|
||||
ContentEn: "# Get Announcements\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/announcements\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": [\n {\n \"id\": 1,\n \"title\": \"System Maintenance Notice\",\n \"content\": \"System will be under maintenance tonight...\",\n \"created_at\": \"2024-03-04T10:00:00Z\"\n }\n ]\n}\n```",
|
||||
SummaryEn: "Get announcements instructions",
|
||||
},
|
||||
"api-instances": {
|
||||
TitleEn: "Get Online Instance List",
|
||||
ContentEn: "# Get Online Instance List\n\n### Endpoint\n```\nGET /api/v1/app/:appKey/instances\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| user_id | number | Yes | User ID |\n\n### Request Example\n```json\n{\n \"user_id\": 123\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"data\": [\n {\n \"id\": 1,\n \"instance_id\": \"instance_001\",\n \"device_id\": \"device_001\",\n \"device_name\": \"My Computer\",\n \"is_online\": true,\n \"last_heartbeat\": \"2024-03-04T13:00:00Z\",\n \"created_at\": \"2024-03-04T12:00:00Z\"\n }\n ]\n}\n```",
|
||||
SummaryEn: "Get online instance list instructions",
|
||||
},
|
||||
"api-force-offline": {
|
||||
TitleEn: "Force Instance Offline",
|
||||
ContentEn: "# Force Instance Offline\n\n### Endpoint\n```\nPOST /api/v1/app/:appKey/instances/:instance_id/offline\n```\n\n### Request Header\n```\nAuthorization: Bearer {token}\n```\n\n### Path Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| instance_id | string | Yes | Instance identifier |\n\n### Request Parameters\n| Parameter | Type | Required | Description |\n|--------|------|------|------|\n| user_id | number | Yes | User ID |\n\n### Request Example\n```json\n{\n \"user_id\": 123\n}\n```\n\n### Response Example\n```json\n{\n \"code\": 200,\n \"message\": \"Forced offline successfully\"\n}\n```",
|
||||
SummaryEn: "Force instance offline instructions",
|
||||
},
|
||||
"example-software": {
|
||||
TitleEn: "Software Verification Example",
|
||||
ContentEn: "# Software Verification Example\n\n## Complete Verification Flow\n\n### 1. User Input Username and Password\n\n```javascript\nconst username = document.getElementById('username').value;\nconst password = document.getElementById('password').value;\n```\n\n### 2. Get Device ID\n\n```javascript\nfunction getDeviceId() {\n let deviceId = localStorage.getItem('device_id');\n if (!deviceId) {\n deviceId = 'device_' + Math.random().toString(36).substr(2, 9);\n localStorage.setItem('device_id', deviceId);\n }\n return deviceId;\n}\n\nconst deviceId = getDeviceId();\n```\n\n### 3. Call Login API\n\n```javascript\nconst response = await fetch(`/api/v1/app/${appKey}/login`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n username,\n password,\n device_id: deviceId\n })\n});\n\nconst result = await response.json();\n\nif (result.code !== 200) {\n alert('Login failed: ' + result.message);\n return;\n}\n```\n\n### 4. Save Token\n\n```javascript\nlocalStorage.setItem('token', result.data.token);\nlocalStorage.setItem('expire_time', result.data.expire_time);\n```\n\n### 5. Start Heartbeat\n\n```javascript\nsetInterval(async () => {\n await fetch(`/api/v1/app/${appKey}/heartbeat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${localStorage.getItem('token')}`\n },\n body: JSON.stringify({\n device_id: deviceId\n })\n });\n}, 30000);\n```\n\n### 6. Enter Main Program\n\n```javascript\nstartApplication();\n```",
|
||||
SummaryEn: "Complete software login verification example",
|
||||
},
|
||||
"example-game": {
|
||||
TitleEn: "Game Verification Example",
|
||||
ContentEn: "# Game Verification Example\n\n## Unity Integration\n\n### 1. Call Login API\n\n```csharp\nusing UnityEngine;\nusing UnityEngine.Networking;\n\npublic class AuthManager : MonoBehaviour {\n private string appKey = \"your_app_key\";\n private string token;\n private string deviceId;\n \n void Start() {\n deviceId = SystemInfo.deviceUniqueIdentifier;\n }\n \n public async void Login(string username, string password) {\n string url = $\"https://api.example.com/api/v1/app/{appKey}/login\";\n \n string jsonData = $\"{{\\\"username\\\": \\\"{username}\\\", \\\"password\\\": \\\"{password}\\\", \\\"device_id\\\": \\\"{deviceId}\\\"}}\";\n \n using (UnityWebRequest request = UnityWebRequest.Post(url, jsonData)) {\n request.SetRequestHeader(\"Content-Type\", \"application/json\");\n \n yield return request.SendWebRequest();\n \n if (request.result == UnityWebRequest.Result.Success) {\n string response = request.downloadHandler.text;\n var result = JsonUtility.FromJson<LoginResponse>(response);\n \n if (result.code == 200) {\n token = result.data.token;\n PlayerPrefs.SetString(\"token\", token);\n \n // Start heartbeat\n StartCoroutine(HeartbeatCoroutine());\n \n // Enter game\n LoadMainScene();\n } else {\n Debug.LogError(\"Login failed: \" + result.message);\n }\n } else {\n Debug.LogError(\"Request failed: \" + request.error);\n }\n }\n }\n}\n```",
|
||||
SummaryEn: "Unity game integration example",
|
||||
},
|
||||
"faq-api-key": {
|
||||
TitleEn: "How to Get API Key?",
|
||||
ContentEn: "# How to Get API Key?\n\n## Steps\n\n1. Login to developer dashboard\n2. Go to \"Application Management\" page\n3. Create new application or select existing one\n4. In application details page you can see:\n - **AppID**: Application unique identifier\n - **AppKey**: Application key\n - **SecretKey**: Encryption key\n\n## Notes\n\n- AppKey is only shown once when created, please save it in time\n- Click \"Reset Key\" button to reset AppKey if needed\n- Old key becomes invalid immediately after reset\n- SecretKey is for server-side response verification, do not use it on client side",
|
||||
SummaryEn: "Detailed steps to get API key",
|
||||
},
|
||||
"faq-card-fail": {
|
||||
TitleEn: "What to Do if Card Key Verification Fails?",
|
||||
ContentEn: "# What to Do if Card Key Verification Fails?\n\n## Common Reasons\n\n1. **Card Key Expired**\n - Check the validity period of the card key\n - Expired card keys cannot be used\n\n2. **Card Key Already Used**\n - Single-use card keys can only be verified once\n - Used card keys cannot be verified again\n\n3. **Device Binding Error**\n - Check if device ID is correct\n - Confirm if application has device binding enabled\n\n4. **Application Configuration Error**\n - Check if AppID and AppKey are correct\n - Confirm if application status is normal\n\n## Solutions\n\n1. Contact application customer service\n2. Get a new card key\n3. Check device network connection\n4. View application logs for detailed error information",
|
||||
SummaryEn: "Common reasons and solutions for card key verification failure",
|
||||
},
|
||||
"faq-agent": {
|
||||
TitleEn: "How to Implement Agent Authorization?",
|
||||
ContentEn: "# How to Implement Agent Authorization?\n\n## What is Agent Authorization?\n\nAgent authorization allows developers to authorize their applications to other developers, who can then generate card keys and sell them.\n\n## Authorization Process\n\n1. **Apply for Authorization**\n - Authorized party submits application to authorizer\n - Enter application ID\n - Wait for authorizer approval\n\n2. **Approve Authorization**\n - Authorizer views application\n - Set card key type permissions after approval\n - Set agent commission and discount\n\n3. **Generate Card Keys**\n - Authorized party selects authorized application\n - Select card key types with permission\n - Generate and sell card keys\n\n4. **Settle Commission**\n - Sales revenue is settled proportionally\n - Authorized party receives commission income\n\n## Permission Management\n\n- Authorizer can modify card key type permissions at any time\n- Can pause or terminate authorization\n- Can view agent's sales data",
|
||||
SummaryEn: "Implementation process and permission management for agent authorization",
|
||||
},
|
||||
}
|
||||
|
||||
var categories []model.DocCategory
|
||||
if err := db.Find(&categories).Error; err != nil {
|
||||
log.Fatal("Failed to get categories:", err)
|
||||
}
|
||||
|
||||
for i := range categories {
|
||||
translation, ok := docTranslations[categories[i].Slug]
|
||||
if ok {
|
||||
categories[i].NameEn = translation.TitleEn
|
||||
categories[i].DescriptionEn = translation.ContentEn
|
||||
db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"name_en", "description_en"}),
|
||||
}).Create(&categories[i])
|
||||
fmt.Printf("Updated category: %s -> %s\n", categories[i].Name, categories[i].NameEn)
|
||||
}
|
||||
}
|
||||
|
||||
var docs []model.Doc
|
||||
if err := db.Find(&docs).Error; err != nil {
|
||||
log.Fatal("Failed to get docs:", err)
|
||||
}
|
||||
|
||||
for i := range docs {
|
||||
translation, ok := docTranslations[docs[i].Slug]
|
||||
if ok {
|
||||
docs[i].TitleEn = translation.TitleEn
|
||||
docs[i].ContentEn = translation.ContentEn
|
||||
docs[i].SummaryEn = translation.SummaryEn
|
||||
db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"title_en", "content_en", "summary_en"}),
|
||||
}).Create(&docs[i])
|
||||
fmt.Printf("Updated doc: %s -> %s\n", docs[i].Title, docs[i].TitleEn)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nDocument translations updated successfully!")
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
var doc model.Doc
|
||||
if err := database.DB.Where("slug = ?", "api-get-dynamic-code").First(&doc).Error; err != nil {
|
||||
log.Printf("未找到文档: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
newContent := `# 获取动态代码接口
|
||||
|
||||
**注意**:动态代码接口需要用户登录认证,需要在请求头中携带JWT token:
|
||||
Authorization: Bearer {token}
|
||||
|
||||
### 接口地址
|
||||
GET /api/v1/app/:appKey/dynamic-code
|
||||
|
||||
### 请求头
|
||||
Authorization: Bearer {token}
|
||||
|
||||
### 路径参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| appKey | string | 是 | 应用密钥 |
|
||||
|
||||
### 响应示例
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "加法函数",
|
||||
"key": "add",
|
||||
"description": "实现数字加法运算",
|
||||
"status": "active",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
### 字段说明
|
||||
- id: 动态代码ID
|
||||
- name: 动态代码名称
|
||||
- key: 动态代码唯一标识
|
||||
- description: 描述
|
||||
- status: 状态(active、inactive)
|
||||
|
||||
---
|
||||
|
||||
## 执行动态代码接口
|
||||
|
||||
### 接口地址
|
||||
POST /api/v1/app/:appKey/dynamic-code/:key/execute
|
||||
|
||||
### 请求头
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
|
||||
### 路径参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| appKey | string | 是 | 应用密钥 |
|
||||
| key | string | 是 | 动态代码标识 |
|
||||
|
||||
### 请求体
|
||||
{
|
||||
"params": {
|
||||
"param1": "value1",
|
||||
"param2": "value2"
|
||||
}
|
||||
}
|
||||
|
||||
### 参数说明
|
||||
- params: 参数对象,键值对形式传递,键名即为JavaScript变量名
|
||||
|
||||
### 响应示例
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"result": "执行结果",
|
||||
"execution_time": 1
|
||||
}
|
||||
}
|
||||
|
||||
### 字段说明
|
||||
- result: 代码执行结果
|
||||
- execution_time: 执行时间(毫秒)
|
||||
|
||||
---
|
||||
|
||||
## JavaScript代码编写规范
|
||||
|
||||
### 1. 参数使用
|
||||
|
||||
动态代码可以直接使用请求参数中传递的变量,无需额外定义:
|
||||
|
||||
a + b
|
||||
|
||||
请求参数:{"params": {"a": 10, "b": 20}}
|
||||
返回结果:30
|
||||
|
||||
### 2. 函数定义与调用
|
||||
|
||||
可以定义函数并在最后一行调用:
|
||||
|
||||
function calculate(x, y) {
|
||||
return Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
calculate(x, y)
|
||||
|
||||
请求参数:{"params": {"x": 3, "y": 4}}
|
||||
返回结果:5
|
||||
|
||||
### 3. 复杂逻辑
|
||||
|
||||
支持完整的JavaScript语法,包括控制流、循环、对象操作等:
|
||||
|
||||
function processScore(score) {
|
||||
if (score >= 90) {
|
||||
return "优秀";
|
||||
} else if (score >= 60) {
|
||||
return "及格";
|
||||
} else {
|
||||
return "不及格";
|
||||
}
|
||||
}
|
||||
|
||||
processScore(score)
|
||||
|
||||
### 4. 返回值规则
|
||||
|
||||
- **最后一个表达式的值即为返回值**
|
||||
- **不要在全局作用域使用return语句**
|
||||
- 可以返回任意类型:数字、字符串、布尔值、对象、数组等
|
||||
|
||||
### 5. 常见示例
|
||||
|
||||
#### 简单计算
|
||||
price * quantity
|
||||
|
||||
#### 字符串拼接
|
||||
name + "的年龄是" + age + "岁"
|
||||
|
||||
#### 条件判断
|
||||
score >= 60 ? "及格" : "不及格"
|
||||
|
||||
#### 对象返回
|
||||
{
|
||||
sum: a + b,
|
||||
product: a * b,
|
||||
difference: a - b
|
||||
}
|
||||
|
||||
#### 数组操作
|
||||
function sumArray(numbers) {
|
||||
let total = 0;
|
||||
for (let i = 0; i < numbers.length; i++) {
|
||||
total += numbers[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
sumArray([1, 2, 3, 4, 5])
|
||||
|
||||
### 6. 注意事项
|
||||
|
||||
1. **全局作用域不能使用return**:return只能在函数内部使用
|
||||
2. **参数变量直接使用**:无需重新定义
|
||||
3. **最后一行是返回值**:确保最后一行是需要返回的表达式
|
||||
4. **支持ES6语法**:可以使用let、const、箭头函数等
|
||||
5. **内置对象可用**:Math、Date、JSON等JavaScript内置对象都可以使用
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误
|
||||
|
||||
#### 1. 语法错误
|
||||
{
|
||||
"code": 400,
|
||||
"message": "代码执行错误: SyntaxError: Unexpected token"
|
||||
}
|
||||
|
||||
#### 2. 运行时错误
|
||||
{
|
||||
"code": 400,
|
||||
"message": "代码执行错误: ReferenceError: x is not defined"
|
||||
}
|
||||
|
||||
#### 3. 全局return错误
|
||||
{
|
||||
"code": 400,
|
||||
"message": "代码执行错误: SyntaxError: Illegal return statement"
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例1:计算折扣价格
|
||||
|
||||
**请求**:
|
||||
POST /api/v1/app/your-app-key/dynamic-code/calculate-discount/execute
|
||||
{
|
||||
"params": {
|
||||
"price": 100,
|
||||
"discount": 20,
|
||||
"tax": 10
|
||||
}
|
||||
}
|
||||
|
||||
**动态代码**:
|
||||
function calculateFinalPrice(price, discount, tax) {
|
||||
const subtotal = price;
|
||||
const discountAmount = subtotal * (discount / 100);
|
||||
const afterDiscount = subtotal - discountAmount;
|
||||
const taxAmount = afterDiscount * (tax / 100);
|
||||
return afterDiscount + taxAmount;
|
||||
}
|
||||
|
||||
calculateFinalPrice(price, discount, tax)
|
||||
|
||||
**响应**:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"result": 88,
|
||||
"execution_time": 1
|
||||
}
|
||||
}
|
||||
|
||||
### 示例2:用户信息格式化
|
||||
|
||||
**请求**:
|
||||
POST /api/v1/app/your-app-key/dynamic-code/format-user/execute
|
||||
{
|
||||
"params": {
|
||||
"name": "张三",
|
||||
"age": 25,
|
||||
"city": "北京"
|
||||
}
|
||||
}
|
||||
|
||||
**动态代码**:
|
||||
function formatUserInfo(name, age, city) {
|
||||
const greeting = "你好";
|
||||
return greeting + ",我是" + name + ",今年" + age + "岁,来自" + city;
|
||||
}
|
||||
|
||||
formatUserInfo(name, age, city)
|
||||
|
||||
**响应**:
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": {
|
||||
"result": "你好,我是张三,今年25岁,来自北京",
|
||||
"execution_time": 1
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
## 性能建议
|
||||
|
||||
1. **避免死循环**:确保循环有明确的退出条件
|
||||
2. **合理使用缓存**:对于重复计算,可以考虑缓存结果
|
||||
3. **控制代码复杂度**:过复杂的代码可能影响执行效率
|
||||
4. **使用内置函数**:优先使用JavaScript内置函数,性能更好
|
||||
|
||||
---
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **不要执行危险操作**:避免访问文件系统、网络请求等
|
||||
2. **参数验证**:在代码中对参数进行必要的验证
|
||||
3. **避免敏感信息**:不要在代码中硬编码密钥、密码等敏感信息
|
||||
4. **限制执行时间**:设置合理的超时时间,防止长时间运行
|
||||
`
|
||||
|
||||
doc.Content = newContent
|
||||
|
||||
if err := database.DB.Save(&doc).Error; err != nil {
|
||||
log.Printf("更新文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("动态代码文档更新成功")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
log.Println("=== 更新云端函数文档 ===")
|
||||
|
||||
var doc model.Doc
|
||||
if err := database.DB.Where("slug = ?", "dynamic-code").First(&doc).Error; err != nil {
|
||||
log.Printf("未找到文档: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
doc.Title = "云端函数接口"
|
||||
doc.Slug = "cloud-function"
|
||||
doc.Summary = "云端函数允许开发者为应用创建自定义的业务逻辑,通过JavaScript代码实现灵活的数据处理和业务规则"
|
||||
|
||||
if err := database.DB.Save(&doc).Error; err != nil {
|
||||
log.Printf("更新文档失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("云端函数文档更新成功")
|
||||
}
|
||||
Reference in New Issue
Block a user