|
|
package model
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"moredoc/util/captcha"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
|
|
"go.uber.org/zap"
|
|
|
"gorm.io/gorm"
|
|
|
)
|
|
|
|
|
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
|
|
|
|
const (
|
|
|
// ConfigCategorySystem 系统配置:系统名称、logo、版权信息、是否闭站等
|
|
|
ConfigCategorySystem = "system"
|
|
|
// ConfigCategoryUser 用户配置:是否启用注册、是否需要审核等
|
|
|
ConfigCategoryUser = "user"
|
|
|
// ConfigCategoryEmail 邮箱配置:smtp服务器、端口、用户名、密码、发件人
|
|
|
ConfigCategoryEmail = "email"
|
|
|
// ConfigCategoryCaptcha 验证码配置:是否启用验证码、验证码有效期、验证码长度、验证码类型等
|
|
|
ConfigCategoryCaptcha = "captcha"
|
|
|
// ConfigCategoryJWT JWT配置:JWT有效期、JWT加密密钥等
|
|
|
ConfigCategoryJWT = "jwt"
|
|
|
// ConfigCategorySecurity 安全配置项
|
|
|
ConfigCategorySecurity = "security"
|
|
|
)
|
|
|
|
|
|
type Config struct {
|
|
|
Id int64 `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
|
|
|
Label string `form:"label" json:"label,omitempty" gorm:"column:label;type:varchar(64);size:64;comment:标签名称;"`
|
|
|
Name string `form:"name" json:"name,omitempty" gorm:"column:name;type:varchar(64);size:64;index:name_category,unique;comment:表单字段名称;"`
|
|
|
Value string `form:"value" json:"value,omitempty" gorm:"column:value;type:text;comment:值;"`
|
|
|
Placeholder string `form:"placeholder" json:"placeholder,omitempty" gorm:"column:placeholder;type:varchar(255);size:255;comment:提示信息;"`
|
|
|
InputType string `form:"input_type" json:"input_type,omitempty" gorm:"column:input_type;type:varchar(32);size:32;default:text;comment:输入类型;"`
|
|
|
Category string `form:"category" json:"category,omitempty" gorm:"column:category;type:varchar(32);size:32;index:name_category,unique;index:category;comment:所属类别;"`
|
|
|
Sort int `form:"sort" json:"sort,omitempty" gorm:"column:sort;type:int(11);size:11;default:0;comment:同一category下的排序;"`
|
|
|
Options string `form:"options" json:"options,omitempty" gorm:"column:options;type:text;comment:针对checkbox等的枚举值;"`
|
|
|
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
|
|
|
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
|
|
|
}
|
|
|
|
|
|
// 这里是proto文件中的结构体,可以根据需要删除或者调整
|
|
|
//message Config {
|
|
|
// int64 id = 1;
|
|
|
// string label = 2;
|
|
|
// string name = 3;
|
|
|
// string value = 4;
|
|
|
// int32 placeholder = 5;
|
|
|
// int32 input_type = 6;
|
|
|
// string category = 7;
|
|
|
// int32 sort = 8;
|
|
|
// string options = 9;
|
|
|
// google.protobuf.Timestamp created_at = 10 [ (gogoproto.stdtime) = true ];
|
|
|
// google.protobuf.Timestamp updated_at = 11 [ (gogoproto.stdtime) = true ];
|
|
|
//}
|
|
|
|
|
|
func (Config) TableName() string {
|
|
|
return tablePrefix + "config"
|
|
|
}
|
|
|
|
|
|
// CreateConfig 创建Config
|
|
|
// TODO: 创建成功之后,注意相关表统计字段数值的增减
|
|
|
func (m *DBModel) CreateConfig(config *Config) (err error) {
|
|
|
err = m.db.Create(config).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("CreateConfig", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// UpdateConfig 更新Config,如果需要更新指定字段,则请指定updateFields参数
|
|
|
func (m *DBModel) UpdateConfig(config *Config, updateFields ...string) (err error) {
|
|
|
db := m.db.Model(config)
|
|
|
|
|
|
updateFields = m.FilterValidFields(Config{}.TableName(), updateFields...)
|
|
|
if len(updateFields) > 0 { // 更新指定字段
|
|
|
db = db.Select(updateFields)
|
|
|
}
|
|
|
|
|
|
err = db.Where("id = ?", config.Id).Updates(config).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("UpdateConfig", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// GetConfig 根据id获取Config
|
|
|
func (m *DBModel) GetConfig(id interface{}, fields ...string) (config Config, err error) {
|
|
|
db := m.db
|
|
|
|
|
|
fields = m.FilterValidFields(Config{}.TableName(), fields...)
|
|
|
if len(fields) > 0 {
|
|
|
db = db.Select(fields)
|
|
|
}
|
|
|
|
|
|
err = db.Where("id = ?", id).First(&config).Error
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// GetConfigByNameCategory(name string, category string, fields ...string) 根据唯一索引获取Config
|
|
|
func (m *DBModel) GetConfigByNameCategory(name string, category string, fields ...string) (config Config, err error) {
|
|
|
db := m.db
|
|
|
|
|
|
fields = m.FilterValidFields(Config{}.TableName(), fields...)
|
|
|
if len(fields) > 0 {
|
|
|
db = db.Select(fields)
|
|
|
}
|
|
|
|
|
|
db = db.Where("name = ?", name)
|
|
|
|
|
|
db = db.Where("category = ?", category)
|
|
|
|
|
|
err = db.First(&config).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetConfigByNameCategory", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
type OptionGetConfigList struct {
|
|
|
Page int
|
|
|
Size int
|
|
|
WithCount bool // 是否返回总数
|
|
|
Ids []interface{} // id列表
|
|
|
SelectFields []string // 查询字段
|
|
|
QueryRange map[string][2]interface{} // map[field][]{min,max}
|
|
|
QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
|
|
|
QueryLike map[string][]interface{} // map[field][]{value1,value2,...}
|
|
|
Sort []string
|
|
|
}
|
|
|
|
|
|
// GetConfigList 获取Config列表
|
|
|
func (m *DBModel) GetConfigList(opt OptionGetConfigList) (configList []Config, total int64, err error) {
|
|
|
db := m.db.Model(&Config{})
|
|
|
|
|
|
for field, rangeValue := range opt.QueryRange {
|
|
|
fields := m.FilterValidFields(Config{}.TableName(), field)
|
|
|
if len(fields) == 0 {
|
|
|
continue
|
|
|
}
|
|
|
if rangeValue[0] != nil {
|
|
|
db = db.Where(fmt.Sprintf("%s >= ?", field), rangeValue[0])
|
|
|
}
|
|
|
if rangeValue[1] != nil {
|
|
|
db = db.Where(fmt.Sprintf("%s <= ?", field), rangeValue[1])
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for field, values := range opt.QueryIn {
|
|
|
fields := m.FilterValidFields(Config{}.TableName(), field)
|
|
|
if len(fields) == 0 {
|
|
|
continue
|
|
|
}
|
|
|
db = db.Where(fmt.Sprintf("%s in (?)", field), values)
|
|
|
}
|
|
|
|
|
|
for field, values := range opt.QueryLike {
|
|
|
fields := m.FilterValidFields(Config{}.TableName(), field)
|
|
|
if len(fields) == 0 {
|
|
|
continue
|
|
|
}
|
|
|
db = db.Where(strings.TrimSuffix(fmt.Sprintf(strings.Join(make([]string, len(values)+1), "%s like ? or"), field), "or"), values...)
|
|
|
}
|
|
|
|
|
|
if len(opt.Ids) > 0 {
|
|
|
db = db.Where("id in (?)", opt.Ids)
|
|
|
}
|
|
|
|
|
|
if opt.WithCount {
|
|
|
err = db.Count(&total).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("GetConfigList", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
|
|
|
opt.SelectFields = m.FilterValidFields(Config{}.TableName(), opt.SelectFields...)
|
|
|
if len(opt.SelectFields) > 0 {
|
|
|
db = db.Select(opt.SelectFields)
|
|
|
}
|
|
|
|
|
|
if len(opt.Sort) > 0 {
|
|
|
var sorts []string
|
|
|
for _, sort := range opt.Sort {
|
|
|
slice := strings.Split(sort, " ")
|
|
|
if len(m.FilterValidFields(Config{}.TableName(), slice[0])) == 0 {
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
if len(slice) == 2 {
|
|
|
sorts = append(sorts, fmt.Sprintf("%s %s", slice[0], slice[1]))
|
|
|
} else {
|
|
|
sorts = append(sorts, fmt.Sprintf("%s desc", slice[0]))
|
|
|
}
|
|
|
}
|
|
|
if len(sorts) > 0 {
|
|
|
db = db.Order(strings.Join(sorts, ","))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
|
|
|
|
|
|
err = db.Find(&configList).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetConfigList", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// DeleteConfig 删除数据
|
|
|
// TODO: 删除数据之后,存在 config_id 的关联表,需要删除对应数据,同时相关表的统计数值,也要随着减少
|
|
|
func (m *DBModel) DeleteConfig(ids []interface{}) (err error) {
|
|
|
err = m.db.Where("id in (?)", ids).Delete(&Config{}).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("DeleteConfig", zap.Error(err))
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const (
|
|
|
ConfigCaptchaLength = "length"
|
|
|
ConfigCaptchaWidth = "width"
|
|
|
ConfigCaptchaHeight = "height"
|
|
|
ConfigCaptchaType = "type"
|
|
|
)
|
|
|
|
|
|
type ConfigCaptcha struct {
|
|
|
Length int `json:"length"` // 验证码长度
|
|
|
Width int `json:"width"`
|
|
|
Height int `json:"height"`
|
|
|
Type string `json:"type"` // 验证码类型
|
|
|
}
|
|
|
|
|
|
// GetConfigOfCaptcha 获取验证码配置
|
|
|
func (m *DBModel) GetConfigOfCaptcha() (config ConfigCaptcha) {
|
|
|
var configs []Config
|
|
|
err := m.db.Where("category = ?", ConfigCategoryCaptcha).Find(&configs).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetConfigOfCaptcha", zap.Error(err))
|
|
|
}
|
|
|
|
|
|
for _, cfg := range configs {
|
|
|
switch cfg.Name {
|
|
|
case "length":
|
|
|
config.Length, _ = strconv.Atoi(cfg.Value)
|
|
|
if config.Length <= 0 {
|
|
|
config.Length = 6
|
|
|
}
|
|
|
case "width":
|
|
|
config.Width, _ = strconv.Atoi(cfg.Value)
|
|
|
if config.Width <= 0 {
|
|
|
config.Width = 240
|
|
|
}
|
|
|
case "height":
|
|
|
config.Height, _ = strconv.Atoi(cfg.Value)
|
|
|
if config.Height <= 0 {
|
|
|
config.Height = 60
|
|
|
}
|
|
|
case "type":
|
|
|
// 验证码类型
|
|
|
config.Type = cfg.Value
|
|
|
}
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const (
|
|
|
ConfigSystemDomain = "domain"
|
|
|
ConfigSystemTitle = "title"
|
|
|
ConfigSystemDescription = "description"
|
|
|
ConfigSystemKeywords = "keywords"
|
|
|
ConfigSystemLogo = "logo"
|
|
|
ConfigSystemFavicon = "favicon"
|
|
|
ConfigSystemIcp = "icp"
|
|
|
ConfigSystemAnalytics = "analytics"
|
|
|
ConfigSystemTheme = "theme"
|
|
|
ConfigSystemCopyright = "copyright"
|
|
|
)
|
|
|
|
|
|
type ConfigSystem struct {
|
|
|
Domain string `json:"domain"` // 站点域名,不带 HTTPS:// 和 HTTP://
|
|
|
Title string `json:"title"` // 系统名称
|
|
|
Keywords string `json:"keywords"` // 系统关键字
|
|
|
Description string `json:"description"` // 系统描述
|
|
|
Logo string `json:"logo"` // logo
|
|
|
Favicon string `json:"favicon"` // logo
|
|
|
Theme string `json:"theme"` // 网站主题
|
|
|
Copyright string `json:"copyright"` // 版权信息
|
|
|
ICP string `json:"icp"` // 网站备案
|
|
|
Analytics string `json:"analytics"` // 统计代码
|
|
|
}
|
|
|
|
|
|
// GetConfigOfSystem 获取系统配置
|
|
|
func (m *DBModel) GetConfigOfSystem() (config ConfigSystem) {
|
|
|
var confgis []Config
|
|
|
err := m.db.Where("category = ?", ConfigCategorySystem).Find(&confgis).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetConfigOfSystem", zap.Error(err))
|
|
|
}
|
|
|
|
|
|
var data = make(map[string]interface{})
|
|
|
|
|
|
for _, cfg := range confgis {
|
|
|
switch cfg.Name {
|
|
|
// 字符串类型的配置项
|
|
|
case "title", "description", "keywords", "logo", "favicon", "icp", "domain", "analytics", "theme", "copyright":
|
|
|
data[cfg.Name] = cfg.Value
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bytes, _ := json.Marshal(data)
|
|
|
json.Unmarshal(bytes, &config)
|
|
|
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const (
|
|
|
ConfigSecurityIsClose = "is_close" // 是否关闭注册
|
|
|
ConfigSecurityEnableRegister = "enable_register" // 是否允许注册
|
|
|
ConfigSecurityEnableCaptchaLogin = "enable_captcha_login" // 是否开启登录验证码
|
|
|
ConfigSecurityEnableCaptchaRegister = "enable_captcha_register" // 是否开启注册验证码
|
|
|
ConfigSecurityEnableCaptchaComment = "enable_captcha_comment" // 是否开启注册验证码
|
|
|
ConfigSecurityEnableCaptchaFindPassword = "enable_captcha_find_password" // 是否开启注册验证码
|
|
|
ConfigSecurityEnableCaptchaUpload = "enable_captcha_upload" // 是否开启注册验证码
|
|
|
)
|
|
|
|
|
|
type ConfigSecurity struct {
|
|
|
IsClose bool `json:"is_close"` // 是否闭站
|
|
|
EnableRegister bool `json:"enable_register"` // 是否启用注册
|
|
|
EnableCaptchaLogin bool `json:"enable_captcha_login"` // 是否启用登录验证码
|
|
|
EnableCaptchaRegister bool `json:"enable_captcha_register"` // 是否启用注册验证码
|
|
|
EnableCaptchaComment bool `json:"enable_captcha_comment"` // 是否启用评论验证码
|
|
|
EnableCaptchaFindPassword bool `json:"enable_captcha_find_password"` // 找回密码是否需要验证码
|
|
|
EnableCaptchaUpload bool `json:"enable_captcha_upload"` // 上传文档是否需要验证码
|
|
|
}
|
|
|
|
|
|
// GetConfigOfSecurity 获取安全配置
|
|
|
func (m *DBModel) GetConfigOfSecurity(name ...string) (config ConfigSecurity) {
|
|
|
var configs []Config
|
|
|
db := m.db.Where("category = ?", ConfigCategorySecurity)
|
|
|
if len(name) > 0 {
|
|
|
db = db.Where("name in (?)", name)
|
|
|
}
|
|
|
err := db.Find(&configs).Error
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
|
|
m.logger.Error("GetConfigOfSecurity", zap.Error(err))
|
|
|
}
|
|
|
|
|
|
var data = make(map[string]interface{})
|
|
|
|
|
|
for _, cfg := range configs {
|
|
|
switch cfg.Name {
|
|
|
case "is_close", "enable_register", "enable_captcha_login", "enable_captcha_register", "enable_captcha_comment", "enable_captcha_find_password", "enable_captcha_upload":
|
|
|
value, _ := strconv.ParseBool(cfg.Value)
|
|
|
data[cfg.Name] = value
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bytes, _ := json.Marshal(data)
|
|
|
json.Unmarshal(bytes, &config)
|
|
|
|
|
|
return
|
|
|
}
|
|
|
|
|
|
func (m *DBModel) initConfig() (err error) {
|
|
|
// 初始化配置项
|
|
|
cfgs := []Config{
|
|
|
// 系统配置项
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemTitle, Label: "网站名称", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的名称", InputType: "text", Sort: 1, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemDescription, Label: "网站描述", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的描述", InputType: "text", Sort: 2, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemKeywords, Label: "网站关键字", Value: "MOREDOC · 魔刀文库", Placeholder: "请输入您网站的关键字", InputType: "text", Sort: 3, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemLogo, Label: "网站Logo", Value: "", Placeholder: "请输入您网站的Logo", InputType: "text", Sort: 4, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemFavicon, Label: "网站Favicon", Value: "", Placeholder: "请输入您网站的Favicon", InputType: "text", Sort: 5, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemIcp, Label: "网站备案号", Value: "", Placeholder: "请输入您网站的备案号", InputType: "text", Sort: 6, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemDomain, Label: "网站域名", Value: "", Placeholder: "请输入您网站的域名", InputType: "textarea", Sort: 7, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemAnalytics, Label: "网站统计代码", Value: "", Placeholder: "请输入您网站的统计代码", InputType: "text", Sort: 8, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemTheme, Label: "网站主题", Value: "default", Placeholder: "请输入您网站的主题", InputType: "text", Sort: 9, Options: ""},
|
|
|
{Category: ConfigCategorySystem, Name: ConfigSystemCopyright, Label: "网站版权信息", Value: "", Placeholder: "请输入您网站的版权信息", InputType: "text", Sort: 10, Options: ""},
|
|
|
|
|
|
// 验证码配置项
|
|
|
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaHeight, Label: "验证码高度", Value: "60", Placeholder: "请输入验证码高度,默认为60", InputType: "number", Sort: 13, Options: ""},
|
|
|
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaWidth, Label: "验证码宽度", Value: "240", Placeholder: "请输入验证码宽度,默认为240", InputType: "number", Sort: 14, Options: ""},
|
|
|
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaLength, Label: "验证码长度", Value: "5", Placeholder: "请输入验证码长度,默认为6", InputType: "number", Sort: 15, Options: ""},
|
|
|
{Category: ConfigCategoryCaptcha, Name: ConfigCaptchaType, Label: "验证码类型", Value: "digit", Placeholder: "请选择验证码类型,默认为数字", InputType: "select", Sort: 16, Options: captcha.CaptchaTypeOptions},
|
|
|
|
|
|
// 安全配置项
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityIsClose, Label: "是否关闭网站", Value: "false", Placeholder: "请选择是否关闭网站", InputType: "swith", Sort: 17, Options: ""},
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableRegister, Label: "是否允许注册", Value: "true", Placeholder: "请选择是否允许用户注册", InputType: "swith", Sort: 18, Options: ""},
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaLogin, Label: "是否开启登录验证码", Value: "true", Placeholder: "请选择是否开启登录验证码", InputType: "swith", Sort: 19, Options: ""},
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaRegister, Label: "是否开启注册验证码", Value: "true", Placeholder: "请选择是否开启注册验证码", InputType: "swith", Sort: 20, Options: ""},
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaComment, Label: "是否开启评论验证码", Value: "true", Placeholder: "请选择是否开启评论验证码", InputType: "swith", Sort: 21, Options: ""},
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaFindPassword, Label: "是否开启找回密码验证码", Value: "true", Placeholder: "请选择是否开启找回密码验证码", InputType: "swith", Sort: 22, Options: ""},
|
|
|
{Category: ConfigCategorySecurity, Name: ConfigSecurityEnableCaptchaUpload, Label: "是否开启文档上传验证码", Value: "true", Placeholder: "请选择是否开启文档上传验证码", InputType: "swith", Sort: 23, Options: ""},
|
|
|
}
|
|
|
|
|
|
for _, cfg := range cfgs {
|
|
|
existConfig, _ := m.GetConfigByNameCategory(cfg.Name, cfg.Category, "id")
|
|
|
if existConfig.Id > 0 {
|
|
|
// 更新除了值之外的所有字段
|
|
|
cfg.Id = existConfig.Id
|
|
|
err = m.db.Omit("value").Updates(&cfg).Error
|
|
|
if err != nil {
|
|
|
m.logger.Error("initConfig", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
continue
|
|
|
}
|
|
|
err = m.CreateConfig(&cfg)
|
|
|
if err != nil {
|
|
|
m.logger.Error("initConfig", zap.Error(err))
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
return
|
|
|
}
|