init commit

This commit is contained in:
2025-09-27 10:17:16 +08:00
commit 7eee7bcd3b
1022 changed files with 78271 additions and 0 deletions
+277
View File
@@ -0,0 +1,277 @@
package initialize
import (
"database/sql"
"embed"
"fmt"
"html/template"
"log"
"net/http"
"os"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/driver/mysql"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/perfect-panel/ppanel-server/initialize/migrate"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/pkg/conf"
"github.com/perfect-panel/ppanel-server/pkg/orm"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
)
//go:embed templates/*.html
var templateFS embed.FS
var initStatus = make(chan bool)
var configPath string
func Config(path string) (chan bool, *http.Server) {
// Set the configuration file path
configPath = path
// Create a new Gin instance
r := gin.Default()
// Create a new HTTP server
server := &http.Server{
Addr: ":8080",
Handler: r,
}
// Load templates
tmpl := template.Must(template.ParseFS(templateFS, "templates/*.html"))
r.SetHTMLTemplate(tmpl)
r.GET("/init", handleInit)
r.POST("/init/config", handleInitConfig)
r.POST("/init/mysql/test", HandleMySQLTest)
r.POST("/init/redis/test", HandleRedisTest)
// Handle 404
r.NoRoute(func(c *gin.Context) {
c.Redirect(http.StatusFound, "/init")
})
go func(server *http.Server) {
// Start the server
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("listen: %s\n", err)
}
}(server)
return initStatus, server
}
func handleInit(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
func handleInitConfig(c *gin.Context) {
// Load configuration file
var cfg config.File
conf.MustLoad(configPath, &cfg)
var request struct {
AdminEmail string `json:"adminEmail"`
AdminPassword string `json:"adminPassword"`
MysqlHost string `json:"mysqlHost"`
MysqlPort string `json:"mysqlPort"`
MysqlDatabase string `json:"mysqlDatabase"`
MysqlUser string `json:"mysqlUser"`
MysqlPassword string `json:"mysqlPassword"`
RedisHost string `json:"redisHost"`
RedisPort string `json:"redisPort"`
RedisPassword string `json:"redisPassword"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "Invalid request",
"data": nil,
})
c.Abort()
return
}
cfg.Debug = false
// jwt secret
cfg.JwtAuth.AccessSecret = uuid.New().String()
// mysql
cfg.MySQL.Addr = fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort)
cfg.MySQL.Dbname = request.MysqlDatabase
cfg.MySQL.Username = request.MysqlUser
cfg.MySQL.Password = request.MysqlPassword
// redis
cfg.Redis.Host = fmt.Sprintf("%s:%s", request.RedisHost, request.RedisPort)
cfg.Redis.Pass = request.RedisPassword
// save config
fileData, err := yaml.Marshal(cfg)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "Configuration initialization failed",
"data": nil,
})
c.Abort()
return
}
// create mysql connection
db, err := orm.ConnectMysql(orm.Mysql{
Config: orm.Config{
Addr: fmt.Sprintf("%s:%s", request.MysqlHost, request.MysqlPort),
Username: request.MysqlUser,
Password: request.MysqlPassword,
Dbname: request.MysqlDatabase,
Config: "charset%3Dutf8mb4%26parseTime%3Dtrue%26loc%3DLocal",
MaxIdleConns: 10,
MaxOpenConns: 10,
SlowThreshold: 1000,
},
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "MySQL connection failed",
"data": nil,
})
c.Abort()
return
}
// init
if err := initMysql(db, request.AdminEmail, request.AdminPassword); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "MySQL initialization failed",
"data": nil,
})
c.Abort()
return
}
// write to file
if err := os.WriteFile(configPath, fileData, 0644); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "Configuration initialization failed",
"data": nil,
})
c.Abort()
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "Configuration initialized",
"status": true,
})
initStatus <- true
}
func HandleMySQLTest(c *gin.Context) {
var request struct {
Host string `json:"host"`
Port string `json:"port"`
Database string `json:"database"`
User string `json:"user"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "Invalid request",
"data": nil,
})
c.Abort()
return
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", request.User, request.Password, request.Host, request.Port, request.Database)
var status = true
var message string
var tx *sql.DB
var tables []string
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
logger.Errorf("connect mysql failed, err: %v\n", err.Error())
status = false
message = "MySQL connection failed"
goto result
}
tx, _ = db.DB()
if err := tx.Ping(); err != nil {
logger.Errorf("ping mysql failed, err: %v\n", err.Error())
status = false
message = "MySQL connection failed"
}
tables, err = db.Migrator().GetTables()
if err != nil {
logger.Errorf("database table check failed, err: %v\n", err.Error())
status = false
message = "Database table check failed"
goto result
}
if len(tables) > 0 {
status = false
message = "The database contains existing data. Please clear it before proceeding with the installation."
goto result
}
result:
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": message,
"status": status,
})
}
func HandleRedisTest(c *gin.Context) {
var request struct {
Host string `json:"host"`
Port string `json:"port"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "Invalid request",
"data": nil,
})
c.Abort()
return
}
if err := tool.RedisPing(fmt.Sprintf("%s:%s", request.Host, request.Port), request.Password, 0); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": nil,
"status": false,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": nil,
"status": true,
})
}
func initMysql(tx *gorm.DB, email, password string) error {
tables, err := tx.Migrator().GetTables()
if err != nil {
return fmt.Errorf("database table validation failed: %w", err)
}
if len(tables) > 0 {
return errors.New("the database contains existing data. Please clear it before proceeding with the installation")
}
if err := migrate.InitPPanelSQL(tx); err != nil {
return fmt.Errorf("failed to initialize database: %w", err)
}
if err := migrate.CreateAdminUser(email, password, tx); err != nil {
return fmt.Errorf("failed to create admin user: %w", err)
}
return nil
}
+33
View File
@@ -0,0 +1,33 @@
package initialize
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
// Email get email smtp config
func Email(ctx *svc.ServiceContext) {
logger.Debug("Email config initialization")
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "email")
if err != nil {
panic(fmt.Sprintf("failed to find email auth method: %v", err.Error()))
}
var cfg config.EmailConfig
var emailConfig = new(auth.EmailAuthConfig)
if err := emailConfig.Unmarshal(method.Config); err != nil {
panic(fmt.Sprintf("failed to unmarshal email auth config: %v", err.Error()))
}
tool.DeepCopy(&cfg, emailConfig)
cfg.Enable = *method.Enabled
value, _ := json.Marshal(emailConfig.PlatformConfig)
cfg.PlatformConfig = string(value)
ctx.Config.Email = cfg
}
+22
View File
@@ -0,0 +1,22 @@
package initialize
import "github.com/perfect-panel/ppanel-server/internal/svc"
func StartInitSystemConfig(svc *svc.ServiceContext) {
// Initialize the system configuration
Mysql(svc)
VerifyVersion(svc)
Site(svc)
Node(svc)
Email(svc)
Invite(svc)
Verify(svc)
Subscribe(svc)
Register(svc)
Mobile(svc)
TrafficDataToRedis(svc)
if !svc.Config.Debug {
Telegram(svc)
}
}
+23
View File
@@ -0,0 +1,23 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Invite(ctx *svc.ServiceContext) {
// Initialize the system configuration
logger.Debug("Register config initialization")
configs, err := ctx.SystemModel.GetInviteConfig(context.Background())
if err != nil {
logger.Error("[Init Invite Config] Get Invite Config Error: ", logger.Field("error", err.Error()))
return
}
var inviteConfig config.InviteConfig
tool.SystemConfigSliceReflectToStruct(configs, &inviteConfig)
ctx.Config.Invite = inviteConfig
}
@@ -0,0 +1,54 @@
-- 先检查 `email` 列是否存在,再删除
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'email';
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `email`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `telephone` 列是否存在,再删除
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone';
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `telephone_area_code` 列是否存在,再删除
SELECT COUNT(*) INTO @col_exists FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'user' AND column_name = 'telephone_area_code';
SET @sql = IF(@col_exists > 0, 'ALTER TABLE `user` DROP COLUMN `telephone_area_code`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `idx_email` 索引是否存在,再删除
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_email';
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_email`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `idx_telephone` 索引是否存在,再删除
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone';
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 先检查 `idx_telephone_area_code` 索引是否存在,再删除
SELECT COUNT(*) INTO @idx_exists FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'user' AND index_name = 'idx_telephone_area_code';
SET @sql = IF(@idx_exists > 0, 'ALTER TABLE `user` DROP INDEX `idx_telephone_area_code`', 'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
+118
View File
@@ -0,0 +1,118 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查表是否存在,如果存在则跳过创建
CREATE TABLE IF NOT EXISTS `oauth_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'platform',
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
`redirect` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Redirect URL',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_oauth_config_platform` (`platform`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 插入记录时忽略重复记录
BEGIN;
INSERT IGNORE INTO `oauth_config` (`id`, `platform`, `config`, `redirect`, `enabled`, `created_at`, `updated_at`) VALUES
(1, 'apple', '{\"team_id\":\"\",\"key_id\":\"\",\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(2, 'google', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(3, 'github', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(4, 'facebook', '{\"client_id\":\"\",\"client_secret\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292'),
(5, 'telegram', '{\"bot\":\"\",\"bot_token\":\"\"}', '', 0, '2025-01-26 20:11:15.292', '2025-01-26 20:11:15.292');
COMMIT;
-- 检测更新设置表
BEGIN;
INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) VALUES
('sms', 'SmsEnabled', 'false', 'bool', '是否启用短信功能', NOW(), NOW()),
('sms', 'SmsKey', 'your-key', 'string', '短信服务用户名或Key',NOW(), NOW()),
('sms', 'SmsSecret', 'your-secret', 'string', '短信服务密码或Secret', NOW(), NOW()),
('sms', 'SmsSign', 'your-sign', 'string', '短信签名', NOW(), NOW()),
('sms', 'SmsTemplate', 'your-template', 'string', '短信模板ID', NOW(), NOW()),
('sms', 'SmsRegion', 'cn-hangzhou', 'string', '短信服务所在区域(适用于阿里云)', NOW(), NOW()),
('sms', 'SmsTemplate', '您的验证码是{{.Code}},请在5分钟内使用。', 'string', '自定义短信模板', NOW(), NOW()),
('sms', 'SmsTemplateCode', 'SMS_12345678', 'string', '阿里云国内短信模板代码',NOW(),NOW()),
('sms', 'SmsTemplateParam', '{\"code\":{{.Code}}}', 'string', '短信模板参数', NOW(), NOW()),
('sms', 'SmsPlatform', 'smsbao', 'string', '当前使用的短信平台', NOW(), NOW()),
('sms', 'SmsLimit', '10', 'int64', '可以发送的短信最大数量', NOW(), NOW()),
('sms', 'SmsInterval', '60', 'int64', '发送短信的时间间隔(单位:秒)',NOW(), NOW()),
('sms', 'SmsExpireTime', '300', 'int64', '短信验证码的过期时间(单位:秒)',NOW(), NOW()),
('email', 'EmailEnabled', 'true', 'bool', '启用邮箱登陆',NOW(), NOW()),
('email', 'EmailSmtpHost', '', 'string', '邮箱服务器地址', NOW(), NOW()),
('email', 'EmailSmtpPort', '465', 'int', '邮箱服务器端口',NOW(), NOW()),
('email', 'EmailSmtpUser', 'domain@f1shyu.com', 'string', '邮箱服务器用户名', NOW(), NOW()),
('email', 'EmailSmtpPass', 'password', 'string', '邮箱服务器密码', NOW(), NOW()),
('email', 'EmailSmtpFrom', 'domain@f1shyu.com', 'string', '发送邮件的邮箱',NOW(), NOW()),
('email', 'EmailSmtpSSL', 'true', 'bool', '邮箱服务器加密方式',NOW(), NOW()),
('email', 'EmailTemplate', '%s', 'string', '邮件模板',NOW(), NOW()),
('email', 'VerifyEmailTemplate', '', 'string', 'Verify Email template',NOW(), NOW()),
('email', 'MaintenanceEmailTemplate', '', 'string', 'Maintenance Email template',NOW(), NOW()),
('email', 'ExpirationEmailTemplate', '', 'string', 'Expiration Email template', NOW(), NOW()),
('email', 'EmailEnableVerify', 'true', 'bool', '是否开启邮箱验证', NOW(), NOW()),
('email', 'EmailEnableDomainSuffix', 'false', 'bool', '是否开启邮箱域名后缀限制',NOW(), NOW()),
('email', 'EmailDomainSuffixList', 'qq.com', 'string', '邮箱域名后缀列表',NOW(), NOW());
COMMIT;
-- User Device
CREATE TABLE IF NOT EXISTS `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`device_number` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Number.',
`online` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`last_online` datetime(3) DEFAULT NULL COMMENT 'Last Online',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- Mobile
CREATE TABLE IF NOT EXISTS `sms` (
`id` bigint NOT NULL AUTO_INCREMENT,
`content` text COLLATE utf8mb4_general_ci,
`platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`status` tinyint(1) DEFAULT '1',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- Application Config
CREATE TABLE IF NOT EXISTS `application_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id',
`encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key',
`encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method',
`domains` text COLLATE utf8mb4_general_ci,
`startup_picture` text COLLATE utf8mb4_general_ci,
`startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- Application Version
CREATE TABLE IF NOT EXISTS `application_version` (
`id` bigint NOT NULL AUTO_INCREMENT,
`url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址',
`version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本',
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台',
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本',
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
`application_id` bigint DEFAULT NULL COMMENT '所属应用',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `fk_application_application_versions` (`application_id`),
CONSTRAINT `fk_application_application_versions` FOREIGN KEY (`application_id`) REFERENCES `application` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
UPDATE `subscribe` SET `unit_time`='Month' WHERE unit_time = '';
SET FOREIGN_KEY_CHECKS = 1;
@@ -0,0 +1,44 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user_device`;
-- User Device
CREATE TABLE IF NOT EXISTS `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
`ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
`Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
`user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `fk_user_user_devices` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for server_rule_group
-- ----------------------------
DROP TABLE IF EXISTS `server_rule_group`;
CREATE TABLE `server_rule_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name',
`icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name`
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Records of server_rule_group
-- ----------------------------
BEGIN;
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
+562
View File
@@ -0,0 +1,562 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for ads
-- ----------------------------
CREATE TABLE IF NOT EXISTS `ads` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads title',
`type` varchar(255) COLLATE utf8mb4_german2_ci NOT NULL DEFAULT '' COMMENT 'Ads type',
`content` text COLLATE utf8mb4_german2_ci COMMENT 'Ads content',
`target_url` varchar(512) COLLATE utf8mb4_german2_ci DEFAULT '' COMMENT 'Ads target url',
`start_time` datetime DEFAULT NULL COMMENT 'Ads start time',
`end_time` datetime DEFAULT NULL COMMENT 'Ads end time',
`status` tinyint(1) DEFAULT '0' COMMENT 'Ads status,0 disable,1 enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci;
-- ----------------------------
-- Table structure for announcement
-- ----------------------------
CREATE TABLE IF NOT EXISTS `announcement` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show',
`pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Pinned',
`popup` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Popup',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for application
-- ----------------------------
CREATE TABLE IF NOT EXISTS `application` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用名称',
`icon` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '应用图标',
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
`subscribe_type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for application_config
-- ----------------------------
CREATE TABLE IF NOT EXISTS `application_config` (
`id` bigint NOT NULL AUTO_INCREMENT,
`app_id` bigint NOT NULL DEFAULT '0' COMMENT 'App id',
`encryption_key` text COLLATE utf8mb4_general_ci COMMENT 'Encryption Key',
`encryption_method` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Encryption Method',
`domains` text COLLATE utf8mb4_general_ci,
`startup_picture` text COLLATE utf8mb4_general_ci,
`startup_picture_skip_time` bigint NOT NULL DEFAULT '0' COMMENT 'Startup Picture Skip Time',
`invitation_link` text COLLATE utf8mb4_general_ci COMMENT 'Invitation Link',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for application_version
-- ----------------------------
CREATE TABLE IF NOT EXISTS `application_version` (
`id` bigint NOT NULL AUTO_INCREMENT,
`url` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用地址',
`version` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用版本',
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '应用平台',
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认版本',
`description` text COLLATE utf8mb4_general_ci COMMENT '更新描述',
`application_id` bigint DEFAULT NULL COMMENT '所属应用',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `fk_application_application_versions` (`application_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for coupon
-- ----------------------------
CREATE TABLE IF NOT EXISTS `coupon` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Name',
`code` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Code',
`count` bigint NOT NULL DEFAULT '0' COMMENT 'Count Limit',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Coupon Type: 1: Percentage 2: Fixed Amount',
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount',
`start_time` bigint NOT NULL DEFAULT '0' COMMENT 'Start Time',
`expire_time` bigint NOT NULL DEFAULT '0' COMMENT 'Expire Time',
`user_limit` bigint NOT NULL DEFAULT '0' COMMENT 'User Limit',
`subscribe` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Limit',
`used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Count',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_coupon_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for document
-- ----------------------------
CREATE TABLE IF NOT EXISTS `document` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Title',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Document Content',
`tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Tags',
`show` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Show',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for auth_method
-- ----------------------------
CREATE TABLE IF NOT EXISTS `auth_method` (
`id` bigint NOT NULL AUTO_INCREMENT,
`method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'method',
`config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_auth_method` (`method`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for order
-- ----------------------------
CREATE TABLE IF NOT EXISTS `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`parent_id` bigint DEFAULT NULL COMMENT 'Parent Order Id',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id',
`order_no` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Order No',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Type: 1: Subscribe, 2: Renewal, 3: ResetTraffic, 4: Recharge',
`quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity',
`price` bigint NOT NULL DEFAULT '0' COMMENT 'Original price',
`amount` bigint NOT NULL DEFAULT '0' COMMENT 'Order Amount',
`gift_amount` bigint NOT NULL DEFAULT '0' COMMENT 'User Gift Amount',
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Discount Amount',
`coupon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Coupon',
`coupon_discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount Amount',
`commission` bigint NOT NULL DEFAULT '0' COMMENT 'Order Commission',
`payment_id` bigint NOT NULL DEFAULT '-1' COMMENT 'Payment Id',
`method` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Method',
`fee_amount` bigint NOT NULL DEFAULT '0' COMMENT 'Fee Amount',
`trade_no` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Trade No',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Status: 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished',
`subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id',
`subscribe_token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Renewal Subscribe Token',
`is_new` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is New Order',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_order_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for payment
-- ----------------------------
CREATE TABLE IF NOT EXISTS `payment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Name',
`platform` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Platform',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Payment Description',
`icon` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Payment Icon',
`domain` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Notification Domain',
`config` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Configuration',
`fee_mode` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Fee Mode: 0: No Fee 1: Percentage 2: Fixed Amount 3: Percentage + Fixed Amount',
`fee_percent` bigint DEFAULT '0' COMMENT 'Fee Percentage',
`fee_amount` bigint DEFAULT '0' COMMENT 'Fixed Fee Amount',
`enable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Payment Token',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_payment_token` (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for server
-- ----------------------------
CREATE TABLE IF NOT EXISTS `server` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
`tags` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
`country` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country',
`city` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City',
`latitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'latitude',
`longitude` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'longitude',
`server_addr` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address',
`relay_mode` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'none' COMMENT 'Relay Mode',
`relay_node` text COLLATE utf8mb4_general_ci COMMENT 'Relay Node',
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
`traffic_ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio',
`group_id` bigint DEFAULT NULL COMMENT 'Group ID',
`protocol` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
`config` text COLLATE utf8mb4_general_ci COMMENT 'Config',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for server_group
-- ----------------------------
CREATE TABLE IF NOT EXISTS `server_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Group Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for sms
-- ----------------------------
CREATE TABLE IF NOT EXISTS `sms` (
`id` bigint NOT NULL AUTO_INCREMENT,
`content` text COLLATE utf8mb4_general_ci,
`platform` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`area_code` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`telephone` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL,
`status` tinyint(1) DEFAULT '1',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for subscribe
-- ----------------------------
CREATE TABLE IF NOT EXISTS `subscribe` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Name',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Subscribe Description',
`unit_price` bigint NOT NULL DEFAULT '0' COMMENT 'Unit Price',
`unit_time` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unit Time',
`discount` text COLLATE utf8mb4_general_ci COMMENT 'Discount',
`replacement` bigint NOT NULL DEFAULT '0' COMMENT 'Replacement',
`inventory` bigint NOT NULL DEFAULT '0' COMMENT 'Inventory',
`traffic` bigint NOT NULL DEFAULT '0' COMMENT 'Traffic',
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
`device_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Device Limit',
`quota` bigint NOT NULL DEFAULT '0' COMMENT 'Quota',
`group_id` bigint DEFAULT NULL COMMENT 'Group Id',
`server_group` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server Group',
`server` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Server',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show portal page',
`sell` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Sell',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`deduction_ratio` bigint DEFAULT '0' COMMENT 'Deduction Ratio',
`allow_deduction` tinyint(1) DEFAULT '1' COMMENT 'Allow deduction',
`reset_cycle` bigint DEFAULT '0' COMMENT 'Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly',
`renewal_reset` tinyint(1) DEFAULT '0' COMMENT 'Renew Reset',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for subscribe_group
-- ----------------------------
CREATE TABLE IF NOT EXISTS `subscribe_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Group Name',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Group Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for subscribe_type
-- ----------------------------
CREATE TABLE IF NOT EXISTS `subscribe_type` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅类型',
`mark` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订阅标识',
`created_at` datetime(3) DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for system
-- ----------------------------
CREATE TABLE IF NOT EXISTS `system` (
`id` bigint NOT NULL AUTO_INCREMENT,
`category` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Category',
`key` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Key Name',
`value` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Key Value',
`type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Type',
`desc` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_system_key` (`key`),
KEY `index_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for ticket
-- ----------------------------
CREATE TABLE IF NOT EXISTS `ticket` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
`description` text COLLATE utf8mb4_general_ci COMMENT 'Description',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'UserId',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Status',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for ticket_follow
-- ----------------------------
CREATE TABLE IF NOT EXISTS `ticket_follow` (
`id` bigint NOT NULL AUTO_INCREMENT,
`ticket_id` bigint NOT NULL DEFAULT '0' COMMENT 'TicketId',
`from` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'From',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type: 1 text, 2 image',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for traffic_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `traffic_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`server_id` bigint NOT NULL COMMENT 'Server ID',
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
`timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Traffic Log Time',
PRIMARY KEY (`id`),
KEY `idx_subscribe_id` (`subscribe_id`),
KEY `idx_server_id` (`server_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`password` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User Password',
`avatar` text COLLATE utf8mb4_general_ci COMMENT 'User Avatar',
`balance` bigint DEFAULT '0' COMMENT 'User Balance',
`telegram` bigint DEFAULT NULL COMMENT 'Telegram Account',
`refer_code` varchar(20) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Referral Code',
`referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID',
`commission` bigint DEFAULT '0' COMMENT 'Commission',
`gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled',
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin',
`valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified',
`enable_email_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Email Notifications',
`enable_telegram_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Telegram Notifications',
`enable_balance_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Balance Change Notifications',
`enable_login_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Login Notifications',
`enable_subscribe_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Subscription Notifications',
`enable_trade_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Trade Notifications',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`deleted_at` datetime(3) DEFAULT NULL COMMENT 'Deletion Time',
`is_del` bigint unsigned DEFAULT NULL COMMENT '1: Normal 0: Deleted',
PRIMARY KEY (`id`),
KEY `idx_referer` (`referer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_auth_methods
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_auth_methods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`auth_type` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Type 1: apple 2: google 3: github 4: facebook 5: telegram 6: email 7: phone',
`auth_identifier` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Identifier',
`verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Verified',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
UNIQUE KEY `idx_auth_identifier` (`auth_identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_balance_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_balance_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`amount` bigint NOT NULL COMMENT 'Amount',
`type` tinyint(1) NOT NULL COMMENT 'Type: 1: Recharge 2: Withdraw 3: Payment 4: Refund 5: Reward',
`order_id` bigint DEFAULT NULL COMMENT 'Order ID',
`balance` bigint NOT NULL COMMENT 'Balance',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_commission_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_commission_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.',
`amount` bigint NOT NULL COMMENT 'Amount',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_device
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
`ip` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
`Identifier` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
`user_agent` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_gift_amount_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_gift_amount_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`user_subscribe_id` bigint DEFAULT NULL COMMENT 'Deduction User Subscribe ID',
`order_no` varchar(191) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Order No.',
`type` tinyint(1) NOT NULL COMMENT 'Type: 1: Increase 2: Reduce',
`amount` bigint NOT NULL COMMENT 'Amount',
`balance` bigint NOT NULL COMMENT 'Balance',
`remark` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Remark',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_subscribe
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_subscribe` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`order_id` bigint NOT NULL COMMENT 'Order ID',
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
`start_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Subscription Start Time',
`expire_time` datetime(3) DEFAULT NULL COMMENT 'Subscription Expire Time',
`traffic` bigint DEFAULT '0' COMMENT 'Traffic',
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
`token` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Token',
`uuid` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'UUID',
`status` tinyint(1) DEFAULT '0' COMMENT 'Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`finished_at` datetime(3) DEFAULT NULL COMMENT 'Finished At',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_user_subscribe_token` (`token`),
UNIQUE KEY `uni_user_subscribe_uuid` (`uuid`),
KEY `idx_user_id` (`user_id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_subscribe_id` (`subscribe_id`),
KEY `idx_token` (`token`),
KEY `idx_uuid` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `server_rule_group` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Rule Group Name',
`icon` text COLLATE utf8mb4_general_ci COMMENT 'Rule Group Icon',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Rule Group Description',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Rule Group Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`) -- Add unique constraint to `name`
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_login_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_login_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`login_ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Login IP',
`user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent',
`success` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Login Success',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- ----------------------------
-- Table structure for user_subscribe_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_subscribe_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`user_subscribe_id` bigint NOT NULL COMMENT 'User Subscribe ID',
`token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Token',
`ip` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'IP',
`user_agent` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'UserAgent',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_user_subscribe_id` (`user_subscribe_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `message_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'email' COMMENT 'Message Type',
`platform` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT 'Platform',
`to` text COLLATE utf8mb4_general_ci NOT NULL COMMENT 'To',
`subject` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subject',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Content',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Status',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user_device_online_record
-- ----------------------------
CREATE TABLE IF NOT EXISTS `user_device_online_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NULL DEFAULT NULL COMMENT 'User ID',
`identifier` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'Device Identifier',
`online_time` datetime(3) NULL DEFAULT NULL COMMENT 'Online Time',
`offline_time` datetime(3) NULL DEFAULT NULL COMMENT 'Offline Time',
`online_seconds` bigint NOT NULL DEFAULT '0' COMMENT 'Online Seconds ',
`duration_days` bigint NOT NULL DEFAULT '0' COMMENT 'Duration Days ',
`created_at` datetime(3) NULL DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
+644
View File
@@ -0,0 +1,644 @@
package migrate
import (
"fmt"
"time"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/model/payment"
"github.com/perfect-panel/ppanel-server/internal/model/subscribeType"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/email"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/sms"
"github.com/perfect-panel/ppanel-server/pkg/tool"
"github.com/perfect-panel/ppanel-server/pkg/uuidx"
"gorm.io/gorm"
)
func InitPPanelSQL(db *gorm.DB) error {
logger.Info("PPanel SQL initialization started")
startTime := time.Now()
defer func() {
logger.Info("PPanel SQL initialization completed", logger.Field("duration", time.Since(startTime).String()))
}()
return db.Transaction(func(tx *gorm.DB) error {
var err error
defer func() {
// If an error occurs, delete all tables
if err != nil {
logger.Debugf("PPanel SQL initialization completed, err: %v", err.Error())
tables, _ := tx.Migrator().GetTables()
for _, table := range tables {
tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table))
}
}
}()
// init ppanel.sql file
if err = ExecuteSQLFile(tx, "database/ppanel.sql"); err != nil {
return err
}
//Insert basic system data
if err = insertBasicSystemData(tx); err != nil {
return err
}
// insert into OAuth config
if err = insertAuthMethodConfig(tx); err != nil {
return err
}
// insert into Payment config
if err = insertPaymentConfig(tx); err != nil {
return err
}
// insert into SubscribeType
if err = insertSubscribeType(tx); err != nil {
return err
}
return err
})
}
func insertBasicSystemData(tx *gorm.DB) error {
if err := insertSiteConfig(tx); err != nil {
return err
}
if err := insertSubscribeConfig(tx); err != nil {
return err
}
if err := insertVerifyConfig(tx); err != nil {
return err
}
if err := insertSeverConfig(tx); err != nil {
return err
}
if err := insertInviteConfig(tx); err != nil {
return err
}
if err := insertRegisterConfig(tx); err != nil {
return err
}
if err := insertCurrencyConfig(tx); err != nil {
return err
}
if err := insertVerifyCodeConfig(tx); err != nil {
return err
}
version := system.System{
Category: "system",
Key: "Version",
Value: constant.Version,
Type: "string",
Desc: "System Version",
}
if err := tx.Model(&system.System{}).Save(&version).Error; err != nil {
return err
}
return nil
}
// insertSiteConfig
func insertSiteConfig(tx *gorm.DB) error {
siteConfig := []system.System{
{
Category: "site",
Key: "SiteLogo",
Value: "/favicon.svg",
Type: "string",
Desc: "Site Logo",
},
{
Category: "site",
Key: "SiteName",
Value: "Perfect Panel",
Type: "string",
Desc: "Site Name",
},
{
Category: "site",
Key: "SiteDesc",
Value: "PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.",
Type: "string",
Desc: "Site Description",
},
{
Category: "site",
Key: "Host",
Value: "",
Type: "string",
Desc: "Site Host",
},
{
Category: "site",
Key: "Keywords",
Value: "Perfect Panel,PPanel",
Type: "string",
Desc: "Site Keywords",
},
{
Category: "site",
Key: "CustomHTML",
Value: "",
Type: "string",
Desc: "Custom HTML",
},
{
Category: "site",
Key: "CustomData",
Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}",
Type: "string",
Desc: "Custom data",
},
{
Category: "tos",
Key: "TosContent",
Value: "Welcome to use Perfect Panel",
Type: "string",
Desc: "Terms of Service",
},
{
Category: "tos",
Key: "PrivacyPolicy",
Value: "",
Type: "string",
Desc: "PrivacyPolicy",
},
{
Category: "ad",
Key: "WebAD",
Value: "false",
Type: "bool",
Desc: "Display ad on the web",
},
}
return tx.Model(&system.System{}).Save(&siteConfig).Error
}
// insertSubscribeConfig
func insertSubscribeConfig(tx *gorm.DB) error {
subscribeConfig := []system.System{
{
Category: "subscribe",
Key: "SingleModel",
Value: "false",
Type: "bool",
Desc: "是否单订阅模式",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
{
Category: "subscribe",
Key: "SubscribePath",
Value: "/api/subscribe",
Type: "string",
Desc: "订阅路径",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
{
Category: "subscribe",
Key: "SubscribeDomain",
Value: "",
Type: "string",
Desc: "订阅域名",
},
{
Category: "subscribe",
Key: "PanDomain",
Value: "false",
Type: "bool",
Desc: "是否使用泛域名",
},
}
return tx.Model(&system.System{}).Save(&subscribeConfig).Error
}
// insertVerifyConfig
func insertVerifyConfig(tx *gorm.DB) error {
verifyConfig := []system.System{
{
Category: "verify",
Key: "TurnstileSiteKey",
Value: "",
Type: "string",
Desc: "TurnstileSiteKey",
},
{
Category: "verify",
Key: "TurnstileSecret",
Value: "",
Type: "string",
Desc: "TurnstileSecret",
},
{
Category: "verify",
Key: "EnableLoginVerify",
Value: "false",
Type: "bool",
Desc: "is enable login verify",
},
{
Category: "verify",
Key: "EnableRegisterVerify",
Value: "false",
Type: "bool",
Desc: "is enable register verify",
},
{
Category: "verify",
Key: "EnableResetPasswordVerify",
Value: "false",
Type: "bool",
Desc: "is enable reset password verify",
},
}
return tx.Model(&system.System{}).Save(&verifyConfig).Error
}
// insertSeverConfig
func insertSeverConfig(tx *gorm.DB) error {
serverConfig := []system.System{
{
Category: "server",
Key: "NodeSecret",
Value: "12345678",
Type: "string",
Desc: "node secret",
},
{
Category: "server",
Key: "NodePullInterval",
Value: "10",
Type: "int",
Desc: "node pull interval",
},
{
Category: "server",
Key: "NodePushInterval",
Value: "60",
Type: "int",
Desc: "node push interval",
},
{
Category: "server",
Key: "NodeMultiplierConfig",
Value: "[]",
Type: "string",
Desc: "node multiplier config",
},
}
return tx.Model(&system.System{}).Save(&serverConfig).Error
}
// insertInviteConfig
func insertInviteConfig(tx *gorm.DB) error {
inviteConfig := []system.System{
{
Category: "invite",
Key: "ForcedInvite",
Value: "false",
Type: "bool",
Desc: "Forced invite",
},
{
Category: "invite",
Key: "ReferralPercentage",
Value: "20",
Type: "int",
Desc: "Referral percentage",
},
{
Category: "invite",
Key: "OnlyFirstPurchase",
Value: "false",
Type: "bool",
Desc: "Only first purchase",
},
}
return tx.Model(&system.System{}).Save(&inviteConfig).Error
}
// insertRegisterConfig
func insertRegisterConfig(tx *gorm.DB) error {
registerConfig := []system.System{
{
Category: "register",
Key: "StopRegister",
Value: "false",
Type: "bool",
Desc: "is stop register",
},
{
Category: "register",
Key: "EnableTrial",
Value: "false",
Type: "bool",
Desc: "is enable trial",
},
{
Category: "register",
Key: "TrialSubscribe",
Value: "",
Type: "int",
Desc: "Trial subscription",
},
{
Category: "register",
Key: "TrialTime",
Value: "24",
Type: "int",
Desc: "Trial time",
},
{
Category: "register",
Key: "TrialTimeUnit",
Value: "Hour",
Type: "string",
Desc: "Trial time unit",
},
{
Category: "register",
Key: "EnableIpRegisterLimit",
Value: "false",
Type: "bool",
Desc: "is enable IP register limit",
},
{
Category: "register",
Key: "IpRegisterLimit",
Value: "3",
Type: "int",
Desc: "IP Register Limit",
},
{
Category: "register",
Key: "IpRegisterLimitDuration",
Value: "64",
Type: "int",
Desc: "IP Register Limit Duration (minutes)",
},
}
return tx.Model(&system.System{}).Save(&registerConfig).Error
}
// insertAuthMethodConfig
func insertAuthMethodConfig(tx *gorm.DB) error {
// insert into OAuth config
var methods []auth.Auth
methods = append(methods, []auth.Auth{
initEmailConfig(),
initMobileConfig(),
{
Method: "apple",
Config: new(auth.AppleAuthConfig).Marshal(),
},
{
Method: "google",
Config: new(auth.GoogleAuthConfig).Marshal(),
},
{
Method: "github",
Config: new(auth.GithubAuthConfig).Marshal(),
},
{
Method: "facebook",
Config: new(auth.FacebookAuthConfig).Marshal(),
},
{
Method: "telegram",
Config: new(auth.TelegramAuthConfig).Marshal(),
},
{
Method: "device",
Config: new(auth.DeviceConfig).Marshal(),
},
}...)
return tx.Model(&auth.Auth{}).Save(&methods).Error
}
// insertPaymentConfig
func insertPaymentConfig(tx *gorm.DB) error {
enable := true
payments := []payment.Payment{
{
Id: -1,
Name: "Balance",
Platform: "balance",
Icon: "",
Domain: "",
Config: "",
FeeMode: 0,
FeePercent: 0,
FeeAmount: 0,
Enable: &enable,
},
}
// reset auto increment
if err := tx.Exec("ALTER TABLE `payment` AUTO_INCREMENT = 1").Error; err != nil {
logger.Errorw("Reset auto increment failed", logger.Field("error", err))
return err
}
return tx.Model(&payment.Payment{}).Save(&payments).Error
}
// insertSubscribeType
func insertSubscribeType(tx *gorm.DB) error {
// insert into subscribe type
var subscribeTypes []subscribeType.SubscribeType
subscribeTypes = append(subscribeTypes, []subscribeType.SubscribeType{
{
Name: "Clash",
Mark: "Clash",
},
{
Name: "Hiddify",
Mark: "Hiddify",
},
{
Name: "Loon",
Mark: "Loon",
},
{
Name: "NekoBox",
Mark: "NekoBox",
},
{
Name: "NekoRay",
Mark: "NekoRay",
},
{
Name: "Netch",
Mark: "Netch",
},
{
Name: "Quantumult",
Mark: "Quantumult",
},
{
Name: "Shadowrocket",
Mark: "Shadowrocket",
},
{
Name: "Singbox",
Mark: "Singbox",
},
{
Name: "Surfboard",
Mark: "Surfboard",
},
{
Name: "Surge",
Mark: "Surge",
},
{
Name: "V2box",
Mark: "V2box",
},
{
Name: "V2rayN",
Mark: "V2rayN",
},
{
Name: "V2rayNg",
Mark: "V2rayNg",
},
}...)
// insert into payment
return tx.Save(&subscribeTypes).Error
}
// CreateAdminUser create admin user
func CreateAdminUser(email, password string, tx *gorm.DB) error {
enable := true
return tx.Transaction(func(tx *gorm.DB) error {
// Prevent duplicate creation
if tx.Model(&user.User{}).Find(&user.User{}).RowsAffected != 0 {
logger.Info("User already exists, skip creating administrator account")
return nil
}
u := user.User{
Password: tool.EncodePassWord(password),
IsAdmin: &enable,
ReferCode: uuidx.UserInviteCode(time.Now().Unix()),
}
if err := tx.Model(&user.User{}).Save(&u).Error; err != nil {
return err
}
method := user.AuthMethods{
UserId: u.Id,
AuthType: "email",
AuthIdentifier: email,
Verified: true,
}
if err := tx.Model(&user.AuthMethods{}).Save(&method).Error; err != nil {
return err
}
return nil
})
}
func initEmailConfig() auth.Auth {
enable := true
smtpConfig := new(auth.SMTPConfig)
emailConfig := auth.EmailAuthConfig{
Platform: "smtp",
PlatformConfig: smtpConfig,
EnableVerify: false,
EnableDomainSuffix: false,
DomainSuffixList: "",
VerifyEmailTemplate: email.DefaultEmailVerifyTemplate,
ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate,
MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate,
TrafficExceedEmailTemplate: email.DefaultTrafficExceedEmailTemplate,
}
authMethod := auth.Auth{
Method: "email",
Config: emailConfig.Marshal(),
Enabled: &enable,
}
return authMethod
}
func initMobileConfig() auth.Auth {
cfg := new(auth.AlibabaCloudConfig)
mobileConfig := auth.MobileAuthConfig{
Platform: sms.AlibabaCloud.String(),
PlatformConfig: cfg,
EnableWhitelist: false,
Whitelist: make([]string, 0),
}
authMethod := auth.Auth{
Method: "mobile",
Config: mobileConfig.Marshal(),
}
return authMethod
}
// insert into currency config
func insertCurrencyConfig(tx *gorm.DB) error {
currencyConfig := []system.System{
{
Category: "currency",
Key: "Currency",
Value: "USD",
Type: "string",
Desc: "Currency",
},
{
Category: "currency",
Key: "CurrencySymbol",
Value: "$",
Type: "string",
Desc: "Currency Symbol",
},
{
Category: "currency",
Key: "CurrencyUnit",
Value: "USD",
Type: "string",
Desc: "Currency Unit",
},
{
Category: "currency",
Key: "AccessKey",
Value: "",
Type: "string",
Desc: "Exchangerate Access Key",
},
}
return tx.Model(&system.System{}).Save(&currencyConfig).Error
}
// insert into verify code config
func insertVerifyCodeConfig(tx *gorm.DB) error {
verifyCodeConfig := []system.System{
{
Category: "verify_code",
Key: "VerifyCodeExpireTime",
Value: "300",
Type: "int",
Desc: "Verify code expire time",
},
{
Category: "verify_code",
Key: "VerifyCodeLimit",
Value: "15",
Type: "int",
Desc: "limits of verify code",
},
{
Category: "verify_code",
Key: "VerifyCodeInterval",
Value: "60",
Type: "int",
Desc: "Interval of verify code",
},
}
return tx.Model(&system.System{}).Save(&verifyCodeConfig).Error
}
+37
View File
@@ -0,0 +1,37 @@
package migrate
import (
"testing"
"github.com/perfect-panel/ppanel-server/pkg/orm"
"gorm.io/gorm"
)
func connMySQL() *gorm.DB {
cfg := orm.Config{
Addr: "127.0.0.1",
Username: "root",
Password: "mylove520",
Dbname: "ppanel",
}
db, err := orm.ConnectMysql(orm.Mysql{
Config: cfg,
})
if err != nil {
return nil
}
return db
}
func TestInitPPanelSQL(t *testing.T) {
t.Skipf("Skip TestInitPPanelSQL")
db := connMySQL()
if db == nil {
t.Error("connect mysql failed")
return
}
if err := InitPPanelSQL(db); err != nil {
t.Error(err)
}
t.Logf("InitPPanelSQL success")
}
+34
View File
@@ -0,0 +1,34 @@
package migrate
import (
"embed"
"time"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
//go:embed database/*.sql
var sqlFiles embed.FS
func Migrate(ctx *svc.ServiceContext) {
logger.Debug("SQL Migrate started")
startTime := time.Now()
defer func() {
logger.WithDuration(time.Since(startTime)).Debug("PPanel SQL Migrate completed")
}()
db := ctx.DB
if !db.Migrator().HasTable(&system.System{}) {
if err := InitPPanelSQL(db); err != nil {
logger.Error("SQL Migrate failed", logger.Field("err", err.Error()))
panic(err)
}
// create admin user
if err := CreateAdminUser(ctx.Config.Administrator.Email, ctx.Config.Administrator.Password, db); err != nil {
logger.Error("Create admin User failed", logger.Field("err", err.Error()))
panic(err)
}
}
}
+456
View File
@@ -0,0 +1,456 @@
package patch
import (
"github.com/perfect-panel/ppanel-server/initialize/migrate"
"github.com/perfect-panel/ppanel-server/internal/model/application"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/model/log"
"github.com/perfect-panel/ppanel-server/internal/model/server"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/email"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/pkg/sms"
"gorm.io/gorm"
)
func Migrate01200(db *gorm.DB) error {
var version = "0.1.2(01200)"
return db.Transaction(func(tx *gorm.DB) error {
if exists := db.Migrator().HasColumn(&user.OldUser{}, "email"); !exists {
logger.Debug("Migrate 01200 skipped", logger.Field("reason", "old user table not exists"))
return nil
}
logger.Debug("Migrate 01200 started", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"))
var users []*user.OldUser
if err := tx.Model(&user.OldUser{}).Find(&users).Error; err != nil {
return err
}
if err := tx.Migrator().AutoMigrate(&user.AuthMethods{}); err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "create user auth methods table"), logger.Field("error", err.Error()))
return err
}
err := tx.Transaction(func(tx *gorm.DB) error {
for _, oldUser := range users {
if oldUser.Email == "" {
continue
}
// create user auth method
authMethod := &user.AuthMethods{
UserId: oldUser.Id,
AuthType: "email",
AuthIdentifier: oldUser.Email,
Verified: false,
}
if err := tx.Create(authMethod).Error; err != nil {
return err
}
}
return nil
})
if err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"), logger.Field("error", err.Error()))
return err
}
logger.Debug("Migrate 01200 completed", logger.Field("step", "1"), logger.Field("action", "migrate old user to user auth methods"))
logger.Debug("Migrate 01200 started", logger.Field("step", "2"), logger.Field("action", "exclude sql files"))
// exclude sql files
if err := migrate.ExecuteSQLFile(tx, "database/01200-patch.sql"); err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error()))
return err
}
logger.Debug("Migrate 01200 completed", logger.Field("step", "2"), logger.Field("action", "exclude sql files"))
logger.Debug("Migrate 01200 started", logger.Field("step", "3"), logger.Field("action", "update system config"))
versionConfig := &system.System{
Category: "system",
Key: "Version",
Value: version,
Type: "string",
Desc: "Version of the system, eg: 1.0.0(10000)",
}
// update system config
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Save(&versionConfig).Error; err != nil {
logger.Errorw("Migrate 01200 failed", logger.Field("step", "3"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01201(db *gorm.DB) error {
version := "0.1.2(01201)"
// exclude sql files
if err := migrate.ExecuteSQLFile(db, "database/01201-patch.sql"); err != nil {
logger.Errorw("Migrate 01201 failed", logger.Field("step", "1"), logger.Field("action", "exclude sql files"), logger.Field("file", "database/01200-patch.sql"), logger.Field("error", err.Error()))
return err
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01201 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
}
func Migrate01202(db *gorm.DB) error {
version := "0.1.2(01202)"
return db.Transaction(func(tx *gorm.DB) error {
// migrate email config to system config
if err := db.Migrator().AutoMigrate(&auth.Auth{}); err != nil {
logger.Errorw("Migrate01202: AutoMigrate Auth failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
if db.Migrator().HasColumn("oauth_config", "platform") {
if err := db.Migrator().RenameColumn("oauth_config", "platform", "method"); err != nil {
logger.Errorw("Migrate01202: RenameColumn platform to method failed", logger.Field("version", version), logger.Field("error", err.Error()))
}
}
// init email config
if err := initEmailConfig(db); err != nil {
logger.Errorw("Migrate01202: initEmailConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// init mobile config
if err := initMobileConfig(db); err != nil {
logger.Errorw("Migrate01202: initMobileConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// drop oauth_config table
err := db.Migrator().DropTable("oauth_config")
if err != nil {
logger.Debug("Migrate01202: DropTable oauth_config failed", logger.Field("version", version), logger.Field("error", err.Error()))
}
// exclude sql files
if err := migrate.ExecuteSQLFile(db, "database/01202-patch.sql"); err != nil {
logger.Errorw("Migrate 01202 failed", logger.Field("action", "exclude sql files"), logger.Field("file", "database/012002-patch.sql"), logger.Field("error", err.Error()))
return err
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01202 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01203(db *gorm.DB) error {
version := "0.1.2(01203)"
return db.Transaction(func(tx *gorm.DB) error {
if err := db.AutoMigrate(&user.LoginLog{}, &user.SubscribeLog{}); err != nil {
logger.Errorw("Migrate01203: AutoMigrate LoginLog/SubscribeLog failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01203: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01204(db *gorm.DB) error {
version := "0.1.2(01204)"
return db.Transaction(func(tx *gorm.DB) error {
if err := db.AutoMigrate(&log.MessageLog{}); err != nil {
logger.Errorw("Migrate01204: AutoMigrate MessageLog failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// Trial configuration
if err := initTrialConfig(tx); err != nil {
logger.Errorw("Migrate01204: initTrialConfig failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// Add auth method with device
if err := addAuthMethodWithDevice(tx); err != nil {
logger.Errorw("Migrate01204: Add auth method with device failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01204: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01205(db *gorm.DB) error {
version := "0.1.2(01205)"
return db.Transaction(func(tx *gorm.DB) error {
// Add VerifyCode public configuration
configs := []system.System{
{
Category: "verify_code",
Key: "VerifyCodeExpireTime",
Value: "5",
Type: "int",
Desc: "Verify code expire time",
},
{
Category: "verify_code",
Key: "VerifyCodeLimit",
Value: "15",
Type: "int",
Desc: "limits of verify code",
},
{
Category: "verify_code",
Key: "VerifyCodeInterval",
Value: "60",
Type: "int",
Desc: "Interval of verify code",
},
}
if err := tx.Model(&system.System{}).Save(&configs).Error; err != nil {
logger.Errorw("Migrate01205: Save VerifyCode public configuration failed", logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01301(db *gorm.DB) error {
version := "0.1.3(01301)"
return db.Transaction(func(tx *gorm.DB) error {
err := tx.Migrator().AlterColumn(&application.Application{}, "icon")
if err != nil {
logger.Errorw("Migrate01301: AlterColumn failed", logger.Field("error", err.Error()))
return err
}
// update version
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate01205: Update Version failed", logger.Field("version", version), logger.Field("error", err.Error()))
return err
}
return nil
})
}
func Migrate01602(db *gorm.DB) error {
version := "0.1.6(01602)"
return db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'TosContent'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "tos",
Key: "TosContent",
Value: "Welcome to use Perfect Panel",
Type: "string",
Desc: "Terms of Service",
}).Error; err != nil {
return err
}
}
// update version
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
return err
}
return nil
})
}
func Migrate01701(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
version := "0.1.7(01701)"
if err := db.Migrator().AlterColumn(&user.User{}, "Avatar"); err != nil {
return err
}
// update version
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
return err
}
return nil
})
}
func Migrate01702(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
version := "0.1.7(01702)"
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'Keywords'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "site",
Key: "Keywords",
Value: "Perfect Panel,PPanel",
Type: "string",
Desc: "Keywords",
}).Error; err != nil {
return err
}
}
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomHTML'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "site",
Key: "CustomHTML",
Value: "",
Type: "string",
Desc: "Custom HTML",
}).Error; err != nil {
return err
}
}
// update version
if err := tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
return err
}
return nil
})
}
func Migrate01703(db *gorm.DB) error {
version := "0.1.7(01703)"
return db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&system.System{}).Where("`category` = 'tos' AND `key` = 'PrivacyPolicy'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "tos",
Key: "PrivacyPolicy",
Value: "",
Type: "string",
Desc: "Privacy Policy",
}).Error; err != nil {
return err
}
}
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
})
}
func Migrate01704(db *gorm.DB) error {
version := "0.1.7(01704)"
// check server table latitude column exists, if not exists, create it
if exists := db.Migrator().HasColumn(&server.Server{}, "latitude"); !exists {
if err := db.Migrator().AddColumn(&server.Server{}, "latitude"); err != nil {
logger.Errorw("Migrate 01704 failed", logger.Field("action", "add latitude column"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01704 success", logger.Field("action", "add latitude column"))
}
// check server table longitude column exists, if not exists, create it
if exists := db.Migrator().HasColumn(&server.Server{}, "longitude"); !exists {
if err := db.Migrator().AddColumn(&server.Server{}, "longitude"); err != nil {
logger.Errorw("Migrate 01704 failed", logger.Field("action", "add longitude column"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01704 success", logger.Field("action", "add longitude column"))
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01704 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01704 success", logger.Field("action", "update system config"))
return nil
}
func Migrate01705(db *gorm.DB) error {
version := "0.1.7(01705)"
// check user_device table exists, if not exists, create it
if exists := db.Migrator().HasTable(&user.Device{}); !exists {
if err := db.Migrator().CreateTable(&user.Device{}); err != nil {
logger.Errorw("Migrate 01705 failed", logger.Field("action", "create user_device table"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01705 success", logger.Field("action", "create user_device table"))
}
// check user_table exists and imei column exists, if exists, update imei column name to identifier
if exists := db.Migrator().HasColumn(&user.Device{}, "imei"); exists {
if err := db.Migrator().RenameColumn(&user.Device{}, "imei", "identifier"); err != nil {
logger.Errorw("Migrate 01705 failed", logger.Field("action", "rename imei column to identifier"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 01705 success", logger.Field("action", "rename imei column to identifier"))
}
// update system config
if err := db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error; err != nil {
logger.Errorw("Migrate 01705 failed", logger.Field("step", "2"), logger.Field("action", "update system config"), logger.Field("error", err.Error()))
return err
}
return nil
}
func initMobileConfig(db *gorm.DB) error {
cfg := new(auth.AlibabaCloudConfig)
mobileConfig := auth.MobileAuthConfig{
Platform: sms.AlibabaCloud.String(),
PlatformConfig: cfg.Marshal(),
EnableWhitelist: false,
Whitelist: make([]string, 0),
}
authMethod := auth.Auth{
Method: "mobile",
Config: mobileConfig.Marshal(),
}
if err := db.Save(&authMethod).Error; err != nil {
return err
}
return nil
}
func initEmailConfig(db *gorm.DB) error {
enable := true
smtpConfig := new(auth.SMTPConfig)
emailConfig := auth.EmailAuthConfig{
Platform: "smtp",
PlatformConfig: smtpConfig.Marshal(),
EnableVerify: false,
EnableDomainSuffix: false,
DomainSuffixList: "",
VerifyEmailTemplate: email.DefaultEmailVerifyTemplate,
ExpirationEmailTemplate: email.DefaultExpirationEmailTemplate,
MaintenanceEmailTemplate: email.DefaultMaintenanceEmailTemplate,
}
authMethod := auth.Auth{
Method: "email",
Config: emailConfig.Marshal(),
Enabled: &enable,
}
return db.Save(&authMethod).Error
}
func initTrialConfig(tx *gorm.DB) error {
configs := []system.System{
{
Category: "register",
Key: "TrialSubscribe",
Value: "",
Type: "int",
Desc: "Trial subscription",
},
{
Category: "register",
Key: "TrialTime",
Value: "24",
Type: "int",
Desc: "Trial time",
},
{
Category: "register",
Key: "TrialTimeUnit",
Value: "Hour",
Type: "string",
Desc: "Trial time unit",
},
}
return tx.Model(&system.System{}).Save(&configs).Error
}
func addAuthMethodWithDevice(tx *gorm.DB) error {
return tx.Model(&auth.Auth{}).Save(&auth.Auth{
Method: "device",
}).Error
}
+252
View File
@@ -0,0 +1,252 @@
package patch
import (
"github.com/perfect-panel/ppanel-server/internal/model/ads"
"github.com/perfect-panel/ppanel-server/internal/model/application"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/model/order"
"github.com/perfect-panel/ppanel-server/internal/model/payment"
"github.com/perfect-panel/ppanel-server/internal/model/server"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
)
func Migrate02000(db *gorm.DB) error {
version := "0.2.0(02000)"
return db.Transaction(func(tx *gorm.DB) error {
if err := initDeviceConfig(tx); err != nil {
logMigrationError("Setting Device Config", err)
return err
}
logMigrationSuccess("Setting Device Config")
if !tx.Migrator().HasTable(&ads.Ads{}) {
if err := createAdsTable(tx); err != nil {
return err
}
}
if err := updatePaymentTable(tx); err != nil {
return err
}
if err := tx.Migrator().AutoMigrate(&order.Order{}); err != nil {
logMigrationError("Auto Migrate Order", err)
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02001(db *gorm.DB) error {
version := "0.2.0(02001)"
return db.Transaction(func(tx *gorm.DB) error {
if tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").Find(&system.System{}).RowsAffected == 0 {
if err := tx.Save(&system.System{
Category: "site",
Key: "CustomData",
Value: "{\"website\":\"\",\"contacts\":{\"email\":\"\",\"telephone\":\"\",\"address\":\"\"},\"community\":{\"telegram\":\"\",\"twitter\":\"\",\"discord\":\"\",\"instagram\":\"\",\"linkedin\":\"\",\"facebook\":\"\",\"github\":\"\"}}",
Type: "string",
Desc: "Custom data",
}).Error; err != nil {
logMigrationError("create custom data system config", err)
return err
}
}
return updateSystemVersion(tx, version)
})
}
func Migrate02002(db *gorm.DB) error {
version := "0.2.0(02002)"
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&system.System{}).Where("`category` = 'site' AND `key` = 'CustomData'").UpdateColumn("type", "string").Error; err != nil {
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02003(db *gorm.DB) error {
version := "0.2.0(02003)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &order.Order{}, "payment_id"); err != nil {
return err
}
if err := addColumnIfNotExists(tx, &payment.Payment{}, "platform"); err != nil {
return err
}
if err := dropColumnIfExists(tx, &payment.Payment{}, "mark"); err != nil {
return err
}
if err := addColumnIfNotExists(tx, &payment.Payment{}, "description"); err != nil {
return err
}
if err := addColumnIfNotExists(tx, &payment.Payment{}, "token"); err != nil {
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02007(db *gorm.DB) error {
version := "0.2.0(02007)"
return db.Transaction(func(tx *gorm.DB) error {
if err := recreateTable(tx, &server.RuleGroup{}); err != nil {
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02008(db *gorm.DB) error {
version := "0.2.0(02008)"
return db.Transaction(func(tx *gorm.DB) error {
if exists := tx.Migrator().HasColumn(&application.ApplicationConfig{}, "invitation_link"); !exists {
if err := tx.Migrator().AddColumn(&application.ApplicationConfig{}, "invitation_link"); err != nil {
logger.Errorw("Migrate 02008 failed", logger.Field("action", "add invitation_link column"), logger.Field("error", err.Error()))
return err
}
logger.Infow("Migrate 02008 success", logger.Field("action", "add invitation_link column"))
}
if exists := tx.Migrator().HasTable(&user.DeviceOnlineRecord{}); !exists {
if err := tx.Migrator().CreateTable(&user.DeviceOnlineRecord{}); err != nil {
logger.Errorw("Migrate 02008 failed", logger.Field("action", "create device_online_record table"), logger.Field("error", err.Error()))
return err
}
}
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
})
}
func Migrate02009(db *gorm.DB) error {
version := "0.2.0(02009)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil {
logger.Errorw("Migrate 02009 failed", logger.Field("action", "subscribe table add finished_at column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02010(db *gorm.DB) error {
version := "0.2.0(02010)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &application.ApplicationConfig{}, "kr_website_id"); err != nil {
logger.Errorw("Migrate 02010 failed", logger.Field("action", "application_config table add kr_website_id column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func Migrate02011(db *gorm.DB) error {
version := "0.2.0(02011)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "used_period"); err != nil {
logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add used_period column"), logger.Field("error", err.Error()))
return err
}
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "total_period"); err != nil {
logger.Errorw("Migrate 02011 failed", logger.Field("action", "user.Subscribe table add total_period column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
func initDeviceConfig(db *gorm.DB) error {
cfg := new(auth.DeviceConfig)
return db.Model(&auth.Auth{}).Where("method = ?", "device").Update("config", cfg.Marshal()).Error
}
func createAdsTable(tx *gorm.DB) error {
if err := tx.Migrator().CreateTable(&ads.Ads{}); err != nil {
logMigrationError("Create Table Ads", err)
return err
}
logMigrationSuccess("Create Table Ads")
return tx.Model(&system.System{}).Save(&system.System{
Category: "ad",
Key: "WebAD",
Value: "false",
Type: "bool",
Desc: "Display ad on the web",
}).Error
}
func updatePaymentTable(tx *gorm.DB) error {
if err := tx.Exec("DROP TABLE IF EXISTS `payment`").Error; err != nil {
logMigrationError("Drop Payment Table", err)
}
if err := tx.AutoMigrate(&payment.Payment{}); err != nil {
logMigrationError("Auto Migrate Payment", err)
return err
}
enable := true
return tx.Model(&payment.Payment{}).Create(&payment.Payment{
Id: -1,
Name: "",
Platform: "balance",
Icon: "",
Domain: "",
Config: "",
FeeMode: 0,
FeePercent: 0,
FeeAmount: 0,
Enable: &enable,
}).Error
}
func updateSystemVersion(tx *gorm.DB, version string) error {
return tx.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").Update("value", version).Error
}
func addColumnIfNotExists(tx *gorm.DB, model interface{}, columnName string) error {
if exists := tx.Migrator().HasColumn(model, columnName); !exists {
if err := tx.Migrator().AddColumn(model, columnName); err != nil {
logMigrationError("add "+columnName+" column", err)
return err
}
logMigrationSuccess("add " + columnName + " column")
}
return nil
}
func dropColumnIfExists(tx *gorm.DB, model interface{}, columnName string) error {
if exists := tx.Migrator().HasColumn(model, columnName); exists {
if err := tx.Migrator().DropColumn(model, columnName); err != nil {
logMigrationError("del "+columnName+" column", err)
return err
}
logMigrationSuccess("del " + columnName + " column")
}
return nil
}
func recreateTable(tx *gorm.DB, model interface{}) error {
if exists := tx.Migrator().HasTable(model); exists {
if err := tx.Migrator().DropTable(model); err != nil {
logMigrationError("drop table", err)
return err
}
}
if err := tx.Migrator().CreateTable(model); err != nil {
logMigrationError("create table", err)
return err
}
return nil
}
func logMigrationError(action string, err error) {
logger.Errorw("Migration failed", logger.Field("action", action), logger.Field("error", err.Error()))
}
func logMigrationSuccess(action string) {
logger.Infow("Migration success", logger.Field("action", action))
}
+18
View File
@@ -0,0 +1,18 @@
package patch
import (
"github.com/perfect-panel/ppanel-server/internal/model/user"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
)
func Migrate03001(db *gorm.DB) error {
version := "0.3.0(1)"
return db.Transaction(func(tx *gorm.DB) error {
if err := addColumnIfNotExists(tx, &user.Subscribe{}, "finished_at"); err != nil {
logger.Errorw("Migrate 03001 failed", logger.Field("action", "user.Subscribe table add finished_at column"), logger.Field("error", err.Error()))
return err
}
return updateSystemVersion(tx, version)
})
}
+101
View File
@@ -0,0 +1,101 @@
package migrate
import (
"fmt"
"strings"
"gorm.io/gorm"
)
// ExecuteSQLFile 执行嵌入的 SQL 文件,去除注释
func ExecuteSQLFile(tx *gorm.DB, path string) error {
// 读取 SQL 文件内容
sqlContent, err := sqlFiles.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read embedded SQL file: %v", err)
}
// 清除注释内容
cleanedSQL := removeComments(string(sqlContent))
// 将清除注释后的内容按分号分割成多个 SQL 语句
sqlStatements := splitSQLStatements(cleanedSQL)
// 遍历 SQL 语句并执行
for _, stmt := range sqlStatements {
stmt = strings.TrimSpace(stmt)
if stmt == "" {
continue
}
// 执行 SQL 语句
if err := tx.Exec(stmt).Error; err != nil {
return fmt.Errorf("failed to execute SQL statement: %v \nSQL: %s", err.Error(), stmt)
}
}
return nil
}
// removeComments 去除 SQL 代码中的注释
func removeComments(sql string) string {
var result strings.Builder
inSingleLineComment := false
inMultiLineComment := false
length := len(sql)
for i := 0; i < length; i++ {
// 处理 -- 单行注释
if !inMultiLineComment && !inSingleLineComment && i+1 < length && sql[i] == '-' && sql[i+1] == '-' {
inSingleLineComment = true
i++ // 跳过 '-'
continue
}
// 结束单行注释(支持 \r\n 和 \n)
if inSingleLineComment && (sql[i] == '\n' || sql[i] == '\r') {
inSingleLineComment = false
}
// 处理 /* 多行注释 */
if !inSingleLineComment && !inMultiLineComment && i+1 < length && sql[i] == '/' && sql[i+1] == '*' {
inMultiLineComment = true
i++ // 跳过 '*'
continue
}
// 结束多行注释
if inMultiLineComment && i+1 < length && sql[i] == '*' && sql[i+1] == '/' {
inMultiLineComment = false
i++ // 跳过 '/'
continue
}
// 不是注释内容时,将字符添加到结果中
if !inSingleLineComment && !inMultiLineComment {
result.WriteByte(sql[i])
}
}
// 去除多余的空白行
lines := strings.Split(result.String(), "\n")
var cleanedLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed != "" {
cleanedLines = append(cleanedLines, trimmed)
}
}
return strings.Join(cleanedLines, "\n")
}
// splitSQLStatements 更安全地分割 SQL 语句
func splitSQLStatements(sql string) []string {
statements := strings.Split(sql, ";")
var results []string
for _, stmt := range statements {
trimmed := strings.TrimSpace(stmt)
if trimmed != "" {
results = append(results, trimmed)
}
}
return results
}
+32
View File
@@ -0,0 +1,32 @@
package initialize
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Mobile(ctx *svc.ServiceContext) {
logger.Debug("Mobile config initialization")
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "mobile")
if err != nil {
panic(err)
}
var cfg config.MobileConfig
var mobileConfig auth.MobileAuthConfig
if err := mobileConfig.Unmarshal(method.Config); err != nil {
panic(fmt.Sprintf("failed to unmarshal mobile auth config: %v", err.Error()))
}
tool.DeepCopy(&cfg, mobileConfig)
cfg.Enable = *method.Enabled
value, _ := json.Marshal(mobileConfig.PlatformConfig)
cfg.PlatformConfig = string(value)
ctx.Config.Mobile = cfg
}
+10
View File
@@ -0,0 +1,10 @@
package initialize
import (
"github.com/perfect-panel/ppanel-server/initialize/migrate"
"github.com/perfect-panel/ppanel-server/internal/svc"
)
func Mysql(ctx *svc.ServiceContext) {
migrate.Migrate(ctx)
}
+51
View File
@@ -0,0 +1,51 @@
package initialize
import (
"context"
"encoding/json"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/nodeMultiplier"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Node(ctx *svc.ServiceContext) {
logger.Debug("Node config initialization")
configs, err := ctx.SystemModel.GetNodeConfig(context.Background())
if err != nil {
panic(err)
}
var nodeConfig config.NodeConfig
tool.SystemConfigSliceReflectToStruct(configs, &nodeConfig)
ctx.Config.Node = nodeConfig
// Manager initialization
if ctx.DB.Model(&system.System{}).Where("`key` = ?", "NodeMultiplierConfig").Find(&system.System{}).RowsAffected == 0 {
if err := ctx.DB.Model(&system.System{}).Create(&system.System{
Key: "NodeMultiplierConfig",
Value: "[]",
Type: "string",
Desc: "Node Multiplier Config",
Category: "server",
}).Error; err != nil {
logger.Errorf("Create Node Multiplier Config Error: %s", err.Error())
}
return
}
nodeMultiplierData, err := ctx.SystemModel.FindNodeMultiplierConfig(context.Background())
if err != nil {
logger.Error("Get Node Multiplier Config Error: ", logger.Field("error", err.Error()))
return
}
var periods []nodeMultiplier.TimePeriod
if err := json.Unmarshal([]byte(nodeMultiplierData.Value), &periods); err != nil {
logger.Error("Unmarshal Node Multiplier Config Error: ", logger.Field("error", err.Error()), logger.Field("value", nodeMultiplierData.Value))
}
ctx.NodeMultiplierManager = nodeMultiplier.NewNodeMultiplierManager(periods)
}
+11
View File
@@ -0,0 +1,11 @@
package initialize
import (
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
func OAuth(svc *svc.ServiceContext) {
logger.Debug("OAuth config initialization")
}
+23
View File
@@ -0,0 +1,23 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Register(ctx *svc.ServiceContext) {
logger.Debug("Register config initialization")
configs, err := ctx.SystemModel.GetRegisterConfig(context.Background())
if err != nil {
logger.Errorf("[Init Register Config] Get Register Config Error: %s", err.Error())
return
}
var registerConfig config.RegisterConfig
tool.SystemConfigSliceReflectToStruct(configs, &registerConfig)
ctx.Config.Register = registerConfig
}
+22
View File
@@ -0,0 +1,22 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Site(ctx *svc.ServiceContext) {
logger.Debug("initialize site config")
configs, err := ctx.SystemModel.GetSiteConfig(context.Background())
if err != nil {
panic(err)
}
var siteConfig config.SiteConfig
tool.SystemConfigSliceReflectToStruct(configs, &siteConfig)
ctx.Config.Site = siteConfig
}
+57
View File
@@ -0,0 +1,57 @@
package initialize
import (
"context"
"time"
"github.com/perfect-panel/ppanel-server/internal/model/cache"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/logger"
)
func TrafficDataToRedis(svcCtx *svc.ServiceContext) {
ctx := context.Background()
// 统计昨天的节点流量数据排行榜前10
nodeData, err := svcCtx.TrafficLogModel.TopServersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10)
if err != nil {
logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error()))
}
var nodeCacheData []cache.NodeTodayTrafficRank
for _, node := range nodeData {
serverInfo, err := svcCtx.ServerModel.FindOne(ctx, node.ServerId)
if err != nil {
logger.Errorw("查询节点信息失败", logger.Field("error", err.Error()))
continue
}
nodeCacheData = append(nodeCacheData, cache.NodeTodayTrafficRank{
ID: node.ServerId,
Name: serverInfo.Name,
Upload: node.Upload,
Download: node.Download,
Total: node.Upload + node.Download,
})
}
// 写入缓存
if err = svcCtx.NodeCache.UpdateYesterdayNodeTotalTrafficRank(ctx, nodeCacheData); err != nil {
logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error()))
}
// 统计昨天的用户流量数据排行榜前10
userData, err := svcCtx.TrafficLogModel.TopUsersTrafficByDay(ctx, time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day()-1, 0, 0, 0, 0, time.Local), 10)
if err != nil {
logger.Errorw("统计昨天的流量数据失败", logger.Field("error", err.Error()))
}
var userCacheData []cache.UserTodayTrafficRank
for _, user := range userData {
userCacheData = append(userCacheData, cache.UserTodayTrafficRank{
SID: user.SubscribeId,
Upload: user.Upload,
Download: user.Download,
Total: user.Upload + user.Download,
})
}
// 写入缓存
if err = svcCtx.NodeCache.UpdateYesterdayUserTotalTrafficRank(ctx, userCacheData); err != nil {
logger.Errorw("写入昨天的流量数据到缓存失败", logger.Field("error", err.Error()))
}
logger.Infow("初始化昨天的流量数据到缓存成功")
}
+24
View File
@@ -0,0 +1,24 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Subscribe(svc *svc.ServiceContext) {
logger.Debug("Subscribe config initialization")
configs, err := svc.SystemModel.GetSubscribeConfig(context.Background())
if err != nil {
logger.Error("[Init Subscribe Config] Get Subscribe Config Error: ", logger.Field("error", err.Error()))
return
}
var subscribeConfig config.SubscribeConfig
tool.SystemConfigSliceReflectToStruct(configs, &subscribeConfig)
svc.Config.Subscribe = subscribeConfig
}
+82
View File
@@ -0,0 +1,82 @@
package initialize
import (
"context"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/logic/telegram"
"github.com/perfect-panel/ppanel-server/internal/model/auth"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func Telegram(svc *svc.ServiceContext) {
method, err := svc.AuthModel.FindOneByMethod(context.Background(), "telegram")
if err != nil {
logger.Errorf("[Init Telegram Config] Get Telegram Config Error: %s", err.Error())
return
}
var tg config.Telegram
tgConfig := new(auth.TelegramAuthConfig)
if err = tgConfig.Unmarshal(method.Config); err != nil {
logger.Errorf("[Init Telegram Config] Unmarshal Telegram Config Error: %s", err.Error())
return
}
if tgConfig.BotToken == "" {
logger.Debug("[Init Telegram Config] Telegram Token is empty")
return
}
bot, err := tgbotapi.NewBotAPI(tg.BotToken)
if err != nil {
logger.Error("[Init Telegram Config] New Bot API Error: ", logger.Field("error", err.Error()))
return
}
if tgConfig.WebHookDomain == "" || svc.Config.Debug {
// set Long Polling mode
updateConfig := tgbotapi.NewUpdate(0)
updateConfig.Timeout = 60
updates := bot.GetUpdatesChan(updateConfig)
go func() {
for update := range updates {
if update.Message != nil {
ctx := context.Background()
l := telegram.NewTelegramLogic(ctx, svc)
l.TelegramLogic(&update)
}
}
}()
} else {
wh, err := tgbotapi.NewWebhook(fmt.Sprintf("%s/v1/telegram/webhook?secret=%s", tgConfig.WebHookDomain, tool.Md5Encode(tgConfig.BotToken, false)))
if err != nil {
logger.Errorf("[Init Telegram Config] New Webhook Error: %s", err.Error())
return
}
_, err = bot.Request(wh)
if err != nil {
logger.Errorf("[Init Telegram Config] Request Webhook Error: %s", err.Error())
return
}
}
user, err := bot.GetMe()
if err != nil {
logger.Error("[Init Telegram Config] Get Bot Info Error: ", logger.Field("error", err.Error()))
return
}
svc.Config.Telegram.BotID = user.ID
svc.Config.Telegram.BotName = user.UserName
svc.Config.Telegram.EnableNotify = tg.EnableNotify
svc.Config.Telegram.WebHookDomain = tg.WebHookDomain
svc.TelegramBot = bot
logger.Info("[Init Telegram Config] Webhook set success")
}
+469
View File
@@ -0,0 +1,469 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 头部内容 -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PPanel - Application Initialization</title>
<!-- 引入 Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Tailwind CSS 配置 -->
<script>
tailwind.config = {
darkMode: ["class"],
theme: {
container: { center: true, padding: "2rem" },
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" },
},
borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" },
},
},
}
</script>
<!-- 自定义样式 -->
<style>
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--destructive: 346.8 77.2% 49.8%;
--destructive-foreground: 355.7 100% 97.3%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
</style>
</head>
<body class="min-h-screen font-sans antialiased">
<div id="appContainer" class="container mx-auto p-4 flex flex-col items-center min-h-screen">
<div class="w-full max-w-2xl space-y-6">
<!-- 设置卡片 -->
<div id="setupCard" class="p-6 space-y-6 bg-white rounded-lg border">
<div class="flex justify-between items-center">
<h1 id="title" class="text-3xl font-bold">Welcome to PPanel Setup</h1>
<button onclick="switchLanguage()" id="langSwitch" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">中文</button>
</div>
<p id="description" class="text-gray-600 !mt-0">Let's get your PPanel application up and running. Please provide the necessary information below.</p>
<form id="setupForm" class="space-y-6" onsubmit="handleSubmit(event)">
<!-- 管理员详情 -->
<div class="space-y-4">
<h2 id="adminInfoTitle" class="font-bold text-xl">Administrator Details</h2>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="adminEmail" id="labelAdminEmail" class="text-sm font-medium">Administrator Email</label>
<input type="email" id="adminEmail" name="adminEmail" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter administrator email" data-placeholder-en="Enter administrator email" data-placeholder-zh="请输入管理员邮箱" oninput="validateInput(this)">
<span id="adminEmailError" class="text-red-500 text-sm mt-1 hidden">Invalid email address.</span>
</div>
<div>
<label for="adminPassword" id="labelAdminPassword" class="text-sm font-medium">Administrator Password</label>
<input type="password" id="adminPassword" name="adminPassword" required minlength="6" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter administrator password" data-placeholder-en="Enter administrator password" data-placeholder-zh="请输入管理员密码" oninput="validateInput(this)">
<span id="adminPasswordError" class="text-red-500 text-sm mt-1 hidden">Password must be at least 6 characters.</span>
</div>
</div>
</div>
<!-- MySQL 数据库设置 -->
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 id="mysqlInfoTitle" class="font-bold text-xl">MySQL Database Setup</h2>
<button type="button" onclick="testConnection('mysql')" id="testMySQLButton" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">Test MySQL Connection</button>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="mysqlHost" id="labelMysqlHost" class="text-sm font-medium">Database Host</label>
<input type="text" id="mysqlHost" name="mysqlHost" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="localhost" placeholder="Enter database host" data-placeholder-en="Enter database host" data-placeholder-zh="请输入数据库主机">
<span id="mysqlHostError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid host.</span>
</div>
<div>
<label for="mysqlPort" id="labelMysqlPort" class="text-sm font-medium">Database Port</label>
<input type="number" id="mysqlPort" name="mysqlPort" required min="1" max="65535" class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="3306" placeholder="Enter database port" data-placeholder-en="Enter database port" data-placeholder-zh="请输入数据库端口">
<span id="mysqlPortError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid port number.</span>
</div>
<div>
<label for="mysqlUser" id="labelMysqlUser" class="text-sm font-medium">Database User</label>
<input type="text" id="mysqlUser" name="mysqlUser" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="root" placeholder="Enter database user" data-placeholder-en="Enter database user" data-placeholder-zh="请输入数据库用户名">
<span id="mysqlUserError" class="text-red-500 text-sm mt-1 hidden">Please enter a username.</span>
</div>
<div>
<label for="mysqlPassword" id="labelMysqlPassword" class="text-sm font-medium">Database Password</label>
<input type="password" id="mysqlPassword" name="mysqlPassword" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter database password" data-placeholder-en="Enter database password" data-placeholder-zh="请输入数据库密码">
</div>
<div>
<label for="mysqlDatabase" id="labelMysqlDatabase" class="text-sm font-medium">Database Name</label>
<input type="text" id="mysqlDatabase" name="mysqlDatabase" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="ppanel_db" placeholder="Enter database name" data-placeholder-en="Enter database name" data-placeholder-zh="请输入数据库名称">
<span id="mysqlDatabaseError" class="text-red-500 text-sm mt-1 hidden">Please enter a database name.</span>
</div>
</div>
</div>
<!-- Redis 缓存设置 -->
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 id="redisInfoTitle" class="font-bold text-xl">Redis Cache Setup</h2>
<button type="button" onclick="testConnection('redis')" id="testRedisButton" class="h-10 px-4 py-2 border rounded-md text-sm font-medium bg-background hover:bg-accent hover:text-accent-foreground">Test Redis Connection</button>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="redisHost" id="labelRedisHost" class="text-sm font-medium">Redis Host</label>
<input type="text" id="redisHost" name="redisHost" required class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="127.0.0.1" placeholder="Enter Redis host" data-placeholder-en="Enter Redis host" data-placeholder-zh="请输入 Redis 主机">
<span id="redisHostError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid host.</span>
</div>
<div>
<label for="redisPort" id="labelRedisPort" class="text-sm font-medium">Redis Port</label>
<input type="number" id="redisPort" name="redisPort" required min="1" max="65535" class="input-field mt-1 block w-full rounded-md border px-3 py-2" value="6379" placeholder="Enter Redis port" data-placeholder-en="Enter Redis port" data-placeholder-zh="请输入 Redis 端口">
<span id="redisPortError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid port number.</span>
</div>
<div>
<label for="redisPassword" id="labelRedisPassword" class="text-sm font-medium">Redis Password</label>
<input type="password" id="redisPassword" name="redisPassword" class="input-field mt-1 block w-full rounded-md border px-3 py-2" placeholder="Enter Redis password (optional)" data-placeholder-en="Enter Redis password (optional)" data-placeholder-zh="请输入 Redis 密码(可选)">
</div>
</div>
</div>
<!-- 提交按钮 -->
<button type="submit" id="submitButton" disabled class="w-full h-10 px-4 py-2 bg-primary text-white rounded-md opacity-50 cursor-not-allowed">Start Initialization</button>
</form>
</div>
</div>
</div>
<!-- 安装中卡片 -->
<div id="installingCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
<div class="w-full max-w-md space-y-6 text-center">
<h1 id="installingTitle" class="text-3xl font-bold">Installing...</h1>
<p id="installingMessage">Please wait while we initialize your application.</p>
<div class="flex justify-center">
<svg class="animate-spin h-10 w-10 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
</div>
</div>
</div>
<!-- 安装完成卡片 -->
<div id="installationCompleteCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
<div class="w-full max-w-md space-y-6 text-center">
<h1 id="installationCompleteTitle" class="text-3xl font-bold">Installation Complete</h1>
<p id="installationCompleteMessage">You can now close this page.</p>
</div>
</div>
<!-- 已初始化页面 -->
<div id="alreadyInitializedCard" class="fixed inset-0 flex items-center justify-center bg-white p-6 hidden">
<div class="w-full max-w-md space-y-6 text-center">
<h1 id="alreadyInitializedTitle" class="text-3xl font-bold">Already Initialized</h1>
<p id="alreadyInitializedMessage">Your PPanel application has already been initialized.</p>
</div>
</div>
<!-- 对话框 -->
<div id="dialog" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-lg p-6 w-96">
<h2 id="dialogTitle" class="text-xl font-bold mb-4"></h2>
<p id="dialogMessage" class="mb-4"></p>
<div class="flex justify-end">
<button onclick="closeDialog()" class="h-10 px-4 py-2 bg-primary text-white rounded-md">Close</button>
</div>
</div>
</div>
<!-- JavaScript -->
<script>
let currentLang = 'en';
const translations = {
en: {
// 英文翻译内容
title: 'Welcome to PPanel Setup',
description: "Let's get your PPanel application up and running. Please provide the necessary information below.",
adminInfoTitle: 'Administrator Details',
mysqlInfoTitle: 'MySQL Database Setup',
redisInfoTitle: 'Redis Cache Setup',
adminEmail: 'Administrator Email',
adminPassword: 'Administrator Password',
mysqlHost: 'Database Host',
mysqlPort: 'Database Port',
mysqlUser: 'Database User',
mysqlPassword: 'Database Password',
mysqlDatabase: 'Database Name',
redisHost: 'Redis Host',
redisPort: 'Redis Port',
redisPassword: 'Redis Password',
submit: 'Start Initialization',
switchLang: '中文',
testMySQLConnection: 'Test MySQL Connection',
testRedisConnection: 'Test Redis Connection',
connectionSuccess: 'Connection Successful',
connectionError: 'Connection Failed',
initializationError: 'Initialization Error',
initializationErrorMessage: 'An error occurred during initialization. Please try again.',
installingTitle: 'Installing...',
installingMessage: 'Please wait while we initialize your application.',
installationCompleteTitle: 'Installation Complete',
installationCompleteMessage: 'You can now close this page.',
proceedButton: 'Go to Application',
mysqlConnectionSuccess: 'Successfully connected to the MySQL database.',
mysqlConnectionError: 'Failed to connect to the MySQL database. Please check your settings.',
redisConnectionSuccess: 'Successfully connected to the Redis server.',
redisConnectionError: 'Failed to connect to the Redis server. Please check your settings.',
alreadyInitializedTitle: 'Already Initialized',
alreadyInitializedMessage: 'Your PPanel application has already been initialized.',
invalidEmail: 'Invalid email address.',
invalidPassword: 'Password must be at least 6 characters.',
},
zh: {
// 中文翻译内容
title: '欢迎使用 PPanel 设置向导',
description: '让我们一起启动您的 PPanel 应用程序。请提供以下必要信息。',
adminInfoTitle: '管理员详情',
mysqlInfoTitle: 'MySQL 数据库设置',
redisInfoTitle: 'Redis 缓存设置',
adminEmail: '管理员邮箱',
adminPassword: '管理员密码',
mysqlHost: '数据库主机',
mysqlPort: '数据库端口',
mysqlUser: '数据库用户',
mysqlPassword: '数据库密码',
mysqlDatabase: '数据库名称',
redisHost: 'Redis 主机',
redisPort: 'Redis 端口',
redisPassword: 'Redis 密码',
submit: '开始初始化',
switchLang: 'English',
testMySQLConnection: '测试 MySQL 连接',
testRedisConnection: '测试 Redis 连接',
connectionSuccess: '连接成功',
connectionError: '连接失败',
initializationError: '初始化错误',
initializationErrorMessage: '初始化过程中发生错误。请重试。',
installingTitle: '正在安装...',
installingMessage: '请稍候,我们正在初始化您的应用程序。',
installationCompleteTitle: '安装完成',
installationCompleteMessage: '您现在可以关闭此页面。',
proceedButton: '前往应用程序',
mysqlConnectionSuccess: '成功连接到 MySQL 数据库。',
mysqlConnectionError: '无法连接到 MySQL 数据库。请检查您的设置。',
redisConnectionSuccess: '成功连接到 Redis 服务器。',
redisConnectionError: '无法连接到 Redis 服务器。请检查您的设置。',
alreadyInitializedTitle: '已初始化',
alreadyInitializedMessage: '您的 PPanel 应用程序已被初始化。',
invalidEmail: '无效的邮箱地址。',
invalidPassword: '密码至少需要6个字符。',
}
};
function switchLanguage() {
currentLang = currentLang === 'en' ? 'zh' : 'en';
updateTexts();
}
function updateTexts() {
const t = translations[currentLang];
document.getElementById('title').textContent = t.title;
document.getElementById('description').textContent = t.description;
document.getElementById('adminInfoTitle').textContent = t.adminInfoTitle;
document.getElementById('mysqlInfoTitle').textContent = t.mysqlInfoTitle;
document.getElementById('redisInfoTitle').textContent = t.redisInfoTitle;
document.getElementById('labelAdminEmail').textContent = t.adminEmail;
document.getElementById('labelAdminPassword').textContent = t.adminPassword;
document.getElementById('labelMysqlHost').textContent = t.mysqlHost;
document.getElementById('labelMysqlPort').textContent = t.mysqlPort;
document.getElementById('labelMysqlUser').textContent = t.mysqlUser;
document.getElementById('labelMysqlPassword').textContent = t.mysqlPassword;
document.getElementById('labelMysqlDatabase').textContent = t.mysqlDatabase;
document.getElementById('labelRedisHost').textContent = t.redisHost;
document.getElementById('labelRedisPort').textContent = t.redisPort;
document.getElementById('labelRedisPassword').textContent = t.redisPassword;
document.getElementById('submitButton').textContent = t.submit;
document.getElementById('langSwitch').textContent = t.switchLang;
document.getElementById('testMySQLButton').textContent = t.testMySQLConnection;
document.getElementById('testRedisButton').textContent = t.testRedisConnection;
document.getElementById('installingTitle').textContent = t.installingTitle;
document.getElementById('installingMessage').textContent = t.installingMessage;
document.getElementById('installationCompleteTitle').textContent = t.installationCompleteTitle;
document.getElementById('installationCompleteMessage').textContent = t.installationCompleteMessage;
document.getElementById('alreadyInitializedTitle').textContent = t.alreadyInitializedTitle;
document.getElementById('alreadyInitializedMessage').textContent = t.alreadyInitializedMessage;
// 更新 placeholder 文本
const inputs = document.querySelectorAll('.input-field');
inputs.forEach((input) => {
const placeholder = input.getAttribute(`data-placeholder-${currentLang}`);
if (placeholder) {
input.setAttribute('placeholder', placeholder);
}
});
}
function checkInitialization() {
const isInitialized = false; // 需要从服务器获取实际状态
if (isInitialized) {
document.getElementById('appContainer').classList.add('hidden');
document.getElementById('alreadyInitializedCard').classList.remove('hidden');
}
}
function testConnection(type) {
const t = translations[currentLang];
let data = {};
let url = '';
if (type === 'mysql') {
data = {
host: document.getElementById('mysqlHost').value,
port: document.getElementById('mysqlPort').value,
user: document.getElementById('mysqlUser').value,
password: document.getElementById('mysqlPassword').value,
database: document.getElementById('mysqlDatabase').value,
};
url = '/init/mysql/test';
} else if (type === 'redis') {
data = {
host: document.getElementById('redisHost').value,
port: document.getElementById('redisPort').value,
password: document.getElementById('redisPassword').value,
};
url = '/init/redis/test';
}
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(result => {
console.log(result);
if (result.code === 200 && result.status) {
showDialog(t.connectionSuccess, result.msg || (type === 'mysql' ? t.mysqlConnectionSuccess : t.redisConnectionSuccess));
} else {
showDialog(t.connectionError, result.msg || (type === 'mysql' ? t.mysqlConnectionError : t.redisConnectionError));
}
})
.catch(error => {
console.error('Error:', error);
showDialog(t.connectionError, t.initializationErrorMessage);
});
}
function handleSubmit(event) {
event.preventDefault();
const form = document.getElementById('setupForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const t = translations[currentLang];
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// 显示安装中界面
document.getElementById('setupCard').classList.add('hidden');
document.getElementById('installingCard').classList.remove('hidden');
// 发送初始化请求
fetch('/init/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(result => {
document.getElementById('installingCard').classList.add('hidden');
if (result.code === 200 && result.status) {
document.getElementById('installationCompleteCard').classList.remove('hidden');
} else {
showDialog(t.initializationError, result.msg || t.initializationErrorMessage);
document.getElementById('setupCard').classList.remove('hidden');
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('installingCard').classList.add('hidden');
showDialog(t.initializationError, t.initializationErrorMessage);
document.getElementById('setupCard').classList.remove('hidden');
});
}
function showDialog(title, message) {
document.getElementById('dialogTitle').textContent = title;
document.getElementById('dialogMessage').textContent = message;
document.getElementById('dialog').classList.remove('hidden');
}
function closeDialog() {
document.getElementById('dialog').classList.add('hidden');
}
// 实时表单校验
function validateInput(input) {
const errorSpan = document.getElementById(input.id + 'Error');
if (input.type === 'email') {
// 邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input.value)) {
errorSpan.textContent = translations[currentLang].invalidEmail;
errorSpan.classList.remove('hidden');
input.setCustomValidity(translations[currentLang].invalidEmail);
} else {
input.setCustomValidity('');
errorSpan.classList.add('hidden');
}
} else if (input.validity.valid) {
errorSpan.classList.add('hidden');
input.setCustomValidity('');
} else {
errorSpan.classList.remove('hidden');
}
updateSubmitButtonState();
}
function updateSubmitButtonState() {
const form = document.getElementById('setupForm');
const submitButton = document.getElementById('submitButton');
if (form.checkValidity()) {
submitButton.disabled = false;
submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
} else {
submitButton.disabled = true;
submitButton.classList.add('opacity-50', 'cursor-not-allowed');
}
}
// 添加事件监听器
document.addEventListener('DOMContentLoaded', () => {
const inputs = document.querySelectorAll('.input-field');
inputs.forEach((input) => {
input.addEventListener('input', () => validateInput(input));
});
updateSubmitButtonState();
updateTexts();
checkInitialization();
});
</script>
</body>
</html>
+48
View File
@@ -0,0 +1,48 @@
package initialize
import (
"context"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/ppanel-server/internal/config"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
type verifyConfig struct {
TurnstileSiteKey string
TurnstileSecret string
EnableLoginVerify bool
EnableRegisterVerify bool
EnableResetPasswordVerify bool
}
func Verify(svc *svc.ServiceContext) {
logger.Debug("Verify config initialization")
configs, err := svc.SystemModel.GetVerifyConfig(context.Background())
if err != nil {
logger.Error("[Init Verify Config] Get Verify Config Error: ", logger.Field("error", err.Error()))
return
}
var verify verifyConfig
tool.SystemConfigSliceReflectToStruct(configs, &verify)
svc.Config.Verify = config.Verify{
TurnstileSiteKey: verify.TurnstileSiteKey,
TurnstileSecret: verify.TurnstileSecret,
LoginVerify: verify.EnableLoginVerify,
RegisterVerify: verify.EnableRegisterVerify,
ResetPasswordVerify: verify.EnableResetPasswordVerify,
}
logger.Debug("Verify code config initialization")
var verifyCodeConfig config.VerifyCode
cfg, err := svc.SystemModel.GetVerifyCodeConfig(context.Background())
if err != nil {
logger.Errorf("[Init Verify Config] Get Verify Code Config Error: %s", err.Error())
return
}
tool.SystemConfigSliceReflectToStruct(cfg, &verifyCodeConfig)
svc.Config.VerifyCode = verifyCodeConfig
}
+127
View File
@@ -0,0 +1,127 @@
package initialize
import (
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"gorm.io/gorm"
"github.com/perfect-panel/ppanel-server/initialize/migrate/patch"
"github.com/perfect-panel/ppanel-server/internal/model/system"
"github.com/perfect-panel/ppanel-server/internal/svc"
"github.com/perfect-panel/ppanel-server/pkg/constant"
"github.com/perfect-panel/ppanel-server/pkg/tool"
)
func VerifyVersion(ctx *svc.ServiceContext) {
var configVersion system.System
err := ctx.DB.Transaction(func(db *gorm.DB) error {
db.Model(&system.System{}).Where("`category` = 'system' AND `key` = 'Version'").First(&configVersion)
if configVersion.Value != constant.Version {
// Version eg: 1.0.0(10000)
current := tool.ExtractVersionNumber(constant.Version)
sqlVersion := tool.ExtractVersionNumber(configVersion.Value)
logger.Infof("Verify System Version, current version: %d, datebase version: %d", current, sqlVersion)
if current > sqlVersion {
// Migrate to Milestone Version
//
// Migrate SQL to 0.1.7(01703)
if sqlVersion < 1705 {
if err := migrate01701(db, sqlVersion); err != nil {
return err
}
}
// 重新执行2000版本的迁移
if sqlVersion == 2002 {
sqlVersion = 2000
}
// Migrate SQL to 0.2.0(02000)
if sqlVersion < 2009 {
if err := migrate02000(db, sqlVersion); err != nil {
return err
}
}
// Migrate SQL to 0.3.0(03000)
if sqlVersion < 3001 {
if err := migrate03000(db, sqlVersion); err != nil {
return err
}
}
}
}
return nil
})
if err != nil {
panic("update system version error:" + err.Error())
}
}
func migrate01701(db *gorm.DB, sqlVersion int) error {
migrations := map[int]func(*gorm.DB) error{
1200: patch.Migrate01200,
1201: patch.Migrate01201,
1202: patch.Migrate01202,
1203: patch.Migrate01203,
1204: patch.Migrate01204,
1205: patch.Migrate01205,
1301: patch.Migrate01301,
1602: patch.Migrate01602,
1701: patch.Migrate01701,
1702: patch.Migrate01702,
1703: patch.Migrate01703,
1704: patch.Migrate01704,
1705: patch.Migrate01705,
}
for v, migrate := range migrations {
if sqlVersion < v {
if err := migrate(db); err != nil {
return fmt.Errorf("migrator %d version error: %w", v, err)
}
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
}
}
return nil
}
func migrate02000(db *gorm.DB, sqlVersion int) error {
migrations := map[int]func(*gorm.DB) error{
2000: patch.Migrate02000,
2001: patch.Migrate02001,
2002: patch.Migrate02002,
2003: patch.Migrate02003,
2007: patch.Migrate02007,
2008: patch.Migrate02008,
2009: patch.Migrate02009,
2010: patch.Migrate02010,
2011: patch.Migrate02011,
3001: patch.Migrate03001,
}
for v, migrate := range migrations {
if sqlVersion < v {
if err := migrate(db); err != nil {
return fmt.Errorf("migrator %d version error: %w", v, err)
}
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
}
}
return nil
}
func migrate03000(db *gorm.DB, sqlVersion int) error {
migrations := map[int]func(*gorm.DB) error{
3001: patch.Migrate03001,
}
for v, migrate := range migrations {
if sqlVersion < v {
if err := migrate(db); err != nil {
return fmt.Errorf("migrator %d version error: %w", v, err)
}
logger.Infof(fmt.Sprintf("Migrate %d version success", v))
}
}
return nil
}