Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18c667bf21 |
+33
-14
@@ -76,6 +76,29 @@ type (
|
|||||||
GiftAmount int64 `json:"gift_amount"`
|
GiftAmount int64 `json:"gift_amount"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
}
|
}
|
||||||
|
UserSubscribeDetail {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
User User `json:"user"`
|
||||||
|
OrderId int64 `json:"order_id"`
|
||||||
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
|
Subscribe Subscribe `json:"subscribe"`
|
||||||
|
NodeGroupId int64 `json:"node_group_id"`
|
||||||
|
GroupLocked bool `json:"group_locked"`
|
||||||
|
StartTime int64 `json:"start_time"`
|
||||||
|
ExpireTime int64 `json:"expire_time"`
|
||||||
|
ResetTime int64 `json:"reset_time"`
|
||||||
|
Traffic int64 `json:"traffic"`
|
||||||
|
Download int64 `json:"download"`
|
||||||
|
Upload int64 `json:"upload"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Status uint8 `json:"status"`
|
||||||
|
EffectiveSpeed int64 `json:"effective_speed"`
|
||||||
|
IsThrottled bool `json:"is_throttled"`
|
||||||
|
ThrottleRule string `json:"throttle_rule,omitempty"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
BatchDeleteUserRequest {
|
BatchDeleteUserRequest {
|
||||||
Ids []int64 `json:"ids" validate:"required"`
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
}
|
}
|
||||||
@@ -135,22 +158,18 @@ type (
|
|||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
}
|
}
|
||||||
CreateUserSubscribeRequest {
|
CreateUserSubscribeRequest {
|
||||||
UserId int64 `json:"user_id"`
|
UserId int64 `json:"user_id"`
|
||||||
ExpiredAt int64 `json:"expired_at"`
|
ExpiredAt int64 `json:"expired_at"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
SpeedLimit int64 `json:"speed_limit,optional"`
|
|
||||||
TrafficLimit string `json:"traffic_limit,optional"`
|
|
||||||
}
|
}
|
||||||
UpdateUserSubscribeRequest {
|
UpdateUserSubscribeRequest {
|
||||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
ExpiredAt int64 `json:"expired_at"`
|
ExpiredAt int64 `json:"expired_at"`
|
||||||
Upload int64 `json:"upload"`
|
Upload int64 `json:"upload"`
|
||||||
Download int64 `json:"download"`
|
Download int64 `json:"download"`
|
||||||
SpeedLimit *int64 `json:"speed_limit,omitempty"`
|
|
||||||
TrafficLimit *string `json:"traffic_limit,omitempty"`
|
|
||||||
}
|
}
|
||||||
GetUserLoginLogsRequest {
|
GetUserLoginLogsRequest {
|
||||||
Page int `form:"page"`
|
Page int `form:"page"`
|
||||||
|
|||||||
+7
-36
@@ -171,13 +171,13 @@ type (
|
|||||||
DeviceLimit int64 `json:"device_limit"`
|
DeviceLimit int64 `json:"device_limit"`
|
||||||
}
|
}
|
||||||
VerifyConfig {
|
VerifyConfig {
|
||||||
CaptchaType string `json:"captcha_type"` // local or turnstile
|
CaptchaType string `json:"captcha_type"` // local or turnstile
|
||||||
TurnstileSiteKey string `json:"turnstile_site_key"`
|
TurnstileSiteKey string `json:"turnstile_site_key"`
|
||||||
TurnstileSecret string `json:"turnstile_secret"`
|
TurnstileSecret string `json:"turnstile_secret"`
|
||||||
EnableUserLoginCaptcha bool `json:"enable_user_login_captcha"` // User login captcha
|
EnableUserLoginCaptcha bool `json:"enable_user_login_captcha"` // User login captcha
|
||||||
EnableUserRegisterCaptcha bool `json:"enable_user_register_captcha"` // User register captcha
|
EnableUserRegisterCaptcha bool `json:"enable_user_register_captcha"` // User register captcha
|
||||||
EnableAdminLoginCaptcha bool `json:"enable_admin_login_captcha"` // Admin login captcha
|
EnableAdminLoginCaptcha bool `json:"enable_admin_login_captcha"` // Admin login captcha
|
||||||
EnableUserResetPasswordCaptcha bool `json:"enable_user_reset_password_captcha"` // User reset password captcha
|
EnableUserResetPasswordCaptcha bool `json:"enable_user_reset_password_captcha"` // User reset password captcha
|
||||||
}
|
}
|
||||||
NodeConfig {
|
NodeConfig {
|
||||||
NodeSecret string `json:"node_secret"`
|
NodeSecret string `json:"node_secret"`
|
||||||
@@ -536,35 +536,6 @@ type (
|
|||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
UserSubscribeDetail {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
UserId int64 `json:"user_id"`
|
|
||||||
User User `json:"user"`
|
|
||||||
OrderId int64 `json:"order_id"`
|
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
|
||||||
Subscribe Subscribe `json:"subscribe"`
|
|
||||||
NodeGroupId int64 `json:"node_group_id"`
|
|
||||||
NodeGroupName string `json:"node_group_name"`
|
|
||||||
GroupLocked bool `json:"group_locked"`
|
|
||||||
StartTime int64 `json:"start_time"`
|
|
||||||
ExpireTime int64 `json:"expire_time"`
|
|
||||||
ResetTime int64 `json:"reset_time"`
|
|
||||||
Traffic int64 `json:"traffic"`
|
|
||||||
Download int64 `json:"download"`
|
|
||||||
Upload int64 `json:"upload"`
|
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
|
||||||
TrafficLimit []TrafficLimit `json:"user_traffic_limit"`
|
|
||||||
PlanSpeedLimit int64 `json:"plan_speed_limit"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
Status uint8 `json:"status"`
|
|
||||||
EffectiveSpeed int64 `json:"effective_speed"`
|
|
||||||
IsThrottled bool `json:"is_throttled"`
|
|
||||||
ThrottleRule string `json:"throttle_rule,omitempty"`
|
|
||||||
ThrottleStart int64 `json:"throttle_start,omitempty"`
|
|
||||||
ThrottleEnd int64 `json:"throttle_end,omitempty"`
|
|
||||||
CreatedAt int64 `json:"created_at"`
|
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
|
||||||
}
|
|
||||||
UserAffiliate {
|
UserAffiliate {
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
-- Purpose: Rollback user-level speed limit overrides from user_subscribe
|
|
||||||
|
|
||||||
ALTER TABLE `user_subscribe`
|
|
||||||
DROP COLUMN IF EXISTS `traffic_limit`,
|
|
||||||
DROP COLUMN IF EXISTS `speed_limit`;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
-- Purpose: Add user-level speed limit overrides to user_subscribe
|
|
||||||
|
|
||||||
SET @column_exists = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'user_subscribe'
|
|
||||||
AND COLUMN_NAME = 'speed_limit'
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @sql = IF(
|
|
||||||
@column_exists = 0,
|
|
||||||
'ALTER TABLE `user_subscribe` ADD COLUMN `speed_limit` int NOT NULL DEFAULT 0 COMMENT ''User-level speed limit override (Mbps, 0=use plan default)'' AFTER `upload`',
|
|
||||||
'SELECT ''Column speed_limit already exists in user_subscribe table'''
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
SET @column_exists = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'user_subscribe'
|
|
||||||
AND COLUMN_NAME = 'traffic_limit'
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @sql = IF(
|
|
||||||
@column_exists = 0,
|
|
||||||
'ALTER TABLE `user_subscribe` ADD COLUMN `traffic_limit` text DEFAULT NULL COMMENT ''User-level traffic limit rules override (JSON, NULL=use plan default)'' AFTER `speed_limit`',
|
|
||||||
'SELECT ''Column traffic_limit already exists in user_subscribe table'''
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
-- Purpose: Rollback promo system tables and order promo snapshot columns
|
|
||||||
|
|
||||||
SET @column_exists = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'order'
|
|
||||||
AND COLUMN_NAME = 'promo_discount'
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @sql = IF(
|
|
||||||
@column_exists = 1,
|
|
||||||
'ALTER TABLE `order` DROP COLUMN `promo_discount`',
|
|
||||||
'SELECT ''Column promo_discount does not exist in order table'''
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
SET @column_exists = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'order'
|
|
||||||
AND COLUMN_NAME = 'promo_rule_id'
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @sql = IF(
|
|
||||||
@column_exists = 1,
|
|
||||||
'ALTER TABLE `order` DROP COLUMN `promo_rule_id`',
|
|
||||||
'SELECT ''Column promo_rule_id does not exist in order table'''
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `promo_usage`;
|
|
||||||
DROP TABLE IF EXISTS `subscribe_promo`;
|
|
||||||
DROP TABLE IF EXISTS `promo_rule`;
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
-- Purpose: Add promo system tables and order promo snapshot columns
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `promo_rule` (
|
|
||||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '规则名称,如"新客7天优惠"',
|
|
||||||
`type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '规则类型:new_user / inactive_user / campaign',
|
|
||||||
`params` JSON NOT NULL COMMENT '类型专属参数',
|
|
||||||
`priority` INT NOT NULL DEFAULT 0 COMMENT '优先级,数值越大越优先匹配',
|
|
||||||
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
|
|
||||||
`start_time` DATETIME DEFAULT NULL COMMENT '生效开始时间,NULL=立即生效',
|
|
||||||
`end_time` DATETIME DEFAULT NULL COMMENT '生效结束时间,NULL=永不过期',
|
|
||||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
`deleted_at` DATETIME DEFAULT NULL COMMENT '软删除时间',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_enabled_priority` (`enabled`, `priority` DESC)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销规则表';
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `subscribe_promo` (
|
|
||||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`subscribe_id` BIGINT UNSIGNED NOT NULL COMMENT '套餐规格 ID',
|
|
||||||
`promo_rule_id` BIGINT UNSIGNED NOT NULL COMMENT '促销规则 ID',
|
|
||||||
`promo_price` BIGINT NOT NULL DEFAULT 0 COMMENT '该规格在此规则下的优惠价(分)',
|
|
||||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_subscribe_rule` (`subscribe_id`, `promo_rule_id`),
|
|
||||||
KEY `idx_promo_rule_id` (`promo_rule_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规格促销价表';
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `promo_usage` (
|
|
||||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户 ID',
|
|
||||||
`promo_rule_id` BIGINT UNSIGNED NOT NULL COMMENT '使用的规则 ID',
|
|
||||||
`subscribe_id` BIGINT UNSIGNED NOT NULL COMMENT '购买的规格 ID',
|
|
||||||
`order_no` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '关联订单号',
|
|
||||||
`promo_price` BIGINT NOT NULL DEFAULT 0 COMMENT '使用时的促销单价(分)',
|
|
||||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_user_rule` (`user_id`, `promo_rule_id`),
|
|
||||||
KEY `idx_order_no` (`order_no`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销使用记录表';
|
|
||||||
|
|
||||||
SET @column_exists = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'order'
|
|
||||||
AND COLUMN_NAME = 'promo_rule_id'
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @sql = IF(
|
|
||||||
@column_exists = 0,
|
|
||||||
'ALTER TABLE `order` ADD COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''促销规则ID, 0=未使用促销''',
|
|
||||||
'SELECT ''Column promo_rule_id already exists in order table'''
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
SET @column_exists = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'order'
|
|
||||||
AND COLUMN_NAME = 'promo_discount'
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @sql = IF(
|
|
||||||
@column_exists = 0,
|
|
||||||
'ALTER TABLE `order` ADD COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT ''促销优惠金额(分)''',
|
|
||||||
'SELECT ''Column promo_discount already exists in order table'''
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
@@ -101,8 +101,6 @@ type Subscribe struct {
|
|||||||
Traffic int64 `gorm:"default:0;comment:Traffic"`
|
Traffic int64 `gorm:"default:0;comment:Traffic"`
|
||||||
Download int64 `gorm:"default:0;comment:Download Traffic"`
|
Download int64 `gorm:"default:0;comment:Download Traffic"`
|
||||||
Upload int64 `gorm:"default:0;comment:Upload Traffic"`
|
Upload int64 `gorm:"default:0;comment:Upload Traffic"`
|
||||||
SpeedLimit int64 `gorm:"default:0;comment:User-level speed limit override (Mbps, 0=use plan default)"`
|
|
||||||
TrafficLimit string `gorm:"type:text;default:null;comment:User-level traffic limit rules override (JSON)"`
|
|
||||||
ExpiredDownload int64 `gorm:"default:0;comment:Expired period download traffic (bytes)"`
|
ExpiredDownload int64 `gorm:"default:0;comment:Expired period download traffic (bytes)"`
|
||||||
ExpiredUpload int64 `gorm:"default:0;comment:Expired period upload traffic (bytes)"`
|
ExpiredUpload int64 `gorm:"default:0;comment:Expired period upload traffic (bytes)"`
|
||||||
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
||||||
|
|||||||
+34
-41
@@ -541,12 +541,10 @@ type CreateUserRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserSubscribeRequest struct {
|
type CreateUserSubscribeRequest struct {
|
||||||
UserId int64 `json:"user_id"`
|
UserId int64 `json:"user_id"`
|
||||||
ExpiredAt int64 `json:"expired_at"`
|
ExpiredAt int64 `json:"expired_at"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
SpeedLimit int64 `json:"speed_limit,optional"`
|
|
||||||
TrafficLimit string `json:"traffic_limit,optional"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUserTicketFollowRequest struct {
|
type CreateUserTicketFollowRequest struct {
|
||||||
@@ -3319,14 +3317,12 @@ type UpdateUserSubscribeNoteRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserSubscribeRequest struct {
|
type UpdateUserSubscribeRequest struct {
|
||||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
ExpiredAt int64 `json:"expired_at"`
|
ExpiredAt int64 `json:"expired_at"`
|
||||||
Upload int64 `json:"upload"`
|
Upload int64 `json:"upload"`
|
||||||
Download int64 `json:"download"`
|
Download int64 `json:"download"`
|
||||||
SpeedLimit *int64 `json:"speed_limit,omitempty"`
|
|
||||||
TrafficLimit *string `json:"traffic_limit,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserTicketStatusRequest struct {
|
type UpdateUserTicketStatusRequest struct {
|
||||||
@@ -3478,33 +3474,30 @@ type UserSubscribe struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserSubscribeDetail struct {
|
type UserSubscribeDetail struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
UserId int64 `json:"user_id"`
|
UserId int64 `json:"user_id"`
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
OrderId int64 `json:"order_id"`
|
OrderId int64 `json:"order_id"`
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
Subscribe Subscribe `json:"subscribe"`
|
Subscribe Subscribe `json:"subscribe"`
|
||||||
NodeGroupId int64 `json:"node_group_id"`
|
NodeGroupId int64 `json:"node_group_id"`
|
||||||
NodeGroupName string `json:"node_group_name"`
|
NodeGroupName string `json:"node_group_name"`
|
||||||
GroupLocked bool `json:"group_locked"`
|
GroupLocked bool `json:"group_locked"`
|
||||||
StartTime int64 `json:"start_time"`
|
StartTime int64 `json:"start_time"`
|
||||||
ExpireTime int64 `json:"expire_time"`
|
ExpireTime int64 `json:"expire_time"`
|
||||||
ResetTime int64 `json:"reset_time"`
|
ResetTime int64 `json:"reset_time"`
|
||||||
Traffic int64 `json:"traffic"`
|
Traffic int64 `json:"traffic"`
|
||||||
Download int64 `json:"download"`
|
Download int64 `json:"download"`
|
||||||
Upload int64 `json:"upload"`
|
Upload int64 `json:"upload"`
|
||||||
SpeedLimit int64 `json:"speed_limit"`
|
Token string `json:"token"`
|
||||||
TrafficLimit []TrafficLimit `json:"user_traffic_limit"`
|
Status uint8 `json:"status"`
|
||||||
PlanSpeedLimit int64 `json:"plan_speed_limit"`
|
EffectiveSpeed int64 `json:"effective_speed"`
|
||||||
Token string `json:"token"`
|
IsThrottled bool `json:"is_throttled"`
|
||||||
Status uint8 `json:"status"`
|
ThrottleRule string `json:"throttle_rule,omitempty"`
|
||||||
EffectiveSpeed int64 `json:"effective_speed"`
|
ThrottleStart int64 `json:"throttle_start,omitempty"`
|
||||||
IsThrottled bool `json:"is_throttled"`
|
ThrottleEnd int64 `json:"throttle_end,omitempty"`
|
||||||
ThrottleRule string `json:"throttle_rule,omitempty"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
ThrottleStart int64 `json:"throttle_start,omitempty"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
ThrottleEnd int64 `json:"throttle_end,omitempty"`
|
|
||||||
CreatedAt int64 `json:"created_at"`
|
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSubscribeInfo struct {
|
type UserSubscribeInfo struct {
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Hifast 上传链路自检 Runbook
|
||||||
|
|
||||||
|
本文档用于排查 `POST /v1/public/file/upload`、`/upload/init`、`/upload/complete` 相关问题。上传链路依赖:
|
||||||
|
|
||||||
|
`客户端 -> Nginx -> ppanel-server -> S3/RustFS endpoint`
|
||||||
|
|
||||||
|
## 1. 先确认业务服务健康
|
||||||
|
|
||||||
|
在测试机执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -k -sS -m 8 -i https://tapi.hifast.biz/v1/common/heartbeat | head -30
|
||||||
|
docker ps --format '{{.Names}} {{.Image}} {{.Status}} {{.Ports}}'
|
||||||
|
ss -lntp | grep -E ':(80|443|8080|3306|6379)'
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:
|
||||||
|
|
||||||
|
- `/v1/common/heartbeat` 返回 `HTTP 200`,业务 `code=200`
|
||||||
|
- `ppanel-server`、`ppanel-mysql`、`ppanel-redis` 运行中
|
||||||
|
- Nginx 监听 `80/443`,ppanel 监听 `8080`
|
||||||
|
|
||||||
|
## 2. 检查上传配置
|
||||||
|
|
||||||
|
测试环境当前业务容器使用挂载配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker inspect ppanel-server --format '{{json .Mounts}}'
|
||||||
|
docker cp ppanel-server:/app/etc/ppanel.yaml /tmp/ppanel.yaml
|
||||||
|
awk '/^S3:/{flag=1} flag && /^[A-Za-z0-9_]+:/{if($1!="S3:") exit} flag{print}' /tmp/ppanel.yaml \
|
||||||
|
| sed -E 's/(AccessKey:).*/\1 ***REDACTED***/; s/(SecretKey:).*/\1 ***REDACTED***/; s/(SessionToken:).*/\1 ***REDACTED***/'
|
||||||
|
```
|
||||||
|
|
||||||
|
重点确认:
|
||||||
|
|
||||||
|
- `S3.Enable: true`
|
||||||
|
- `S3.Endpoint` 是 ppanel-server 所在机器可以访问的地址
|
||||||
|
- `S3.Bucket` 存在且凭据有 `PutObject` / `HeadObject` 权限
|
||||||
|
- `S3.UsePathStyle` 与对象存储实现一致
|
||||||
|
- `S3.PublicBaseURL` 只影响返回 URL,不代表写入 endpoint
|
||||||
|
|
||||||
|
## 3. 检查对象存储连通性
|
||||||
|
|
||||||
|
先从测试机主机网络检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
endpoint='http://107.173.50.22:5017'
|
||||||
|
host='107.173.50.22'
|
||||||
|
port='5017'
|
||||||
|
|
||||||
|
timeout 5 bash -lc "</dev/tcp/${host}/${port}" && echo tcp_ok || echo tcp_fail
|
||||||
|
curl -v --connect-timeout 5 --max-time 10 -I "${endpoint}/"
|
||||||
|
curl -v --connect-timeout 5 --max-time 10 -I "${endpoint}/hifastvpn"
|
||||||
|
ip route get "${host}"
|
||||||
|
```
|
||||||
|
|
||||||
|
判定:
|
||||||
|
|
||||||
|
- TCP 超时:优先查对象存储主机防火墙、安全组、服务监听、源 IP 白名单
|
||||||
|
- TCP 通但 HTTP 空响应:优先查 endpoint 协议、反向代理、对象存储进程健康
|
||||||
|
- 返回 S3 XML/鉴权错误:网络已通,再查 AK/SK、bucket、path-style 配置
|
||||||
|
|
||||||
|
## 4. 检查应用日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs --since 3h ppanel-server 2>&1 \
|
||||||
|
| grep -Ei 'put object failed|upload|s3|timeout|context canceled|error' \
|
||||||
|
| tail -120
|
||||||
|
```
|
||||||
|
|
||||||
|
典型根因:
|
||||||
|
|
||||||
|
- `put object failed ... context canceled` 且请求耗时约 60 秒:上游对象存储请求没有在 Nginx upstream timeout 前完成,通常是 `S3.Endpoint` 不可达或对象存储服务 hang。
|
||||||
|
- 立即返回业务错误:通常是 `S3.Enable=false`、Content-Type 不在白名单、bucket/权限错误。
|
||||||
|
|
||||||
|
## 5. Nginx 检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -RIn 'proxy_pass\|proxy_read_timeout\|client_max_body_size\|tapi.hifast.biz' /etc/nginx/sites-enabled /etc/nginx/conf.d
|
||||||
|
nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:单纯放大 `proxy_read_timeout` 只能掩盖现象,不能修复对象存储不可达。
|
||||||
|
|
||||||
|
## 6. 修复与回滚
|
||||||
|
|
||||||
|
修复优先级:
|
||||||
|
|
||||||
|
1. 恢复对象存储 endpoint 的网络访问或服务监听。
|
||||||
|
2. 如 endpoint 已迁移,更新 `/root/bindbox/configs/ppanel.yaml` 的 `S3.Endpoint`,重启 `ppanel-server`。
|
||||||
|
3. 如果凭据或 bucket 变更,同步更新 `S3.AccessKey`、`S3.SecretKey`、`S3.Bucket`、`S3.UsePathStyle`。
|
||||||
|
|
||||||
|
改配置前先备份:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp /root/bindbox/configs/ppanel.yaml /root/bindbox/configs/ppanel.yaml.bak.$(date +%Y%m%d%H%M%S)
|
||||||
|
docker restart ppanel-server
|
||||||
|
```
|
||||||
|
|
||||||
|
回滚:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp /root/bindbox/configs/ppanel.yaml.bak.YYYYMMDDHHMMSS /root/bindbox/configs/ppanel.yaml
|
||||||
|
docker restart ppanel-server
|
||||||
|
curl -k -sS -m 8 -i https://tapi.hifast.biz/v1/common/heartbeat | head -30
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 验证
|
||||||
|
|
||||||
|
使用有效 JWT 和有效签名复测:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -i -X POST 'https://tapi.hifast.biz/v1/public/file/upload' \
|
||||||
|
-H 'x-app-id: android-client' \
|
||||||
|
-H 'Authorization: <client-jwt>' \
|
||||||
|
-H 'x-signature-version: v101' \
|
||||||
|
-H 'login-type: device' \
|
||||||
|
-H 'x-signature: <sig>' \
|
||||||
|
-H 'x-nonce: <nonce>' \
|
||||||
|
-H 'x-timestamp: <ts>' \
|
||||||
|
-F 'biz_type=app-package' \
|
||||||
|
-F 'file=@nconf.zip;type=application/zip' \
|
||||||
|
--max-time 120
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:
|
||||||
|
|
||||||
|
- HTTP 不再出现 `504 Gateway Time-out`
|
||||||
|
- 业务响应 `code=200`
|
||||||
|
- `docker logs ppanel-server` 不再出现新的 `put object failed`
|
||||||
Reference in New Issue
Block a user