Support personal records and persistent smoking visibility. Add upload quota preflight, file cleanup, privacy entry, image caching and revision-based page refresh across the backend, admin and mini program.
169 lines
5.2 KiB
Go
169 lines
5.2 KiB
Go
// Package config 读取服务配置(对应 PHP 的 config.php + config.credentials.php)。
|
||
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
type RedisConfig struct {
|
||
Addr string `yaml:"addr"`
|
||
Username string `yaml:"username"`
|
||
Password string `yaml:"password"`
|
||
DB int `yaml:"db"`
|
||
KeyPrefix string `yaml:"key_prefix"`
|
||
}
|
||
|
||
type LogConfig struct {
|
||
Level string `yaml:"level"`
|
||
Console bool `yaml:"console"`
|
||
RetentionDays int `yaml:"retention_days"`
|
||
MaxSizeMB int `yaml:"max_size_mb"`
|
||
MaxBackups int `yaml:"max_backups"`
|
||
}
|
||
|
||
type MinioConfig struct {
|
||
Endpoint string `yaml:"endpoint"`
|
||
AccessKey string `yaml:"access_key"`
|
||
SecretKey string `yaml:"secret_key"`
|
||
Bucket string `yaml:"bucket"`
|
||
UseSSL bool `yaml:"use_ssl"`
|
||
}
|
||
|
||
type WechatPayConfig struct {
|
||
Enabled bool `yaml:"enabled"`
|
||
AppID string `yaml:"appid"`
|
||
MchID string `yaml:"mch_id"`
|
||
CertificateSerial string `yaml:"certificate_serial"`
|
||
CertificatePath string `yaml:"certificate_path"`
|
||
APIv3Key string `yaml:"api_v3_key"`
|
||
PrivateKeyPath string `yaml:"private_key_path"`
|
||
PublicKeyID string `yaml:"public_key_id"`
|
||
PublicKeyPath string `yaml:"public_key_path"`
|
||
NotifyURL string `yaml:"notify_url"`
|
||
}
|
||
|
||
func (c WechatPayConfig) ValidateNotifyURL() error {
|
||
u, err := url.Parse(c.NotifyURL)
|
||
if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" {
|
||
return fmt.Errorf("微信支付 notify_url 必须为公网 HTTPS 地址,且不含查询参数")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type Config struct {
|
||
Redis RedisConfig `yaml:"redis"`
|
||
Log LogConfig `yaml:"log"`
|
||
WechatPay WechatPayConfig `yaml:"wechat_pay"`
|
||
Minio MinioConfig `yaml:"minio"`
|
||
|
||
Listen string `yaml:"listen"` // 监听地址,如 ":8080"
|
||
Debug bool `yaml:"debug"` // gin debug 模式(打印路由等)
|
||
|
||
// DataDir 运行目录:log/ 存日志,data/tmp/ 存临时文件;文件存储在 MinIO。
|
||
// 对应 PHP 的 backend/ 目录;默认为可执行文件所在目录。
|
||
DataDir string `yaml:"data_dir"`
|
||
|
||
DB struct {
|
||
Host string `yaml:"host"`
|
||
Port int `yaml:"port"`
|
||
Name string `yaml:"name"`
|
||
User string `yaml:"user"`
|
||
Pass string `yaml:"pass"`
|
||
SSLMode string `yaml:"sslmode"`
|
||
} `yaml:"db"`
|
||
|
||
Wechat struct {
|
||
AppID string `yaml:"appid"`
|
||
Secret string `yaml:"secret"`
|
||
// 订阅消息模板 ID(对应 WECHAT_SUBSCRIBE_TEMPLATES['notify'])
|
||
NotifyTemplateID string `yaml:"notify_template_id"`
|
||
} `yaml:"wechat"`
|
||
|
||
TencentMapKey string `yaml:"tencent_map_key"`
|
||
|
||
// PHPCompat 对齐的 PHP 版本:"8.0"(默认,数据库数值按字符串返回)或 "8.1"(原生类型)
|
||
PHPCompat string `yaml:"php_compat"`
|
||
|
||
// MaxUploadMB 单文件上传上限(PHP: MAX_UPLOAD_SIZE = 50MB)
|
||
MaxUploadMB int64 `yaml:"max_upload_mb"`
|
||
|
||
// Notify 定时补发通知(对应 cron_notify.php)的执行间隔(分钟),0 表示关闭
|
||
NotifyIntervalMin int `yaml:"notify_interval_min"`
|
||
}
|
||
|
||
func Load(path string) (*Config, error) {
|
||
c := &Config{}
|
||
c.Redis = RedisConfig{Addr: "127.0.0.1:6379", KeyPrefix: "past-go:"}
|
||
c.Log = LogConfig{Level: "info", Console: true, RetentionDays: 30, MaxSizeMB: 100, MaxBackups: 10}
|
||
c.Listen = ":8080"
|
||
c.DB.Host = "192.168.100.127"
|
||
c.DB.Port = 5432
|
||
c.DB.Name = "past-go"
|
||
c.DB.User = "root"
|
||
c.DB.Pass = "root"
|
||
c.DB.SSLMode = "disable"
|
||
c.MaxUploadMB = 50
|
||
c.NotifyIntervalMin = 5
|
||
c.Wechat.NotifyTemplateID = "K_iQMwE_jre0j6QDVL78DZz7wiR1SKVY5h2YJCK6uPU"
|
||
c.PHPCompat = "8.0"
|
||
|
||
b, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取配置文件 %s: %w", path, err)
|
||
}
|
||
if err := yaml.Unmarshal(b, c); err != nil {
|
||
return nil, fmt.Errorf("解析配置文件 %s: %w", path, err)
|
||
}
|
||
if c.WechatPay.AppID == "" {
|
||
c.WechatPay.AppID = c.Wechat.AppID
|
||
}
|
||
if c.WechatPay.Enabled && c.WechatPay.AppID != c.Wechat.AppID {
|
||
return nil, fmt.Errorf("微信支付 AppID 必须与微信登录 AppID 一致")
|
||
}
|
||
// Certificate paths are relative to the selected YAML file, independent of cwd.
|
||
for _, p := range []*string{&c.WechatPay.PrivateKeyPath, &c.WechatPay.PublicKeyPath, &c.WechatPay.CertificatePath} {
|
||
if *p != "" && !filepath.IsAbs(*p) {
|
||
*p = filepath.Join(filepath.Dir(path), *p)
|
||
}
|
||
}
|
||
if c.DataDir == "" {
|
||
exe, err := os.Executable()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
c.DataDir = filepath.Dir(exe)
|
||
}
|
||
abs, err := filepath.Abs(c.DataDir)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
c.DataDir = abs
|
||
return c, nil
|
||
}
|
||
|
||
func (c *Config) UploadDir() string { return filepath.Join(c.DataDir, "uploads") }
|
||
func (c *Config) LogDir() string { return filepath.Join(c.DataDir, "log") }
|
||
|
||
// DSN uses URL escaping for database names, passwords and IPv6 hosts.
|
||
func (c *Config) DSN() string {
|
||
u := &url.URL{Scheme: "postgres", User: url.UserPassword(c.DB.User, c.DB.Pass), Host: net.JoinHostPort(c.DB.Host, strconv.Itoa(c.DB.Port)), Path: "/" + c.DB.Name}
|
||
q := url.Values{}
|
||
mode := c.DB.SSLMode
|
||
if mode == "" {
|
||
mode = "disable"
|
||
}
|
||
q.Set("sslmode", mode)
|
||
q.Set("TimeZone", "Asia/Shanghai")
|
||
q.Set("connect_timeout", "10")
|
||
u.RawQuery = strings.ReplaceAll(q.Encode(), "%2F", "/")
|
||
return u.String()
|
||
}
|