mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 07:57:21 +08:00
feat: command and user service
This commit is contained in:
52
.gitlab-ci.yml
Normal file
52
.gitlab-ci.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
image: golang:alpine
|
||||
|
||||
# 在每个任务执行前运行
|
||||
before_script:
|
||||
- mkdir -p .go
|
||||
- go version
|
||||
- go env -w GO111MODULE=on
|
||||
- go env -w GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
.go_cache:
|
||||
variables:
|
||||
GOPATH: $CI_PROJECT_DIR/.go
|
||||
cache:
|
||||
paths:
|
||||
- .go/pkg/mod/
|
||||
|
||||
# 全局变量
|
||||
variables:
|
||||
OUTPUT_NAME: "panel"
|
||||
GO111MODULE: "on"
|
||||
GOPROXY: "https://goproxy.cn,direct"
|
||||
|
||||
stages:
|
||||
- prepare
|
||||
- build
|
||||
|
||||
golangci_lint:
|
||||
stage: prepare
|
||||
image: golangci/golangci-lint:latest-alpine
|
||||
extends: .go_cache
|
||||
allow_failure: true
|
||||
script:
|
||||
- golangci-lint run --timeout 30m
|
||||
|
||||
unit_test:
|
||||
stage: prepare
|
||||
extends: .go_cache
|
||||
allow_failure: true
|
||||
script:
|
||||
- go test -v -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
build:
|
||||
stage: build
|
||||
extends: .go_cache
|
||||
script:
|
||||
- go mod download
|
||||
- CGO_ENABLED=0 go build -ldflags '-s -w --extldflags "-static -fpic"' -o $OUTPUT_NAME
|
||||
artifacts:
|
||||
name: "$OUTPUT_NAME"
|
||||
paths:
|
||||
- $OUTPUT_NAME
|
||||
expire_in: 1 week
|
||||
321
app/console/commands/panel.go
Normal file
321
app/console/commands/panel.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/app/services"
|
||||
"panel/packages/helpers"
|
||||
)
|
||||
|
||||
type Panel struct {
|
||||
}
|
||||
|
||||
// Signature The name and signature of the console command.
|
||||
func (receiver *Panel) Signature() string {
|
||||
return "panel"
|
||||
}
|
||||
|
||||
// Description The console command description.
|
||||
func (receiver *Panel) Description() string {
|
||||
return "[面板] 命令行"
|
||||
}
|
||||
|
||||
// Extend The console command extend.
|
||||
func (receiver *Panel) Extend() command.Extend {
|
||||
return command.Extend{
|
||||
Category: "panel",
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Execute the console command.
|
||||
func (receiver *Panel) Handle(ctx console.Context) error {
|
||||
action := ctx.Argument(0)
|
||||
arg1 := ctx.Argument(1)
|
||||
arg2 := ctx.Argument(2)
|
||||
|
||||
switch action {
|
||||
case "init":
|
||||
var check models.User
|
||||
err := facades.Orm().Query().FirstOrFail(&check)
|
||||
if err == nil {
|
||||
color.Redln("面板已初始化")
|
||||
return nil
|
||||
}
|
||||
|
||||
settings := []models.Setting{{Key: "name", Value: "耗子Linux面板"}, {Key: "monitor", Value: "1"}, {Key: "monitor_days", Value: "30"}, {Key: "mysql_root_password", Value: ""}, {Key: "postgresql_root_password", Value: ""}}
|
||||
err = facades.Orm().Query().Create(&settings)
|
||||
if err != nil {
|
||||
color.Redln("初始化失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
hash, err := facades.Hash().Make(helpers.RandomString(32))
|
||||
if err != nil {
|
||||
color.Redln("初始化失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
user := services.NewUserImpl()
|
||||
_, err = user.Create("admin", hash)
|
||||
if err != nil {
|
||||
color.Redln("创建管理员失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("初始化成功")
|
||||
break
|
||||
|
||||
case "getInfo":
|
||||
var user models.User
|
||||
err := facades.Orm().Query().Where("id", 1).FirstOrFail(&user)
|
||||
if err != nil {
|
||||
color.Redln("获取管理员信息失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
password := helpers.RandomString(16)
|
||||
hash, err := facades.Hash().Make(password)
|
||||
if err != nil {
|
||||
color.Redln("生成密码失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
user.Username = helpers.RandomString(8)
|
||||
user.Password = hash
|
||||
|
||||
err = facades.Orm().Query().Save(&user)
|
||||
if err != nil {
|
||||
color.Redln("保存管理员信息失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
nginxConf, err := os.ReadFile("/www/server/nginx/conf/nginx.conf")
|
||||
if err != nil {
|
||||
color.Redln("获取面板端口失败,请检查Nginx主配置文件")
|
||||
return nil
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`listen\s+(\d+)`).FindStringSubmatch(string(nginxConf))
|
||||
if len(match) < 2 {
|
||||
color.Redln("获取面板端口失败,请检查Nginx主配置文件")
|
||||
return nil
|
||||
}
|
||||
|
||||
port := match[1]
|
||||
color.Greenln("用户名: " + user.Username)
|
||||
color.Greenln("密码: " + password)
|
||||
color.Greenln("面板端口: " + port)
|
||||
break
|
||||
|
||||
case "getPort":
|
||||
nginxConf, err := os.ReadFile("/www/server/nginx/conf/nginx.conf")
|
||||
if err != nil {
|
||||
color.Redln("获取面板端口失败,请检查Nginx主配置文件")
|
||||
return nil
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`listen\s+(\d+)`).FindStringSubmatch(string(nginxConf))
|
||||
if len(match) < 2 {
|
||||
color.Redln("获取面板端口失败,请检查Nginx主配置文件")
|
||||
return nil
|
||||
}
|
||||
|
||||
port := match[1]
|
||||
color.Greenln("面板端口: " + port)
|
||||
break
|
||||
|
||||
case "writePlugin":
|
||||
slug := arg1
|
||||
version := arg2
|
||||
if len(slug) == 0 || len(version) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
var plugin models.Plugin
|
||||
err := facades.Orm().Query().UpdateOrCreate(&plugin, models.Plugin{
|
||||
Slug: slug,
|
||||
}, models.Plugin{
|
||||
Version: version,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
color.Redln("写入插件安装状态失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入插件安装状态成功")
|
||||
break
|
||||
|
||||
case "deletePlugin":
|
||||
slug := arg1
|
||||
if len(slug) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := facades.Orm().Query().Where("slug", slug).Delete(&models.Plugin{})
|
||||
if err != nil {
|
||||
color.Redln("移除插件安装状态失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("移除插件安装状态成功")
|
||||
break
|
||||
|
||||
case "writeMysqlPassword":
|
||||
password := arg1
|
||||
if len(password) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
var setting models.Setting
|
||||
err := facades.Orm().Query().UpdateOrCreate(&setting, models.Setting{
|
||||
Key: "mysql_root_password",
|
||||
}, models.Setting{
|
||||
Value: password,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
color.Redln("写入MySQL root密码失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入MySQL root密码成功")
|
||||
break
|
||||
|
||||
case "cleanRunningTask":
|
||||
_, err := facades.Orm().Query().Model(&models.Task{}).Where("status", models.TaskStatusRunning).Update("status", models.TaskStatusFailed)
|
||||
if err != nil {
|
||||
color.Redln("清理正在运行的任务失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("清理正在运行的任务成功")
|
||||
break
|
||||
|
||||
case "backup":
|
||||
|
||||
case "writeSite":
|
||||
name := arg1
|
||||
status := cast.ToBool(arg2)
|
||||
path := ctx.Argument(3)
|
||||
php := cast.ToInt(ctx.Argument(4))
|
||||
ssl := cast.ToBool(ctx.Argument(5))
|
||||
if len(name) == 0 || len(path) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
var website models.Website
|
||||
if err := facades.Orm().Query().Where("name", name).FirstOrFail(&website); err == nil {
|
||||
color.Redln("网站已存在")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
color.Redln("网站目录不存在")
|
||||
return nil
|
||||
}
|
||||
|
||||
err = facades.Orm().Query().Create(&models.Website{
|
||||
Name: name,
|
||||
Status: status,
|
||||
Path: path,
|
||||
Php: php,
|
||||
Ssl: ssl,
|
||||
})
|
||||
if err != nil {
|
||||
color.Redln("写入网站失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入网站成功")
|
||||
break
|
||||
|
||||
case "deleteSite":
|
||||
name := arg1
|
||||
if len(name) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := facades.Orm().Query().Where("name", name).Delete(&models.Website{})
|
||||
if err != nil {
|
||||
color.Redln("删除网站失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("删除网站成功")
|
||||
break
|
||||
|
||||
case "writeSetting":
|
||||
key := arg1
|
||||
value := arg2
|
||||
if len(key) == 0 || len(value) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
var setting models.Setting
|
||||
err := facades.Orm().Query().UpdateOrCreate(&setting, models.Setting{
|
||||
Key: key,
|
||||
}, models.Setting{
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
color.Redln("写入设置失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("写入设置成功")
|
||||
break
|
||||
|
||||
case "deleteSetting":
|
||||
key := arg1
|
||||
if len(key) == 0 {
|
||||
color.Redln("参数错误")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := facades.Orm().Query().Where("key", key).Delete(&models.Setting{})
|
||||
if err != nil {
|
||||
color.Redln("删除设置失败")
|
||||
return nil
|
||||
}
|
||||
|
||||
color.Greenln("删除设置成功")
|
||||
break
|
||||
|
||||
default:
|
||||
color.Yellowln("耗子Linux面板命令行工具")
|
||||
color.Greenln("请使用以下命令:")
|
||||
color.Greenln("panel update 更新/修复面板到最新版本")
|
||||
color.Greenln("panel getInfo 重新初始化面板账号信息")
|
||||
color.Greenln("panel getPort 获取面板访问端口")
|
||||
color.Greenln("panel cleanRunningTask 强制清理面板正在运行的任务")
|
||||
color.Greenln("panel backup {website/mysql/postgresql} {name} {path} 备份网站/MySQL数据库/PostgreSQL数据库到指定目录")
|
||||
color.Redln("以下命令请在开发者指导下使用:")
|
||||
color.Yellowln("panel init 初始化面板")
|
||||
color.Yellowln("panel writePlugin {slug} 写入插件安装状态")
|
||||
color.Yellowln("panel deletePlugin {slug} 移除插件安装状态")
|
||||
color.Yellowln("panel writeMysqlPassword {password} 写入MySQL root密码")
|
||||
color.Yellowln("panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板")
|
||||
color.Yellowln("panel deleteSite {name} 删除面板网站数据")
|
||||
color.Yellowln("panel writeSetting {name} {value} 写入/更新面板设置数据")
|
||||
color.Yellowln("panel deleteSetting {name} 删除面板设置数据")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -16,6 +16,7 @@ func (kernel *Kernel) Schedule() []schedule.Event {
|
||||
|
||||
func (kernel *Kernel) Commands() []console.Command {
|
||||
return []console.Command{
|
||||
&commands.Panel{},
|
||||
&commands.Monitoring{},
|
||||
}
|
||||
}
|
||||
|
||||
18
app/http/controllers/helpers.go
Normal file
18
app/http/controllers/helpers.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package controllers
|
||||
|
||||
import "github.com/goravel/framework/contracts/http"
|
||||
|
||||
func Success(ctx http.Context, data http.Json) {
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
func Error(ctx http.Context, code int, message any) {
|
||||
ctx.Response().Json(code, http.Json{
|
||||
"code": code,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
@@ -22,17 +22,11 @@ func (r *UserController) Login(ctx http.Context) {
|
||||
var loginRequest requests.LoginRequest
|
||||
errors, err := ctx.Request().ValidateRequest(&loginRequest)
|
||||
if err != nil {
|
||||
ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
|
||||
"code": 422,
|
||||
"message": err.Error(),
|
||||
})
|
||||
Error(ctx, http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
if errors != nil {
|
||||
ctx.Response().Json(http.StatusUnprocessableEntity, http.Json{
|
||||
"code": 422,
|
||||
"message": errors.All(),
|
||||
})
|
||||
Error(ctx, http.StatusUnprocessableEntity, errors.All())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,31 +34,20 @@ func (r *UserController) Login(ctx http.Context) {
|
||||
err = facades.Orm().Query().Where("username", loginRequest.Username).First(&user)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][UserController] 查询用户失败 ", err)
|
||||
ctx.Response().Json(http.StatusInternalServerError, http.Json{
|
||||
"code": 500,
|
||||
"message": "系统内部错误",
|
||||
})
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if user.ID == 0 || !facades.Hash().Check(loginRequest.Password, user.Password) {
|
||||
ctx.Response().Json(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "用户名或密码错误",
|
||||
})
|
||||
Error(ctx, http.StatusUnauthorized, "用户名或密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查密码是否需要重新哈希
|
||||
if facades.Hash().NeedsRehash(user.Password) {
|
||||
// 更新密码
|
||||
user.Password, err = facades.Hash().Make(loginRequest.Password)
|
||||
if err != nil {
|
||||
facades.Log().Error("[面板][UserController] 更新密码失败 ", err)
|
||||
ctx.Response().Json(http.StatusInternalServerError, http.Json{
|
||||
"code": 500,
|
||||
"message": "系统内部错误",
|
||||
})
|
||||
Error(ctx, http.StatusInternalServerError, "系统内部错误")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -72,38 +55,24 @@ func (r *UserController) Login(ctx http.Context) {
|
||||
token, loginErr := facades.Auth().LoginUsingID(ctx, user.ID)
|
||||
if loginErr != nil {
|
||||
facades.Log().Error("[面板][UserController] 登录失败 ", loginErr)
|
||||
ctx.Response().Json(http.StatusInternalServerError, http.Json{
|
||||
"code": 500,
|
||||
"message": loginErr.Error(),
|
||||
})
|
||||
Error(ctx, http.StatusInternalServerError, loginErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"code": 0,
|
||||
"message": "登录成功",
|
||||
"data": http.Json{
|
||||
"access_token": token,
|
||||
},
|
||||
Success(ctx, http.Json{
|
||||
"access_token": token,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *UserController) Info(ctx http.Context) {
|
||||
user, ok := ctx.Value("user").(models.User)
|
||||
if !ok {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "登录已过期",
|
||||
})
|
||||
Error(ctx, http.StatusUnauthorized, "登录已过期")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"code": 0,
|
||||
"message": "获取用户信息成功",
|
||||
"data": http.Json{
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
},
|
||||
Success(ctx, http.Json{
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@ type Cron struct {
|
||||
Status bool `gorm:"not null;default:false"`
|
||||
Type string `gorm:"not null"`
|
||||
Time string `gorm:"not null"`
|
||||
Shell string `gorm:"default:null"`
|
||||
Log string `gorm:"default:null"`
|
||||
Shell string `gorm:"default:''"`
|
||||
Log string `gorm:"default:''"`
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@ type Database struct {
|
||||
Host string `gorm:"not null"`
|
||||
Port int `gorm:"not null"`
|
||||
Username string `gorm:"not null"`
|
||||
Password string `gorm:"default:null"`
|
||||
Remark string `gorm:"default:null"`
|
||||
Password string `gorm:"default:''"`
|
||||
Remark string `gorm:"default:''"`
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
type Setting struct {
|
||||
orm.Model
|
||||
Key string `gorm:"unique;not null"`
|
||||
Value string `gorm:"default:null"`
|
||||
Value string `gorm:"default:''"`
|
||||
}
|
||||
|
||||
@@ -4,10 +4,17 @@ import (
|
||||
"github.com/goravel/framework/database/orm"
|
||||
)
|
||||
|
||||
const (
|
||||
TaskStatusWaiting = "waiting"
|
||||
TaskStatusRunning = "running"
|
||||
TaskStatusSuccess = "finished"
|
||||
TaskStatusFailed = "failed"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
orm.Model
|
||||
Name string `gorm:"not null"`
|
||||
Status string `gorm:"not null;default:'waiting'"`
|
||||
Shell string `gorm:"default:null"`
|
||||
Log string `gorm:"default:null"`
|
||||
Shell string `gorm:"default:''"`
|
||||
Log string `gorm:"default:''"`
|
||||
}
|
||||
|
||||
@@ -8,6 +8,5 @@ type User struct {
|
||||
orm.Model
|
||||
Username string `gorm:"unique;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
Email string `gorm:"default:null"`
|
||||
orm.SoftDeletes
|
||||
Email string `gorm:"default:''"`
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ type Website struct {
|
||||
Path string `gorm:"not null"`
|
||||
Php int `gorm:"default:0;not null;index"`
|
||||
Ssl bool `gorm:"default:false;not null;index"`
|
||||
Remark string `gorm:"default:null"`
|
||||
Remark string `gorm:"default:''"`
|
||||
}
|
||||
|
||||
2
app/services/backup.go
Normal file
2
app/services/backup.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package services 备份服务
|
||||
package services
|
||||
39
app/services/user.go
Normal file
39
app/services/user.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/models"
|
||||
)
|
||||
|
||||
type User interface {
|
||||
Create(name, password string) (models.User, error)
|
||||
Update(user models.User) (models.User, error)
|
||||
}
|
||||
|
||||
type UserImpl struct {
|
||||
}
|
||||
|
||||
func NewUserImpl() *UserImpl {
|
||||
return &UserImpl{}
|
||||
}
|
||||
|
||||
func (r *UserImpl) Create(username, password string) (models.User, error) {
|
||||
user := models.User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
if err := facades.Orm().Query().Create(&user); err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *UserImpl) Update(user models.User) (models.User, error) {
|
||||
if _, err := facades.Orm().Query().Update(&user); err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
39
app/services/user_test.go
Normal file
39
app/services/user_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goravel/framework/testing/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"panel/app/models"
|
||||
)
|
||||
|
||||
type UserTestSuite struct {
|
||||
suite.Suite
|
||||
user User
|
||||
}
|
||||
|
||||
func TestUserTestSuite(t *testing.T) {
|
||||
suite.Run(t, &UserTestSuite{
|
||||
user: NewUserImpl(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserTestSuite) SetupTest() {
|
||||
|
||||
}
|
||||
|
||||
func (s *UserTestSuite) TestCreate() {
|
||||
mockOrm, mockDb, _, _ := mock.Orm()
|
||||
mockOrm.On("Query").Return(mockDb).Once()
|
||||
mockDb.On("Create", &models.User{
|
||||
Username: "haozi",
|
||||
Password: "123456",
|
||||
}).Return(nil).Once()
|
||||
user, err := s.user.Create("haozi", "123456")
|
||||
s.Nil(err)
|
||||
s.Equal("haozi", user.Username)
|
||||
mockOrm.AssertExpectations(s.T())
|
||||
mockDb.AssertExpectations(s.T())
|
||||
}
|
||||
2
app/services/website.go
Normal file
2
app/services/website.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package services 网站服务
|
||||
package services
|
||||
@@ -14,7 +14,7 @@ func init() {
|
||||
// Default driver is "bcrypt".
|
||||
//
|
||||
// Supported Drivers: "bcrypt", "argon2id"
|
||||
"driver": "bcrypt",
|
||||
"driver": "argon2id",
|
||||
|
||||
// Bcrypt Hashing Options
|
||||
// rounds: The cost factor that should be used to compute the bcrypt hash.
|
||||
|
||||
1
database/.gitignore
vendored
Normal file
1
database/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
panel.db
|
||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module panel
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/goravel/framework v1.12.2
|
||||
github.com/goravel/framework v1.12.3-0.20230622070736-f7260a71f319
|
||||
google.golang.org/grpc v1.56.0
|
||||
)
|
||||
|
||||
|
||||
2
go.sum
2
go.sum
@@ -348,6 +348,8 @@ github.com/goravel/file-rotatelogs/v2 v2.4.1 h1:ogkeIFcTHSBRUBpZYiyJbpul8hkVXxHP
|
||||
github.com/goravel/file-rotatelogs/v2 v2.4.1/go.mod h1:euk9qr52WrzM8ICs1hecFcR4CZ/ZZOPdacHfvHgbOf0=
|
||||
github.com/goravel/framework v1.12.2 h1:2d+RQEVzcky6ff6LxlcPvnAj0tZPc0Y4yQAXQk88+nA=
|
||||
github.com/goravel/framework v1.12.2/go.mod h1:96GRS8270PKLfJU9zrrLE7XKlp20S2TJ9RB337jBMy4=
|
||||
github.com/goravel/framework v1.12.3-0.20230622070736-f7260a71f319 h1:xs7YlSAXdSJs0olT59PBwwj+3Rn5nTKASgNc+GUqsV8=
|
||||
github.com/goravel/framework v1.12.3-0.20230622070736-f7260a71f319/go.mod h1:96GRS8270PKLfJU9zrrLE7XKlp20S2TJ9RB337jBMy4=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
|
||||
@@ -87,10 +87,10 @@ func MD5(str string) string {
|
||||
|
||||
// FormatBytes 格式化bytes
|
||||
func FormatBytes(size float64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB"}
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
|
||||
i := 0
|
||||
for ; size >= 1024 && i < 4; i++ {
|
||||
for ; size >= 1024 && i < len(units); i++ {
|
||||
size /= 1024
|
||||
}
|
||||
|
||||
|
||||
64
packages/helpers/helpers_test.go
Normal file
64
packages/helpers/helpers_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type HelperTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestHelperTestSuite(t *testing.T) {
|
||||
suite.Run(t, &HelperTestSuite{})
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestEmpty() {
|
||||
s.True(Empty(""))
|
||||
s.True(Empty(nil))
|
||||
s.True(Empty([]string{}))
|
||||
s.True(Empty(map[string]string{}))
|
||||
s.True(Empty(0))
|
||||
s.True(Empty(0.0))
|
||||
s.True(Empty(false))
|
||||
|
||||
s.False(Empty(" "))
|
||||
s.False(Empty([]string{"Panel"}))
|
||||
s.False(Empty(map[string]string{"Panel": "HaoZi"}))
|
||||
s.False(Empty(1))
|
||||
s.False(Empty(1.0))
|
||||
s.False(Empty(true))
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestFirstElement() {
|
||||
s.Equal("HaoZi", FirstElement([]string{"HaoZi"}))
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestRandomNumber() {
|
||||
s.Len(RandomNumber(10), 10)
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestRandomString() {
|
||||
s.Len(RandomString(10), 10)
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestMD5() {
|
||||
s.Equal("e10adc3949ba59abbe56e057f20f883e", MD5("123456"))
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestFormatBytes() {
|
||||
s.Equal("1.00 B", FormatBytes(1))
|
||||
s.Equal("1.00 KB", FormatBytes(1024))
|
||||
s.Equal("1.00 MB", FormatBytes(1024*1024))
|
||||
s.Equal("1.00 GB", FormatBytes(1024*1024*1024))
|
||||
s.Equal("1.00 TB", FormatBytes(1024*1024*1024*1024))
|
||||
s.Equal("1.00 PB", FormatBytes(1024*1024*1024*1024*1024))
|
||||
s.Equal("1.00 EB", FormatBytes(1024*1024*1024*1024*1024*1024))
|
||||
s.Equal("1.00 ZB", FormatBytes(1024*1024*1024*1024*1024*1024*1024))
|
||||
s.Equal("1.00 YB", FormatBytes(1024*1024*1024*1024*1024*1024*1024*1024))
|
||||
}
|
||||
|
||||
func (s *HelperTestSuite) TestCut() {
|
||||
s.Equal("aoZ", Cut("H", "i", "HaoZi"))
|
||||
}
|
||||
Reference in New Issue
Block a user