// Copyright 2019 GoAdmin Core Team. All rights reserved.
// Use of this source code is governed by a Apache-2.0 style
// license that can be found in the LICENSE file.
package config
import (
"encoding/json"
"fmt"
"html/template"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/GoAdminGroup/go-admin/modules/logger"
"github.com/GoAdminGroup/go-admin/modules/utils"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v2"
)
// Database is a type of database connection config.
//
// Because a little difference of different database driver.
// The Config has multiple options but may not be used.
// Such as the sqlite driver only use the File option which
// can be ignored when the driver is mysql.
//
// If the Dsn is configured, when driver is mysql/postgresql/
// mssql, the other configurations will be ignored, except for
// MaxIdleConns and MaxOpenConns.
type Database struct {
Host string `json:"host,omitempty" yaml:"host,omitempty" ini:"host,omitempty"`
Port string `json:"port,omitempty" yaml:"port,omitempty" ini:"port,omitempty"`
User string `json:"user,omitempty" yaml:"user,omitempty" ini:"user,omitempty"`
Pwd string `json:"pwd,omitempty" yaml:"pwd,omitempty" ini:"pwd,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty" ini:"name,omitempty"`
Driver string `json:"driver,omitempty" yaml:"driver,omitempty" ini:"driver,omitempty"`
DriverMode string `json:"driver_mode,omitempty" yaml:"driver_mode,omitempty" ini:"driver_mode,omitempty"`
File string `json:"file,omitempty" yaml:"file,omitempty" ini:"file,omitempty"`
Dsn string `json:"dsn,omitempty" yaml:"dsn,omitempty" ini:"dsn,omitempty"`
MaxIdleConns int `json:"max_idle_con,omitempty" yaml:"max_idle_con,omitempty" ini:"max_idle_con,omitempty"`
MaxOpenConns int `json:"max_open_con,omitempty" yaml:"max_open_con,omitempty" ini:"max_open_con,omitempty"`
ConnMaxLifetime time.Duration `json:"conn_max_life_time,omitempty" yaml:"conn_max_life_time,omitempty" ini:"conn_max_life_time,omitempty"`
ConnMaxIdleTime time.Duration `json:"conn_max_idle_time,omitempty" yaml:"conn_max_idle_time,omitempty" ini:"conn_max_idle_time,omitempty"`
Params map[string]string `json:"params,omitempty" yaml:"params,omitempty" ini:"params,omitempty"`
}
func (d Database) GetDSN() string {
if d.Dsn != "" {
return d.Dsn
}
if d.Driver == DriverMysql {
return d.User + ":" + d.Pwd + "@tcp(" + d.Host + ":" + d.Port + ")/" +
d.Name + d.ParamStr()
}
if d.Driver == DriverPostgresql {
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s"+d.ParamStr(),
d.Host, d.Port, d.User, d.Pwd, d.Name)
}
if d.Driver == DriverMssql {
return fmt.Sprintf("user id=%s;password=%s;server=%s;port=%s;database=%s;"+d.ParamStr(),
d.User, d.Pwd, d.Host, d.Port, d.Name)
}
if d.Driver == DriverSqlite {
return d.File + d.ParamStr()
}
if d.Driver == DriverOceanBase {
return d.User + ":" + d.Pwd + "@tcp(" + d.Host + ":" + d.Port + ")/" +
d.Name + d.ParamStr()
}
return ""
}
func (d Database) ParamStr() string {
p := ""
if d.Params == nil {
d.Params = make(map[string]string)
}
if d.Driver == DriverMysql || d.Driver == DriverSqlite || d.Driver == DriverOceanBase {
if d.Driver == DriverMysql || d.Driver == DriverOceanBase {
if _, ok := d.Params["charset"]; !ok {
d.Params["charset"] = "utf8mb4"
}
}
if len(d.Params) > 0 {
p = "?"
for k, v := range d.Params {
p += k + "=" + v + "&"
}
p = p[:len(p)-1]
}
}
if d.Driver == DriverMssql {
if _, ok := d.Params["encrypt"]; !ok {
d.Params["encrypt"] = "disable"
}
for k, v := range d.Params {
p += k + "=" + v + ";"
}
p = p[:len(p)-1]
}
if d.Driver == DriverPostgresql {
if _, ok := d.Params["sslmode"]; !ok {
d.Params["sslmode"] = "disable"
}
p = " "
for k, v := range d.Params {
p += k + "=" + v + " "
}
p = p[:len(p)-1]
}
return p
}
// DatabaseList is a map of Database.
type DatabaseList map[string]Database
// GetDefault get the default Database.
func (d DatabaseList) GetDefault() Database {
return d["default"]
}
// Add add a Database to the DatabaseList.
func (d DatabaseList) Add(key string, db Database) {
d[key] = db
}
// GroupByDriver group the Databases with the drivers.
func (d DatabaseList) GroupByDriver() map[string]DatabaseList {
drivers := make(map[string]DatabaseList)
for key, item := range d {
if driverList, ok := drivers[item.Driver]; ok {
driverList.Add(key, item)
} else {
drivers[item.Driver] = make(DatabaseList)
drivers[item.Driver].Add(key, item)
}
}
return drivers
}
func (d DatabaseList) JSON() string {
return utils.JSON(d)
}
func (d DatabaseList) Copy() DatabaseList {
var c = make(DatabaseList)
for k, v := range d {
c[k] = v
}
return c
}
func (d DatabaseList) Connections() []string {
conns := make([]string, len(d))
count := 0
for key := range d {
conns[count] = key
count++
}
return conns
}
func GetDatabaseListFromJSON(m string) DatabaseList {
var d = make(DatabaseList)
if m == "" {
panic("wrong config")
}
_ = json.Unmarshal([]byte(m), &d)
return d
}
const (
// EnvTest is a const value of test environment.
EnvTest = "test"
// EnvLocal is a const value of local environment.
EnvLocal = "local"
// EnvProd is a const value of production environment.
EnvProd = "prod"
// DriverMysql is a const value of mysql driver.
DriverMysql = "mysql"
// DriverSqlite is a const value of sqlite driver.
DriverSqlite = "sqlite"
// DriverPostgresql is a const value of postgresql driver.
DriverPostgresql = "postgresql"
// DriverMssql is a const value of mssql driver.
DriverMssql = "mssql"
// DriverOceanBase is a const value of mysql driver.
DriverOceanBase = "oceanbase"
)
// Store is the file store config. Path is the local store path.
// and prefix is the url prefix used to visit it.
type Store struct {
Path string `json:"path,omitempty" yaml:"path,omitempty" ini:"path,omitempty"`
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty" ini:"prefix,omitempty"`
}
func (s Store) URL(suffix string) string {
if len(suffix) > 4 && suffix[:4] == "http" {
return suffix
}
if s.Prefix == "" {
if suffix[0] == '/' {
return suffix
}
return "/" + suffix
}
if s.Prefix[0] == '/' {
if suffix[0] == '/' {
return s.Prefix + suffix
}
return s.Prefix + "/" + suffix
}
if suffix[0] == '/' {
if len(s.Prefix) > 4 && s.Prefix[:4] == "http" {
return s.Prefix + suffix
}
return "/" + s.Prefix + suffix
}
if len(s.Prefix) > 4 && s.Prefix[:4] == "http" {
return s.Prefix + "/" + suffix
}
return "/" + s.Prefix + "/" + suffix
}
func (s Store) JSON() string {
if s.Path == "" && s.Prefix == "" {
return ""
}
return utils.JSON(s)
}
func GetStoreFromJSON(m string) Store {
var s Store
if m == "" {
return s
}
_ = json.Unmarshal([]byte(m), &s)
return s
}
// Config type is the global config of goAdmin. It will be
// initialized in the engine.
type Config struct {
// An map supports multi database connection. The first
// element of Databases is the default connection. See the
// file connection.go.
Databases DatabaseList `json:"database,omitempty" yaml:"database,omitempty" ini:"database,omitempty"`
// The application unique ID. Once generated, don't modify.
AppID string `json:"app_id,omitempty" yaml:"app_id,omitempty" ini:"app_id,omitempty"`
// The cookie domain used in the auth modules. see
// the session.go.
Domain string `json:"domain,omitempty" yaml:"domain,omitempty" ini:"domain,omitempty"`
// Used to set as the localize language which show in the
// interface.
Language string `json:"language,omitempty" yaml:"language,omitempty" ini:"language,omitempty"`
// The global url prefix.
UrlPrefix string `json:"prefix,omitempty" yaml:"prefix,omitempty" ini:"prefix,omitempty"`
// The theme name of template.
Theme string `json:"theme,omitempty" yaml:"theme,omitempty" ini:"theme,omitempty"`
// The path where files will be stored into.
Store Store `json:"store,omitempty" yaml:"store,omitempty" ini:"store,omitempty"`
// The title of web page.
Title string `json:"title,omitempty" yaml:"title,omitempty" ini:"title,omitempty"`
// Logo is the top text in the sidebar.
Logo template.HTML `json:"logo,omitempty" yaml:"logo,omitempty" ini:"logo,omitempty"`
// Mini-logo is the top text in the sidebar when folding.
MiniLogo template.HTML `json:"mini_logo,omitempty" yaml:"mini_logo,omitempty" ini:"mini_logo,omitempty"`
// The url redirect to after login.
IndexUrl string `json:"index,omitempty" yaml:"index,omitempty" ini:"index,omitempty"`
// Login page URL
LoginUrl string `json:"login_url,omitempty" yaml:"login_url,omitempty" ini:"login_url,omitempty"`
// Debug mode
Debug bool `json:"debug,omitempty" yaml:"debug,omitempty" ini:"debug,omitempty"`
// Env is the environment,which maybe local,test,prod.
Env string `json:"env,omitempty" yaml:"env,omitempty" ini:"env,omitempty"`
// Info log path.
InfoLogPath string `json:"info_log,omitempty" yaml:"info_log,omitempty" ini:"info_log,omitempty"`
// Error log path.
ErrorLogPath string `json:"error_log,omitempty" yaml:"error_log,omitempty" ini:"error_log,omitempty"`
// Access log path.
AccessLogPath string `json:"access_log,omitempty" yaml:"access_log,omitempty" ini:"access_log,omitempty"`
// Access assets log off
AccessAssetsLogOff bool `json:"access_assets_log_off,omitempty" yaml:"access_assets_log_off,omitempty" ini:"access_assets_log_off,omitempty"`
// Sql operator record log switch.
SqlLog bool `json:"sql_log,omitempty" yaml:"sql_log,omitempty" ini:"sql_log,omitempty"`
AccessLogOff bool `json:"access_log_off,omitempty" yaml:"access_log_off,omitempty" ini:"access_log_off,omitempty"`
InfoLogOff bool `json:"info_log_off,omitempty" yaml:"info_log_off,omitempty" ini:"info_log_off,omitempty"`
ErrorLogOff bool `json:"error_log_off,omitempty" yaml:"error_log_off,omitempty" ini:"error_log_off,omitempty"`
Logger Logger `json:"logger,omitempty" yaml:"logger,omitempty" ini:"logger,omitempty"`
// Color scheme.
ColorScheme string `json:"color_scheme,omitempty" yaml:"color_scheme,omitempty" ini:"color_scheme,omitempty"`
// Session valid time duration,units are seconds. Default 7200.
SessionLifeTime int `json:"session_life_time,omitempty" yaml:"session_life_time,omitempty" ini:"session_life_time,omitempty"`
// Assets visit link.
AssetUrl string `json:"asset_url,omitempty" yaml:"asset_url,omitempty" ini:"asset_url,omitempty"`
// File upload engine,default "local"
FileUploadEngine FileUploadEngine `json:"file_upload_engine,omitempty" yaml:"file_upload_engine,omitempty" ini:"file_upload_engine,omitempty"`
// Custom html in the tag head.
CustomHeadHtml template.HTML `json:"custom_head_html,omitempty" yaml:"custom_head_html,omitempty" ini:"custom_head_html,omitempty"`
// Custom html after body.
CustomFootHtml template.HTML `json:"custom_foot_html,omitempty" yaml:"custom_foot_html,omitempty" ini:"custom_foot_html,omitempty"`
// Footer Info html
FooterInfo template.HTML `json:"footer_info,omitempty" yaml:"footer_info,omitempty" ini:"footer_info,omitempty"`
// Login page title
LoginTitle string `json:"login_title,omitempty" yaml:"login_title,omitempty" ini:"login_title,omitempty"`
// Login page logo
LoginLogo template.HTML `json:"login_logo,omitempty" yaml:"login_logo,omitempty" ini:"login_logo,omitempty"`
// Auth user table
AuthUserTable string `json:"auth_user_table,omitempty" yaml:"auth_user_table,omitempty" ini:"auth_user_table,omitempty"`
// Extra config info
Extra ExtraInfo `json:"extra,omitempty" yaml:"extra,omitempty" ini:"extra,omitempty"`
// Page animation
Animation PageAnimation `json:"animation,omitempty" yaml:"animation,omitempty" ini:"animation,omitempty"`
// Limit login with different IPs
NoLimitLoginIP bool `json:"no_limit_login_ip,omitempty" yaml:"no_limit_login_ip,omitempty" ini:"no_limit_login_ip,omitempty"`
// When site off is true, website will be closed
SiteOff bool `json:"site_off,omitempty" yaml:"site_off,omitempty" ini:"site_off,omitempty"`
// Hide config center entrance flag
HideConfigCenterEntrance bool `json:"hide_config_center_entrance,omitempty" yaml:"hide_config_center_entrance,omitempty" ini:"hide_config_center_entrance,omitempty"`
// Prohibit config modification
ProhibitConfigModification bool `json:"prohibit_config_modification,omitempty" yaml:"prohibit_config_modification,omitempty" ini:"prohibit_config_modification,omitempty"`
// Hide app info entrance flag
HideAppInfoEntrance bool `json:"hide_app_info_entrance,omitempty" yaml:"hide_app_info_entrance,omitempty" ini:"hide_app_info_entrance,omitempty"`
// Hide tool entrance flag
HideToolEntrance bool `json:"hide_tool_entrance,omitempty" yaml:"hide_tool_entrance,omitempty" ini:"hide_tool_entrance,omitempty"`
HidePluginEntrance bool `json:"hide_plugin_entrance,omitempty" yaml:"hide_plugin_entrance,omitempty" ini:"hide_plugin_entrance,omitempty"`
Custom404HTML template.HTML `json:"custom_404_html,omitempty" yaml:"custom_404_html,omitempty" ini:"custom_404_html,omitempty"`
Custom403HTML template.HTML `json:"custom_403_html,omitempty" yaml:"custom_403_html,omitempty" ini:"custom_403_html,omitempty"`
Custom500HTML template.HTML `json:"custom_500_html,omitempty" yaml:"custom_500_html,omitempty" ini:"custom_500_html,omitempty"`
// Update Process Function
UpdateProcessFn UpdateConfigProcessFn `json:"-" yaml:"-" ini:"-"`
// Favicon string `json:"favicon,omitempty" yaml:"favicon,omitempty" ini:"favicon,omitempty"`
// Is open admin plugin json api
OpenAdminApi bool `json:"open_admin_api,omitempty" yaml:"open_admin_api,omitempty" ini:"open_admin_api,omitempty"`
HideVisitorUserCenterEntrance bool `json:"hide_visitor_user_center_entrance,omitempty" yaml:"hide_visitor_user_center_entrance,omitempty" ini:"hide_visitor_user_center_entrance,omitempty"`
ExcludeThemeComponents []string `json:"exclude_theme_components,omitempty" yaml:"exclude_theme_components,omitempty" ini:"exclude_theme_components,omitempty"`
BootstrapFilePath string `json:"bootstrap_file_path,omitempty" yaml:"bootstrap_file_path,omitempty" ini:"bootstrap_file_path,omitempty"`
GoModFilePath string `json:"go_mod_file_path,omitempty" yaml:"go_mod_file_path,omitempty" ini:"go_mod_file_path,omitempty"`
AllowDelOperationLog bool `json:"allow_del_operation_log,omitempty" yaml:"allow_del_operation_log,omitempty" ini:"allow_del_operation_log,omitempty"`
OperationLogOff bool `json:"operation_log_off,omitempty" yaml:"operation_log_off,omitempty" ini:"operation_log_off,omitempty"`
AssetRootPath string `json:"asset_root_path,omitempty" yaml:"asset_root_path,omitempty" ini:"asset_root_path,omitempty"`
URLFormat URLFormat `json:"url_format,omitempty" yaml:"url_format,omitempty" ini:"url_format,omitempty"`
prefix string `json:"-" yaml:"-" ini:"-"`
lock sync.RWMutex `json:"-" yaml:"-" ini:"-"`
}
type Logger struct {
Encoder EncoderCfg `json:"encoder,omitempty" yaml:"encoder,omitempty" ini:"encoder,omitempty"`
Rotate RotateCfg `json:"rotate,omitempty" yaml:"rotate,omitempty" ini:"rotate,omitempty"`
Level int8 `json:"level,omitempty" yaml:"level,omitempty" ini:"level,omitempty"`
}
type EncoderCfg struct {
TimeKey string `json:"time_key,omitempty" yaml:"time_key,omitempty" ini:"time_key,omitempty"`
LevelKey string `json:"level_key,omitempty" yaml:"level_key,omitempty" ini:"level_key,omitempty"`
NameKey string `json:"name_key,omitempty" yaml:"name_key,omitempty" ini:"name_key,omitempty"`
CallerKey string `json:"caller_key,omitempty" yaml:"caller_key,omitempty" ini:"caller_key,omitempty"`
MessageKey string `json:"message_key,omitempty" yaml:"message_key,omitempty" ini:"message_key,omitempty"`
StacktraceKey string `json:"stacktrace_key,omitempty" yaml:"stacktrace_key,omitempty" ini:"stacktrace_key,omitempty"`
Level string `json:"level,omitempty" yaml:"level,omitempty" ini:"level,omitempty"`
Time string `json:"time,omitempty" yaml:"time,omitempty" ini:"time,omitempty"`
Duration string `json:"duration,omitempty" yaml:"duration,omitempty" ini:"duration,omitempty"`
Caller string `json:"caller,omitempty" yaml:"caller,omitempty" ini:"caller,omitempty"`
Encoding string `json:"encoding,omitempty" yaml:"encoding,omitempty" ini:"encoding,omitempty"`
}
type RotateCfg struct {
MaxSize int `json:"max_size,omitempty" yaml:"max_size,omitempty" ini:"max_size,omitempty"`
MaxBackups int `json:"max_backups,omitempty" yaml:"max_backups,omitempty" ini:"max_backups,omitempty"`
MaxAge int `json:"max_age,omitempty" yaml:"max_age,omitempty" ini:"max_age,omitempty"`
Compress bool `json:"compress,omitempty" yaml:"compress,omitempty" ini:"compress,omitempty"`
}
type URLFormat struct {
Info string `json:"info,omitempty" yaml:"info,omitempty" ini:"info,omitempty"`
Detail string `json:"detail,omitempty" yaml:"detail,omitempty" ini:"detail,omitempty"`
Create string `json:"create,omitempty" yaml:"create,omitempty" ini:"create,omitempty"`
Delete string `json:"delete,omitempty" yaml:"delete,omitempty" ini:"delete,omitempty"`
Export string `json:"export,omitempty" yaml:"export,omitempty" ini:"export,omitempty"`
Edit string `json:"edit,omitempty" yaml:"edit,omitempty" ini:"edit,omitempty"`
ShowEdit string `json:"show_edit,omitempty" yaml:"show_edit,omitempty" ini:"show_edit,omitempty"`
ShowCreate string `json:"show_create,omitempty" yaml:"show_create,omitempty" ini:"show_create,omitempty"`
Update string `json:"update,omitempty" yaml:"update,omitempty" ini:"update,omitempty"`
}
func (f URLFormat) SetDefault() URLFormat {
f.Detail = utils.SetDefault(f.Detail, "", "/info/:__prefix/detail")
f.ShowEdit = utils.SetDefault(f.ShowEdit, "", "/info/:__prefix/edit")
f.ShowCreate = utils.SetDefault(f.ShowCreate, "", "/info/:__prefix/new")
f.Edit = utils.SetDefault(f.Edit, "", "/edit/:__prefix")
f.Create = utils.SetDefault(f.Create, "", "/new/:__prefix")
f.Delete = utils.SetDefault(f.Delete, "", "/delete/:__prefix")
f.Export = utils.SetDefault(f.Export, "", "/export/:__prefix")
f.Info = utils.SetDefault(f.Info, "", "/info/:__prefix")
f.Update = utils.SetDefault(f.Update, "", "/update/:__prefix")
return f
}
type ExtraInfo map[string]interface{}
type UpdateConfigProcessFn func(values form.Values) (form.Values, error)
// see more: https://daneden.github.io/animate.css/
type PageAnimation struct {
Type string `json:"type,omitempty" yaml:"type,omitempty" ini:"type,omitempty"`
Duration float32 `json:"duration,omitempty" yaml:"duration,omitempty" ini:"duration,omitempty"`
Delay float32 `json:"delay,omitempty" yaml:"delay,omitempty" ini:"delay,omitempty"`
}
func (p PageAnimation) JSON() string {
if p.Type == "" {
return ""
}
return utils.JSON(p)
}
// FileUploadEngine is a file upload engine.
type FileUploadEngine struct {
Name string `json:"name,omitempty" yaml:"name,omitempty" ini:"name,omitempty"`
Config map[string]interface{} `json:"config,omitempty" yaml:"config,omitempty" ini:"config,omitempty"`
}
func (f FileUploadEngine) JSON() string {
if f.Name == "" {
return ""
}
if len(f.Config) == 0 {
f.Config = nil
}
return utils.JSON(f)
}
func GetFileUploadEngineFromJSON(m string) FileUploadEngine {
var f FileUploadEngine
if m == "" {
return f
}
_ = json.Unmarshal([]byte(m), &f)
return f
}
// GetIndexURL get the index url with prefix.
func (c *Config) GetIndexURL() string {
index := c.Index()
if index == "/" {
return c.Prefix()
}
return c.Prefix() + index
}
// Url get url with the given suffix.
func (c *Config) Url(suffix string) string {
if c.prefix == "/" {
return suffix
}
if suffix == "/" {
return c.prefix
}
return c.prefix + suffix
}
// IsTestEnvironment check the environment if it is test.
func (c *Config) IsTestEnvironment() bool {
return c.Env == EnvTest
}
// IsLocalEnvironment check the environment if it is local.
func (c *Config) IsLocalEnvironment() bool {
return c.Env == EnvLocal
}
// IsProductionEnvironment check the environment if it is production.
func (c *Config) IsProductionEnvironment() bool {
return c.Env == EnvProd
}
// IsNotProductionEnvironment check the environment if it is not production.
func (c *Config) IsNotProductionEnvironment() bool {
return c.Env != EnvProd
}
func (c *Config) IsAllowConfigModification() bool {
return !c.ProhibitConfigModification
}
// URLRemovePrefix remove prefix from the given url.
func (c *Config) URLRemovePrefix(url string) string {
if url == c.prefix {
return "/"
}
if c.prefix == "/" {
return url
}
return strings.Replace(url, c.prefix, "", 1)
}
// Index return the index url without prefix.
func (c *Config) Index() string {
if c.IndexUrl == "" {
return "/"
}
if c.IndexUrl[0] != '/' {
return "/" + c.IndexUrl
}
return c.IndexUrl
}
// Prefix return the prefix.
func (c *Config) Prefix() string {
return c.prefix
}
// AssertPrefix return the prefix of assert.
func (c *Config) AssertPrefix() string {
if c.prefix == "/" {
return ""
}
return c.prefix
}
func (c *Config) AddUpdateProcessFn(fn UpdateConfigProcessFn) *Config {
c.UpdateProcessFn = fn
return c
}
// PrefixFixSlash return the prefix fix the slash error.
func (c *Config) PrefixFixSlash() string {
if c.UrlPrefix == "/" {
return ""
}
if c.UrlPrefix != "" && c.UrlPrefix[0] != '/' {
return "/" + c.UrlPrefix
}
return c.UrlPrefix
}
func (c *Config) Copy() *Config {
c.lock.RLock()
defer c.lock.RUnlock()
var (
newCfg = new(Config)
srcType = reflect.TypeOf(c).Elem()
srcVal = reflect.ValueOf(c).Elem()
distType = reflect.TypeOf(newCfg).Elem()
distVal = reflect.ValueOf(newCfg).Elem()
)
for i := 0; i < distType.NumField(); i++ {
v := distVal.Field(i)
if distType.Field(i).Type.String() == "config.DatabaseList" {
newCfg.Databases = c.Databases.Copy()
} else if v.CanInterface() {
for j := 0; j < srcType.NumField(); j++ {
if distType.Field(i).Name == srcType.Field(j).Name {
v.Set(reflect.ValueOf(srcVal.Field(i).Interface()))
break
}
}
}
}
newCfg.prefix = c.prefix
return newCfg
}
func (c *Config) ToMap() map[string]string {
c.lock.RLock()
defer c.lock.RUnlock()
var (
m = make(map[string]string)
rType = reflect.TypeOf(c).Elem()
rVal = reflect.ValueOf(c).Elem()
)
for i := 0; i < rType.NumField(); i++ {
v := rVal.Field(i)
if !v.CanInterface() {
continue
}
t := rType.Field(i)
keyName := t.Tag.Get("json")
if keyName == "-" {
continue
}
keyName = keyName[:len(keyName)-10]
switch t.Type.Kind() {
case reflect.Bool:
m[keyName] = strconv.FormatBool(v.Bool())
case reflect.String:
if keyName == "prefix" {
keyName = "url_prefix"
} else if keyName == "index" {
keyName = "index_url"
} else if keyName == "info_log" || keyName == "error_log" || keyName == "access_log" {
keyName += "_path"
}
m[keyName] = v.String()
case reflect.Int:
m[keyName] = fmt.Sprintf("%d", v.Int())
case reflect.Struct:
switch t.Type.String() {
case "config.PageAnimation":
m["animation_type"] = c.Animation.Type
m["animation_duration"] = fmt.Sprintf("%.2f", c.Animation.Duration)
m["animation_delay"] = fmt.Sprintf("%.2f", c.Animation.Delay)
case "config.Logger":
m["logger_rotate_max_size"] = strconv.Itoa(c.Logger.Rotate.MaxSize)
m["logger_rotate_max_backups"] = strconv.Itoa(c.Logger.Rotate.MaxBackups)
m["logger_rotate_max_age"] = strconv.Itoa(c.Logger.Rotate.MaxAge)
m["logger_rotate_compress"] = strconv.FormatBool(c.Logger.Rotate.Compress)
m["logger_encoder_time_key"] = c.Logger.Encoder.TimeKey
m["logger_encoder_level_key"] = c.Logger.Encoder.LevelKey
m["logger_encoder_name_key"] = c.Logger.Encoder.NameKey
m["logger_encoder_caller_key"] = c.Logger.Encoder.CallerKey
m["logger_encoder_message_key"] = c.Logger.Encoder.MessageKey
m["logger_encoder_stacktrace_key"] = c.Logger.Encoder.StacktraceKey
m["logger_encoder_level"] = c.Logger.Encoder.Level
m["logger_encoder_time"] = c.Logger.Encoder.Time
m["logger_encoder_duration"] = c.Logger.Encoder.Duration
m["logger_encoder_caller"] = c.Logger.Encoder.Caller
m["logger_encoder_encoding"] = c.Logger.Encoder.Encoding
m["logger_level"] = strconv.Itoa(int(c.Logger.Level))
case "config.DatabaseList":
m["databases"] = utils.JSON(v.Interface())
case "config.FileUploadEngine":
m["file_upload_engine"] = c.FileUploadEngine.JSON()
}
case reflect.Map:
if t.Type.String() == "config.ExtraInfo" {
if len(c.Extra) == 0 {
m["extra"] = ""
} else {
m["extra"] = utils.JSON(c.Extra)
}
}
default:
m[keyName] = utils.JSON(v.Interface())
}
}
return m
}
func (c *Config) Update(m map[string]string) error {
c.lock.Lock()
defer c.lock.Unlock()
rType := reflect.TypeOf(c).Elem()
rVal := reflect.ValueOf(c).Elem()
for i := 0; i < rType.NumField(); i++ {
v := rVal.Field(i)
if !v.CanInterface() {
continue
}
t := rType.Field(i)
keyName := t.Tag.Get("json")
if keyName == "-" {
continue
}
keyName = keyName[:len(keyName)-10]
switch t.Type.Kind() {
case reflect.Bool:
if mv, ok := m[keyName]; ok {
v.Set(reflect.ValueOf(utils.ParseBool(mv)))
}
case reflect.String:
if t.Type.String() == "template.HTML" {
if mv, ok := m[keyName]; ok {
v.Set(reflect.ValueOf(template.HTML(mv)))
}
continue
}
if keyName == "prefix" {
keyName = "url_prefix"
} else if keyName == "index" {
keyName = "index_url"
} else if keyName == "info_log" || keyName == "error_log" || keyName == "access_log" {
keyName += "_path"
}
if mv, ok := m[keyName]; ok {
if keyName == "info_log" || keyName == "error_log" || keyName == "access_log" {
v.Set(reflect.ValueOf(utils.SetDefault(mv, v.String(), v.String())))
} else if keyName == "app_id" {
v.Set(reflect.ValueOf(utils.SetDefault(mv, "", v.String())))
} else if keyName == "color_scheme" {
if m["theme"] == "adminlte" {
v.Set(reflect.ValueOf(mv))
}
} else {
v.Set(reflect.ValueOf(mv))
}
}
case reflect.Int:
ses, _ := strconv.Atoi(m[keyName])
if ses != 0 {
v.Set(reflect.ValueOf(ses))
}
case reflect.Struct:
switch t.Type.String() {
case "config.PageAnimation":
c.Animation.Type = m["animation_type"]
c.Animation.Duration = utils.ParseFloat32(m["animation_duration"])
c.Animation.Delay = utils.ParseFloat32(m["animation_delay"])
case "config.Logger":
c.Logger.Rotate.MaxSize, _ = strconv.Atoi(m["logger_rotate_max_size"])
c.Logger.Rotate.MaxBackups, _ = strconv.Atoi(m["logger_rotate_max_backups"])
c.Logger.Rotate.MaxAge, _ = strconv.Atoi(m["logger_rotate_max_age"])
c.Logger.Rotate.Compress = utils.ParseBool(m["logger_rotate_compress"])
c.Logger.Encoder.Encoding = m["logger_encoder_encoding"]
loggerLevel, _ := strconv.Atoi(m["logger_level"])
c.Logger.Level = int8(loggerLevel)
if c.Logger.Encoder.Encoding == "json" {
c.Logger.Encoder.TimeKey = m["logger_encoder_time_key"]
c.Logger.Encoder.LevelKey = m["logger_encoder_level_key"]
c.Logger.Encoder.NameKey = m["logger_encoder_name_key"]
c.Logger.Encoder.CallerKey = m["logger_encoder_caller_key"]
c.Logger.Encoder.MessageKey = m["logger_encoder_message_key"]
c.Logger.Encoder.StacktraceKey = m["logger_encoder_stacktrace_key"]
c.Logger.Encoder.Level = m["logger_encoder_level"]
c.Logger.Encoder.Time = m["logger_encoder_time"]
c.Logger.Encoder.Duration = m["logger_encoder_duration"]
c.Logger.Encoder.Caller = m["logger_encoder_caller"]
}
initLogger(c)
case "config.FileUploadEngine":
c.FileUploadEngine = GetFileUploadEngineFromJSON(m["file_upload_engine"])
}
case reflect.Map:
if t.Type.String() == "config.ExtraInfo" && m["extra"] != "" {
var extra = make(map[string]interface{})
_ = json.Unmarshal([]byte(m["extra"]), &extra)
c.Extra = extra
}
}
}
return nil
}
// eraseSens erase sensitive info.
func (c *Config) EraseSens() *Config {
for key := range c.Databases {
c.Databases[key] = Database{
Driver: c.Databases[key].Driver,
}
}
return c
}
var (
_global = new(Config)
count uint32
initializeLock sync.Mutex
)
// ReadFromJson read the Config from a JSON file.
func ReadFromJson(path string) Config {
jsonByte, err := os.ReadFile(path)
if err != nil {
panic(err)
}
var cfg Config
err = json.Unmarshal(jsonByte, &cfg)
if err != nil {
panic(err)
}
return cfg
}
// ReadFromYaml read the Config from a YAML file.
func ReadFromYaml(path string) Config {
jsonByte, err := os.ReadFile(path)
if err != nil {
panic(err)
}
var cfg Config
err = yaml.Unmarshal(jsonByte, &cfg)
if err != nil {
panic(err)
}
return cfg
}
// ReadFromINI read the Config from a INI file.
func ReadFromINI(path string) Config {
iniCfg, err := ini.Load(path)
if err != nil {
panic(err)
}
var cfg = Config{
Databases: make(DatabaseList),
}
err = iniCfg.MapTo(&cfg)
if err != nil {
panic(err)
}
for _, child := range iniCfg.ChildSections("database") {
var d Database
err = child.MapTo(&d)
if err != nil {
panic(err)
}
cfg.Databases[child.Name()[9:]] = d
}
return cfg
}
func SetDefault(cfg *Config) *Config {
cfg.Title = utils.SetDefault(cfg.Title, "", "GoAdmin")
cfg.LoginTitle = utils.SetDefault(cfg.LoginTitle, "", "GoAdmin")
cfg.Logo = template.HTML(utils.SetDefault(string(cfg.Logo), "", "GoAdmin"))
cfg.MiniLogo = template.HTML(utils.SetDefault(string(cfg.MiniLogo), "", "GA"))
cfg.Theme = utils.SetDefault(cfg.Theme, "", "adminlte")
cfg.IndexUrl = utils.SetDefault(cfg.IndexUrl, "", "/info/manager")
cfg.LoginUrl = utils.SetDefault(cfg.LoginUrl, "", "/login")
cfg.AuthUserTable = utils.SetDefault(cfg.AuthUserTable, "", "goadmin_users")
cfg.ColorScheme = utils.SetDefault(cfg.ColorScheme, "", "skin-black")
cfg.AssetRootPath = utils.SetDefault(cfg.AssetRootPath, "", "./public/")
cfg.AssetRootPath = filepath.ToSlash(cfg.AssetRootPath)
cfg.FileUploadEngine.Name = utils.SetDefault(cfg.FileUploadEngine.Name, "", "local")
cfg.Env = utils.SetDefault(cfg.Env, "", EnvProd)
if cfg.SessionLifeTime == 0 {
// default two hours
cfg.SessionLifeTime = 7200
}
cfg.AppID = utils.Uuid(12)
if cfg.UrlPrefix == "" {
cfg.prefix = "/"
} else if cfg.UrlPrefix[0] != '/' {
cfg.prefix = "/" + cfg.UrlPrefix
} else {
cfg.prefix = cfg.UrlPrefix
}
cfg.URLFormat = cfg.URLFormat.SetDefault()
return cfg
}
// Initialize initialize the config.
func Initialize(cfg *Config) *Config {
initializeLock.Lock()
defer initializeLock.Unlock()
if atomic.LoadUint32(&count) != 0 {
panic("can not initialize config twice")
}
atomic.StoreUint32(&count, 1)
initLogger(SetDefault(cfg))
_global = cfg
return _global
}
func initLogger(cfg *Config) {
logger.InitWithConfig(logger.Config{
InfoLogOff: cfg.InfoLogOff,
ErrorLogOff: cfg.ErrorLogOff,
AccessLogOff: cfg.AccessLogOff,
SqlLogOpen: cfg.SqlLog,
InfoLogPath: cfg.InfoLogPath,
ErrorLogPath: cfg.ErrorLogPath,
AccessLogPath: cfg.AccessLogPath,
AccessAssetsLogOff: cfg.AccessAssetsLogOff,
Rotate: logger.RotateCfg{
MaxSize: cfg.Logger.Rotate.MaxSize,
MaxBackups: cfg.Logger.Rotate.MaxBackups,
MaxAge: cfg.Logger.Rotate.MaxAge,
Compress: cfg.Logger.Rotate.Compress,
},
Encode: logger.EncoderCfg{
TimeKey: cfg.Logger.Encoder.TimeKey,
LevelKey: cfg.Logger.Encoder.LevelKey,
NameKey: cfg.Logger.Encoder.NameKey,
CallerKey: cfg.Logger.Encoder.CallerKey,
MessageKey: cfg.Logger.Encoder.MessageKey,
StacktraceKey: cfg.Logger.Encoder.StacktraceKey,
Level: cfg.Logger.Encoder.Level,
Time: cfg.Logger.Encoder.Time,
Duration: cfg.Logger.Encoder.Duration,
Caller: cfg.Logger.Encoder.Caller,
Encoding: cfg.Logger.Encoder.Encoding,
},
Debug: cfg.Debug,
Level: cfg.Logger.Level,
})
}
// AssertPrefix return the prefix of assert.
func AssertPrefix() string {
return _global.AssertPrefix()
}
// GetIndexURL get the index url with prefix.
func GetIndexURL() string {
return _global.GetIndexURL()
}
// IsProductionEnvironment check the environment if it is production.
func IsProductionEnvironment() bool {
return _global.IsProductionEnvironment()
}
// IsNotProductionEnvironment check the environment if it is not production.
func IsNotProductionEnvironment() bool {
return _global.IsNotProductionEnvironment()
}
// URLRemovePrefix remove prefix from the given url.
func URLRemovePrefix(url string) string {
return _global.URLRemovePrefix(url)
}
func Url(suffix string) string {
return _global.Url(suffix)
}
func GetURLFormats() URLFormat {
return _global.URLFormat
}
// Prefix return the prefix.
func Prefix() string {
return _global.prefix
}
// PrefixFixSlash return the prefix fix the slash error.
func PrefixFixSlash() string {
return _global.PrefixFixSlash()
}
// Get gets the config.
func Get() *Config {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Copy().EraseSens()
}
// Getter methods
// ============================
func GetDatabases() DatabaseList {
var list = make(DatabaseList, len(_global.Databases))
for key := range _global.Databases {
list[key] = Database{
Driver: _global.Databases[key].Driver,
DriverMode: _global.Databases[key].DriverMode,
}
}
return list
}
func GetDomain() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Domain
}
func GetLanguage() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Language
}
func GetAppID() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AppID
}
func GetUrlPrefix() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.UrlPrefix
}
func GetOpenAdminApi() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.OpenAdminApi
}
func GetAllowDelOperationLog() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AllowDelOperationLog
}
func GetOperationLogOff() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.OperationLogOff
}
func GetCustom500HTML() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Custom500HTML
}
func GetCustom404HTML() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Custom404HTML
}
func GetCustom403HTML() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Custom403HTML
}
func GetTheme() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Theme
}
func GetStore() Store {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Store
}
func GetTitle() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Title
}
func GetAssetRootPath() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AssetRootPath
}
func GetLogo() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Logo
}
func GetSiteOff() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.SiteOff
}
func GetMiniLogo() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.MiniLogo
}
func GetIndexUrl() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.IndexUrl
}
func GetLoginUrl() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.LoginUrl
}
func GetDebug() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Debug
}
func GetEnv() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Env
}
func GetInfoLogPath() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.InfoLogPath
}
func GetErrorLogPath() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.ErrorLogPath
}
func GetAccessLogPath() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AccessLogPath
}
func GetSqlLog() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.SqlLog
}
func GetAccessLogOff() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AccessLogOff
}
func GetInfoLogOff() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.InfoLogOff
}
func GetErrorLogOff() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.ErrorLogOff
}
func GetColorScheme() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.ColorScheme
}
func GetSessionLifeTime() int {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.SessionLifeTime
}
func GetAssetUrl() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AssetUrl
}
func GetFileUploadEngine() FileUploadEngine {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.FileUploadEngine
}
func GetCustomHeadHtml() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.CustomHeadHtml
}
func GetCustomFootHtml() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.CustomFootHtml
}
func GetFooterInfo() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.FooterInfo
}
func GetLoginTitle() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.LoginTitle
}
func GetLoginLogo() template.HTML {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.LoginLogo
}
func GetAuthUserTable() string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.AuthUserTable
}
func GetExtra() map[string]interface{} {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Extra
}
func GetAnimation() PageAnimation {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.Animation
}
func GetNoLimitLoginIP() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.NoLimitLoginIP
}
func GetHideVisitorUserCenterEntrance() bool {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.HideVisitorUserCenterEntrance
}
func GetExcludeThemeComponents() []string {
_global.lock.RLock()
defer _global.lock.RUnlock()
return _global.ExcludeThemeComponents
}
type Service struct {
C *Config
}
func (s *Service) Name() string {
return "config"
}
func SrvWithConfig(c *Config) *Service {
return &Service{c}
}
func GetService(s interface{}) *Config {
if srv, ok := s.(*Service); ok {
return srv.C
}
panic("wrong service")
}