2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-05 03:22:32 +08:00
Files
panel/app/http/controllers/plugins/mysql_controller.go
2024-07-28 20:58:33 +08:00

507 lines
16 KiB
Go

package plugins
import (
"fmt"
"regexp"
"github.com/goravel/framework/contracts/http"
"github.com/spf13/cast"
"github.com/TheTNB/panel/v2/app/models"
"github.com/TheTNB/panel/v2/internal"
"github.com/TheTNB/panel/v2/internal/services"
"github.com/TheTNB/panel/v2/pkg/db"
"github.com/TheTNB/panel/v2/pkg/h"
"github.com/TheTNB/panel/v2/pkg/io"
"github.com/TheTNB/panel/v2/pkg/shell"
"github.com/TheTNB/panel/v2/pkg/str"
"github.com/TheTNB/panel/v2/pkg/systemctl"
"github.com/TheTNB/panel/v2/pkg/types"
)
type MySQLController struct {
setting internal.Setting
backup internal.Backup
}
func NewMySQLController() *MySQLController {
return &MySQLController{
setting: services.NewSettingImpl(),
backup: services.NewBackupImpl(),
}
}
// GetConfig 获取配置
func (r *MySQLController) GetConfig(ctx http.Context) http.Response {
config, err := io.Read("/www/server/mysql/conf/my.cnf")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, "获取MySQL配置失败")
}
return h.Success(ctx, config)
}
// SaveConfig 保存配置
func (r *MySQLController) SaveConfig(ctx http.Context) http.Response {
config := ctx.Request().Input("config")
if len(config) == 0 {
return h.Error(ctx, http.StatusUnprocessableEntity, "配置不能为空")
}
if err := io.Write("/www/server/mysql/conf/my.cnf", config, 0644); err != nil {
return h.Error(ctx, http.StatusInternalServerError, "写入MySQL配置失败")
}
if err := systemctl.Reload("mysqld"); err != nil {
return h.Error(ctx, http.StatusInternalServerError, "重载MySQL失败")
}
return h.Success(ctx, nil)
}
// Load 获取负载
func (r *MySQLController) Load(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return h.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
status, _ := systemctl.Status("mysqld")
if !status {
return h.Success(ctx, []types.NV{})
}
raw, err := shell.Execf("mysqladmin -uroot -p" + rootPassword + " extended-status 2>&1")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, "获取MySQL负载失败")
}
var data []map[string]string
expressions := []struct {
regex string
name string
}{
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到磁盘"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
}
for _, expression := range expressions {
re := regexp.MustCompile(expression.regex)
matches := re.FindStringSubmatch(raw)
if len(matches) > 1 {
d := map[string]string{"name": expression.name, "value": matches[1]}
if expression.name == "发送" || expression.name == "接收" {
d["value"] = str.FormatBytes(cast.ToFloat64(matches[1]))
}
data = append(data, d)
}
}
// 索引命中率
readRequests := cast.ToFloat64(data[9]["value"])
reads := cast.ToFloat64(data[10]["value"])
data[9]["value"] = fmt.Sprintf("%.2f%%", readRequests/(reads+readRequests)*100)
// Innodb 索引命中率
bufferPoolReads := cast.ToFloat64(data[11]["value"])
bufferPoolReadRequests := cast.ToFloat64(data[12]["value"])
data[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
return h.Success(ctx, data)
}
// ErrorLog 获取错误日志
func (r *MySQLController) ErrorLog(ctx http.Context) http.Response {
log, err := shell.Execf("tail -n 100 /www/server/mysql/mysql-error.log")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, log)
}
return h.Success(ctx, log)
}
// ClearErrorLog 清空错误日志
func (r *MySQLController) ClearErrorLog(ctx http.Context) http.Response {
if out, err := shell.Execf("echo '' > /www/server/mysql/mysql-error.log"); err != nil {
return h.Error(ctx, http.StatusInternalServerError, out)
}
return h.Success(ctx, nil)
}
// SlowLog 获取慢查询日志
func (r *MySQLController) SlowLog(ctx http.Context) http.Response {
log, err := shell.Execf("tail -n 100 /www/server/mysql/mysql-slow.log")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, log)
}
return h.Success(ctx, log)
}
// ClearSlowLog 清空慢查询日志
func (r *MySQLController) ClearSlowLog(ctx http.Context) http.Response {
if out, err := shell.Execf("echo '' > /www/server/mysql/mysql-slow.log"); err != nil {
return h.Error(ctx, http.StatusInternalServerError, out)
}
return h.Success(ctx, nil)
}
// GetRootPassword 获取root密码
func (r *MySQLController) GetRootPassword(ctx http.Context) http.Response {
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
if len(rootPassword) == 0 {
return h.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码为空")
}
return h.Success(ctx, rootPassword)
}
// SetRootPassword 设置root密码
func (r *MySQLController) SetRootPassword(ctx http.Context) http.Response {
rootPassword := ctx.Request().Input("password")
if len(rootPassword) == 0 {
return h.Error(ctx, http.StatusUnprocessableEntity, "MySQL root密码不能为空")
}
oldRootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", oldRootPassword, r.getSock(), "unix")
if err != nil {
// 尝试安全模式直接改密
if err = db.MySQLResetRootPassword(rootPassword); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
} else {
if err = mysql.UserPassword("root", rootPassword); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
}
if err = r.setting.Set(models.SettingKeyMysqlRootPassword, rootPassword); err != nil {
return h.Error(ctx, http.StatusInternalServerError, fmt.Sprintf("设置保存失败: %v", err))
}
return h.Success(ctx, nil)
}
// DatabaseList 获取数据库列表
func (r *MySQLController) DatabaseList(ctx http.Context) http.Response {
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
if err != nil {
return h.Success(ctx, http.Json{
"total": 0,
"items": []types.MySQLDatabase{},
})
}
databases, err := mysql.Databases()
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, "获取数据库列表失败")
}
paged, total := h.Paginate(ctx, databases)
return h.Success(ctx, http.Json{
"total": total,
"items": paged,
})
}
// AddDatabase 添加数据库
func (r *MySQLController) AddDatabase(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:32",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.DatabaseCreate(database); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.UserCreate(user, password); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.PrivilegesGrant(user, database); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// DeleteDatabase 删除数据库
func (r *MySQLController) DeleteDatabase(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
database := ctx.Request().Input("database")
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.DatabaseDrop(database); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// BackupList 获取备份列表
func (r *MySQLController) BackupList(ctx http.Context) http.Response {
backups, err := r.backup.MysqlList()
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
paged, total := h.Paginate(ctx, backups)
return h.Success(ctx, http.Json{
"total": total,
"items": paged,
})
}
// UploadBackup 上传备份
func (r *MySQLController) UploadBackup(ctx http.Context) http.Response {
file, err := ctx.Request().File("file")
if err != nil {
return h.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
if !io.Exists(backupPath) {
if err = io.Mkdir(backupPath, 0644); err != nil {
return nil
}
}
name := file.GetClientOriginalName()
_, err = file.StoreAs(backupPath, name)
if err != nil {
return h.Error(ctx, http.StatusUnprocessableEntity, "上传文件失败")
}
return h.Success(ctx, nil)
}
// CreateBackup 创建备份
func (r *MySQLController) CreateBackup(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
}); sanitize != nil {
return sanitize
}
database := ctx.Request().Input("database")
if err := r.backup.MysqlBackup(database); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// DeleteBackup 删除备份
func (r *MySQLController) DeleteBackup(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"name": "required|min_len:1|max_len:255",
}); sanitize != nil {
return sanitize
}
backupPath := r.setting.Get(models.SettingKeyBackupPath) + "/mysql"
fileName := ctx.Request().Input("name")
if err := io.Remove(backupPath + "/" + fileName); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// RestoreBackup 还原备份
func (r *MySQLController) RestoreBackup(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"backup": "required|min_len:1|max_len:255",
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys",
}); sanitize != nil {
return sanitize
}
if err := r.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("backup")); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// UserList 用户列表
func (r *MySQLController) UserList(ctx http.Context) http.Response {
password := r.setting.Get(models.SettingKeyMysqlRootPassword)
mysql, err := db.NewMySQL("root", password, r.getSock(), "unix")
if err != nil {
return h.Success(ctx, http.Json{
"total": 0,
"items": []types.MySQLUser{},
})
}
users, err := mysql.Users()
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, "获取用户列表失败")
}
paged, total := h.Paginate(ctx, users)
return h.Success(ctx, http.Json{
"total": total,
"items": paged,
})
}
// AddUser 添加用户
func (r *MySQLController) AddUser(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:32",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
database := ctx.Request().Input("database")
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.UserCreate(user, password); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.PrivilegesGrant(user, database); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// DeleteUser 删除用户
func (r *MySQLController) DeleteUser(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.UserDrop(user); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// SetUserPassword 设置用户密码
func (r *MySQLController) SetUserPassword(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"password": "required|min_len:8|max_len:32",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
password := ctx.Request().Input("password")
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.UserPassword(user, password); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// SetUserPrivileges 设置用户权限
func (r *MySQLController) SetUserPrivileges(ctx http.Context) http.Response {
if sanitize := h.Sanitize(ctx, map[string]string{
"user": "required|min_len:1|max_len:32|regex:^[a-zA-Z0-9_]+$",
"database": "required|min_len:1|max_len:64|regex:^[a-zA-Z0-9_]+$",
}); sanitize != nil {
return sanitize
}
rootPassword := r.setting.Get(models.SettingKeyMysqlRootPassword)
user := ctx.Request().Input("user")
database := ctx.Request().Input("database")
mysql, err := db.NewMySQL("root", rootPassword, r.getSock(), "unix")
if err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
if err = mysql.PrivilegesGrant(user, database); err != nil {
return h.Error(ctx, http.StatusInternalServerError, err.Error())
}
return h.Success(ctx, nil)
}
// getSock 获取sock文件位置
func (r *MySQLController) getSock() string {
if io.Exists("/tmp/mysql.sock") {
return "/tmp/mysql.sock"
}
if io.Exists("/www/server/mysql/config/my.cnf") {
config, _ := io.Read("/www/server/mysql/config/my.cnf")
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
matches := re.FindStringSubmatch(config)
if len(matches) > 2 {
return matches[2]
}
}
if io.Exists("/etc/my.cnf") {
config, _ := io.Read("/etc/my.cnf")
re := regexp.MustCompile(`socket\s*=\s*(['"]?)([^'"]+)`)
matches := re.FindStringSubmatch(config)
if len(matches) > 2 {
return matches[2]
}
}
return "/tmp/mysql.sock"
}