系统概述
幽游网络验证是一套开箱即用的软件授权与会员管理系统。它对外暴露 RESTful API,你的软件(易语言、C#、Python、Go、Java 等任意能发 HTTP 请求的语言)按约定调用,就能拥有用户注册登录、会员校验、卡密兑换、心跳在线检测、商店充值等完整功能。
系统架构
系统采用经典的三层架构:
- 客户端层:你的软件通过 HTTPS 请求调用 API,请求体使用 RSA 加密 + MD5 签名保障安全
- 服务端层:Go + Gin 构建的高性能后端,处理所有业务逻辑
- 数据层:MySQL 存储业务数据,Redis 处理会话缓存和防重放
功能模块
| 模块 | 说明 |
|---|---|
| 用户系统 | 邮箱注册/登录、双 Token 认证、改密、封禁控制 |
| 会员体系 | 普通/高级/永久三档,有效期管理,过期自动降级 |
| 卡密系统 | 批量生成卡密,支持 U 币卡/会员时长卡,兑换用行锁防并发 |
| 心跳系统 | 客户端定时上报,服务端校验授权有效性,超时自动断线 |
| 商店系统 | U 币购买虚拟商品,原子事务保证一致性 |
| 签到系统 | 每日签到奖励 U 币或会员时长 |
| 充值支付 | 支持易支付、支付宝、微信,回调验签 + 幂等发币 |
| 定时任务 | 会员过期回收、卡密过期回收、超时关单等自动化任务 |
环境准备
在开始部署之前,请确保你的服务器已安装以下软件:
必需软件
| 软件 | 版本要求 | 用途 |
|---|---|---|
| MySQL | 5.6+ | 存储用户、会员、订单等业务数据 |
| Redis | 5.0+ | 会话管理、防重放、缓存 |
可选软件
| 软件 | 用途 |
|---|---|
| Go 1.22+ | 如果需要从源码编译(推荐使用预编译二进制) |
| Nginx | 反向代理,用于域名访问和 HTTPS |
| Docker | 容器化部署(可选) |
配置文件
所有配置集中在 configs/config.yaml 文件中。以下是关键配置项说明:
MySQL 数据库配置
mysql:
host: 127.0.0.1 # MySQL 服务器地址
port: 3306 # MySQL 端口
username: root # 数据库用户名
password: your_password # 数据库密码(请修改为实际密码)
database: youyou_auth # 数据库名(无需手动创建,init 命令会自动创建)
charset: utf8mb4 # 字符集(不要修改)
max_idle_conns: 10 # 连接池最大空闲连接数
max_open_conns: 100 # 连接池最大连接数
conn_max_lifetime: 1h # 连接最大存活时间
Redis 配置
redis:
host: 127.0.0.1 # Redis 服务器地址
port: 6379 # Redis 端口
password: "" # Redis 密码(宝塔默认无密码,设了密码就填这里)
db: 0 # Redis 数据库编号(0-15)
pool_size: 100 # 连接池大小
JWT 配置
jwt:
secret: "youyou-auth-secret-key-change-me" # 必须修改!用随机字符串
issuer: "youyou-auth"
access_expire: 2h # Token 有效期
refresh_expire: 168h # Refresh Token 有效期(7 天)
jwt.secret 为随机字符串!可以使用 openssl rand -hex 32 生成。邮件配置(用于注册验证码)
email:
host: smtp.qq.com # SMTP 服务器
port: 465 # SMTP 端口(SSL 用 465)
username: your_email@qq.com # 邮箱地址
password: your_smtp_code # SMTP 授权码(不是邮箱登录密码!)
from_name: "幽游网络验证"
from_addr: your_email@qq.com
QQ 邮箱获取 SMTP 授权码:登录 QQ 邮箱 → 设置 → 账户 → 找到「POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务」→ 开启「IMAP/SMTP 服务」→ 按提示用手机发短信获取授权码。
服务配置
server:
port: 8080 # 服务监听端口(需在安全组放行)
mode: release # release=生产模式 / debug=开发模式
read_timeout: 10s
write_timeout: 10s
安装与初始化
配置好 config.yaml 后,运行初始化命令创建数据库和管理员账号:
# 在项目根目录执行
go run cmd/init/main.go -config configs/config.yaml
# 或使用编译后的二进制文件
./youyou-auth-init -config configs/config.yaml
# 可选:自定义管理员账号
./youyou-auth-init -config configs/config.yaml -admin-user admin -admin-pass MyStr0ngP@ss
初始化命令会自动完成:
- 连接 MySQL — 使用 config.yaml 中的配置
- 创建数据库 — 自动创建
youyou_auth数据库(如果不存在) - 创建数据表 — 自动创建所有业务表(13 张表)
- 创建管理员账号 — 用户名 + 密码(bcrypt 加密存储)
- 输出凭证 — 显示管理员用户名和密码,请妥善保存!
管理员用户名:admin
管理员密码:a1b2c3d4
昵称:超级管理员
启动服务
初始化完成后,可以启动服务了。有三种方式:
方式一:直接运行(推荐新手)
# Linux
./youyou-auth -config configs/config.yaml
# Windows(双击运行)
youyou-auth-windows-amd64.exe
方式二:Systemd 服务(推荐生产环境)
# 创建 systemd 服务文件
cat > /etc/systemd/system/youyou-auth.service << 'EOF'
[Unit]
Description=YouYou Auth Service
After=network.target mysql.service redis.service
[Service]
Type=simple
WorkingDirectory=/www/wwwroot/youyou-auth
ExecStart=/www/wwwroot/youyou-auth/youyou-auth -config configs/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# 启用并启动服务
systemctl daemon-reload
systemctl enable youyou-auth
systemctl start youyou-auth
# 查看状态
systemctl status youyou-auth
方式三:宝塔面板
在宝塔面板的「Go 项目管理器」或「Supervisor 管理器」中添加项目:
- 执行命令:
/www/wwwroot/youyou-auth/youyou-auth -config configs/config.yaml - 运行目录:
/www/wwwroot/youyou-auth - 端口:
8080
验证服务
# 健康检查
curl http://localhost:8080/health
# 返回: {"status":"ok","service":"youyou-auth"}
# Ping 测试
curl http://localhost:8080/ping
# 返回: {"pong":"ok"}
一键安装模式
如果你不想手动编辑 config.yaml,系统提供了网页可视化安装向导。只需指定端口启动程序,打开浏览器即可完成数据库、Redis 和管理员账号的配置,特别适合初学者。
config.yaml。第一步:上传程序文件
将下载好的程序文件(如 youyou-auth-linux-amd64)上传到服务器任意目录,例如 /www/wwwroot/youyou/。同时确保 configs/ 目录下没有 config.yaml 文件(如果之前生成过,请先删除或重命名)。
# 给程序添加执行权限
chmod +x /www/wwwroot/youyou/youyou-auth-linux-amd64
# 确认 configs 目录下没有 config.yaml(有则移走)
ls /www/wwwroot/youyou/configs/
# 如果看到 config.yaml,执行:
mv /www/wwwroot/youyou/configs/config.yaml /www/wwwroot/youyou/configs/config.yaml.bak
第二步:通过宝塔面板创建 Go 项目
在宝塔面板左侧菜单找到 「Go 项目管理」,点击 「添加项目」,按以下信息填写:
/www/wwwroot/youyou/youyou-auth-linux-amd64uuyzserver8899(可自定义,确保未被占用)youyou-auth-linux-amd64 -port 8899(端口号与上一步一致)root 即可(无特殊需求也可选 www)-port 8899 是必须的,它告诉程序监听 8899 端口。端口号可以自定义,但必须与「项目端口」保持一致。第三步:打开安装向导页面
保存配置并启动项目后,在浏览器中访问:
http://你的服务器IP:8899/
程序检测到 config.yaml 不存在,会自动跳转到安装向导页面 /install。你将看到一个美观的配置表单,包含以下三个部分:
MySQL 数据库配置
| 字段 | 说明 | 默认值 | 示例 |
|---|---|---|---|
| 数据库主机 | MySQL 服务器地址 | 127.0.0.1 | 127.0.0.1 |
| 数据库端口 | MySQL 监听端口 | 3306 | 3306 |
| 数据库名 | 自动创建的数据库名称 | youyou_auth | youyou_auth |
| 表前缀 | 所有表名的前缀(可选) | (空) | yy_ |
| 用户名 | MySQL 登录用户名 | — | root |
| 密码 | MySQL 登录密码 | — | your_password |
Redis 配置
| 字段 | 说明 | 默认值 | 示例 |
|---|---|---|---|
| Redis 主机 | Redis 服务器地址 | 127.0.0.1 | 127.0.0.1 |
| Redis 端口 | Redis 监听端口 | 6379 | 6379 |
| Redis DB | 使用的数据库编号 | 0 | 0 |
| Redis 密码 | Redis 连接密码(可选) | (空) | your_redis_pass |
管理员账号配置
| 字段 | 说明 | 默认值 | 示例 |
|---|---|---|---|
| 域名 / IP | 系统对外访问地址(可选) | (空) | auth.example.com |
| 管理入口路径 | 后台管理页面路径 | admin | admin |
| 管理员用户名 | 后台登录用户名 | admin | admin |
| 管理员密码 | 后台登录密码(5-18 位) | — | your_secure_pass |
第四步:测试连接并确认安装
- 创建 MySQL 数据库(如果不存在)
- 自动建表(20+ 张业务表,含默认签到奖励配置)
- 创建管理员账号(密码加密存储)
- 生成
configs/config.yaml配置文件(含随机 JWT 密钥)
config.yaml 进入正常运行模式。重启后访问 http://你的IP:8899/admin 即可进入后台管理界面。命令行方式(非宝塔用户)
如果你没有使用宝塔面板,也可以直接在终端运行:
# Linux / macOS
./youyou-auth-linux-amd64 -port 8899
# Windows
youyou-auth-windows-amd64.exe -port 8899
启动后同样在浏览器中访问 http://localhost:8899/ 即可进入安装向导。
config.yaml 后重新启动会再次进入安装向导,请妥善保管配置文件。Nginx 反向代理
如果你想用域名访问(如 api.yourdomain.com),可以配置 Nginx 反向代理:
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
宝塔面板配置方法:网站 → 添加站点 → 设置 → 反向代理 → 目标 URL 填 http://127.0.0.1:8080,发送域名填 $host。
Docker 部署
项目根目录包含 Dockerfile,支持容器化部署:
# 构建镜像
docker build -t youyou-auth .
# 运行容器
docker run -d \
--name youyou-auth \
-p 8080:8080 \
-v $(pwd)/configs:/app/configs \
youyou-auth
API 概述
客户端 API 统一挂在 /api/v1 路由组下,所有请求默认经过安全中间件处理:
- 请求参数:使用应用专属 RSA 公钥加密后放在
X-Encrypt请求头 - 请求完整性:用 APPKEY 对参数做 MD5 签名(
X-Sign),防篡改 - 防重放:10 位时间戳 + Redis SETNX,5 分钟窗口
- 响应体:默认返回明文 JSON;可选加密响应(
X-Resp-Enc: 1)
接口分类
| 角色 | 说明 | 鉴权 |
|---|---|---|
| 🧑 终端用户 | 软件最终使用者 | 用户 Token |
| 👤 应用管理者 | 应用所有者/客服 | 用户 Token |
| 公开/服务 | 无需登录 | 无 |
/api/v1)。管理端 API(/api/admin)是后台管理系统使用的,不在本文档范围。安全机制
所有客户端请求(除两个例外)都必须携带以下安全头:
| 请求头 | 必填 | 说明 |
|---|---|---|
X-App-Id | ✅ | 应用 ID |
X-Timestamp | ✅ | 10 位 Unix 秒,与参数中的 time 一致 |
X-Sign | ✅ | MD5(排序拼接参数 + APPKEY),小写十六进制 |
X-Encrypt | ✅ | base64(RSA_PKCS1v15(应用公钥, JSON(params))) |
Authorization | 🔑 | Bearer <用户 Token>(需登录的接口) |
X-Resp-Enc | 置 1 请求加密响应(可选) |
两个例外(参数不加密)
| 接口 | 原因 |
|---|---|
GET /api/v1/public-key | 引导拉取公钥,明文请求、明文返回 |
POST /api/v1/user/avatar-upload | multipart 文件上传无法走 RSA 通道 |
签名算法
签名计算步骤:
- 收集业务参数到
params(map,全部为字符串) - 必须包含
time字段,值等于X-Timestamp - 忽略
sign、sign_type字段 - 按 key 升序排序,拼接为
k=v&k=v格式 - 末尾追加 APPKEY(无分隔符)
- 取 MD5,结果小写十六进制
import hashlib
def sign_params(params: dict, app_key: str) -> str:
# 忽略 sign / sign_type
keys = sorted(k for k in params if k not in ("sign", "sign_type"))
raw = "&".join(f"{k}={params[k]}" for k in keys) + app_key
return hashlib.md5(raw.encode()).hexdigest() # 小写十六进制
快速接入
以下是完整的接入流程,适合开发初学者参考:
第一步:获取应用凭证
登录管理后台,创建应用后系统会自动生成:
- APP_ID:应用 ID(用于
X-App-Id请求头) - APP_KEY:应用密钥(用于签名,不传输,仅本地参与 MD5 计算)
- RSA 公钥:用于加密请求体
第二步:拉取公钥
GET /api/v1/public-key?app_id=APP1234567890
// 返回明文 JSON(无需加密头)
{
"code": 0,
"data": {
"public_key": "-----BEGIN RSA PUBLIC KEY-----\n..."
}
}
第三步:构造请求(Python 示例)
import time, json, hashlib, base64, requests
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
# 配置
APP_ID = "APP1234567890"
APP_KEY = "your_app_key_here"
PUB_PEM = """-----BEGIN RSA PUBLIC KEY-----
<应用专属 RSA 公钥>
-----END RSA PUBLIC KEY-----"""
def load_pubkey(pem):
return serialization.load_pem_public_key(pem.encode())
def sign_params(params, app_key):
keys = sorted(k for k in params if k not in ("sign", "sign_type"))
raw = "&".join(f"{k}={params[k]}" for k in keys) + app_key
return hashlib.md5(raw.encode()).hexdigest()
def rsa_encrypt(plain_json, pub_pem):
pub = load_pubkey(pub_pem)
ct = pub.encrypt(plain_json.encode(), padding.PKCS1v15())
return base64.b64encode(ct).decode()
def build_headers(params, token=None):
ts = str(int(time.time()))
params = dict(params)
params["time"] = ts # 必须带 time,且与 X-Timestamp 一致
sign = sign_params(params, APP_KEY)
x_encrypt = rsa_encrypt(json.dumps(params), PUB_PEM)
h = {
"X-App-Id": APP_ID,
"X-Timestamp": ts,
"X-Sign": sign,
"X-Encrypt": x_encrypt,
"Content-Type": "application/x-www-form-urlencoded",
}
if token:
h["Authorization"] = f"Bearer {token}"
return h
params 必须包含 time 字段,且其值等于 X-Timestamp。否则签名比对会失败,返回 CodeSignError。第四步:发送验证码并注册
# 发送验证码
params = {"email": "user@example.com", "type": "register"}
headers = build_headers(params)
r = requests.post("https://your-host/api/v1/email/send-code", headers=headers)
print(r.json())
# 注册(收到验证码后)
params = {
"email": "user@example.com",
"password": "123456",
"nickname": "测试用户",
"verify_code": "123456", # 邮箱收到的验证码
}
headers = build_headers(params)
r = requests.post("https://your-host/api/v1/user/register", headers=headers)
result = r.json()
print(result)
# 成功返回: {"code":0,"msg":"注册成功","data":{"uid":..,"token":..,"refresh_token":..}}
# 保存 token 用于后续请求
USER_TOKEN = result["data"]["token"]
第五步:调用需登录接口
# 查询用户资料
params = {}
headers = build_headers(params, token=USER_TOKEN)
r = requests.get("https://your-host/api/v1/user/profile", headers=headers)
print(r.json())
# 签到
params = {}
headers = build_headers(params, token=USER_TOKEN)
r = requests.post("https://your-host/api/v1/checkin/do", headers=headers)
print(r.json())
用户系统
发送验证码
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 必填 | 邮箱地址 |
type | string | 必填 | 验证码用途:register / reset_password / change_password |
用户注册
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 必填 | 邮箱地址 |
password | string | 必填 | 登录密码(≥6 位) |
nickname | string | 必填 | 用户昵称 |
verify_code | string | 必填 | 邮箱验证码 |
用户登录
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 必填 | 邮箱地址 |
password | string | 必填 | 登录密码 |
udid | string | 选填 | 设备唯一标识(建议传,用于多端管理和按设备踢人) |
查询用户资料
需要登录态(携带有效用户 Token)。
修改密码
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
old_password | string | 必填 | 旧密码 |
new_password | string | 必填 | 新密码(≥6 位) |
verify_code | string | 必填 | 邮箱验证码 |
心跳系统
心跳系统用于检测用户是否还在线,防止多开/盗用。客户端应定时上报心跳。
心跳上报
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
udid | string | 必填 | 设备唯一标识 |
client_ver | string | 选填 | 客户端版本号(用于版本校验提示) |
推荐心跳间隔:根据返回的 next_interval 字段定时发送,通常 30-60 秒一次。
心跳状态查询
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
udid | string | 必填 | 设备唯一标识 |
断开授权
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
udid | string | 必填 | 设备唯一标识 |
会员系统
查询会员状态
需要登录态。返回当前用户的会员等级和有效期。
会员权限校验
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
udid | string | 选填 | 设备标识(用于踢出检查) |
feature | string | 选填 | 功能标识(门禁判断依据) |
服务间会员查询
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
uid | uint | 必填 | 目标用户 UID |
无需用户 Token,用于第三方服务端按 UID 查会员状态。
卡密系统
兑换卡密
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
card_no | string | 必填 | 卡号 |
兑换到当前用户。已用/不存在的卡密会报错。
查询卡密信息
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
card_no | string | 必填 | 卡号 |
我的卡密列表
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
status | int | 选填 | 状态筛选:0=未使用 1=已使用 2=全部(默认 2) |
page | int | 选填 | 页码(默认 1) |
page_size | int | 选填 | 每页条数(默认 20) |
商店系统
商品列表
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | int | 选填 | 页码(默认 1) |
page_size | int | 选填 | 每页条数(默认 20,上限 100) |
仅返回上架商品。
购买商品
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
product_id | uint | 必填 | 商品 ID |
quantity | uint | 选填 | 购买数量(默认 1) |
扣 U 币/库存。余额不足/限购/下架等会返回细分错误码。
签到系统
每日签到
无需参数。重复签到会报错 CodeAlreadyCheckin。
签到状态
返回今日是否已签到。
签到历史
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | int | 选填 | 页码(默认 1) |
page_size | int | 选填 | 每页条数(默认 20) |
U 币充值
U 币充值是「人民币 → U 币」的兑换通道。支持易支付、支付宝、微信。
充值流程
- 拉取套餐:
GET /charge/packages→ 展示套餐 - 创建订单:
POST /charge/create→ 返回支付参数 - 拉起支付:按
pay.method处理(跳转/扫码/调起) - 轮询结果:
GET /charge/status→ 直到status=1
创建充值订单
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
channel | string | 必填 | 支付渠道:third_epay / alipay / wechat |
package_id | uint | 必填 | 充值套餐 ID |
mode | string | 选填 | 拉起方式:redirect(默认) / qrcode / jsapi / app |
openid | string | 选填 | 微信 jsapi 调起时必传 |
查询充值套餐
返回本应用已上架充值套餐列表。
查询订单状态
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
order_no | string | 必填 | 充值订单号 |
money=3000 表示 ¥30.00。前端展示需 /100 转元。订单状态枚举
| status | 含义 | 客户端表现 |
|---|---|---|
0 | 待支付 | 已拉起支付页,轮询 status 等待变为 1 |
1 | 已支付 | U 币已到账 |
2 | 已关闭 | 订单已关闭,不可再支付 |
错误码
以下是常见错误码及其含义:
| 错误码 | 含义 | 触发位置 |
|---|---|---|
CodeInvalidParams | 参数缺失/非法、RSA 解密失败 | SecurityMiddleware / handler |
CodeExpired | 时间戳超出窗口(5 分钟) | SecurityMiddleware |
CodeAppNotFound | 应用不存在 | SecurityMiddleware |
CodeAppDisabled | 应用已禁用 | SecurityMiddleware |
CodeSignError | 签名不匹配 | SecurityMiddleware |
CodeReplayAttack | 重放攻击(重复请求) | SecurityMiddleware |
CodeUnauthorized | Token 无效/缺失 | AuthMiddleware |
CodeUserBanned | 用户被封禁 | AuthMiddleware |
CodeForbidden | 功能开关关闭(注册/登录) | user_handler |
CodeAlreadyCheckin | 重复签到 | checkin_handler |
CodeCardNotFound | 卡密不存在 | cardkey_handler |
CodeCardUsed | 卡密已使用 | cardkey_handler |
CodeInsufficientUcoin | U 币余额不足 | store_handler |
CodeStockInsufficient | 库存不足 | store_handler |
CodePurchaseLimit | 超过限购数量 | store_handler |
CodeSignError 时,检查 params 是否包含 time 字段,且其值与 X-Timestamp 一致。