150 lines
4.4 KiB
Go
150 lines
4.4 KiB
Go
|
|
package svc
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"strings"
|
|||
|
|
|
|||
|
|
"apps/backend/internal/config"
|
|||
|
|
"apps/backend/internal/middleware"
|
|||
|
|
fsDomain "apps/backend/internal/module/filestorage/domain"
|
|||
|
|
"apps/backend/internal/module/filestorage/noop"
|
|||
|
|
"apps/backend/internal/module/filestorage/s3store"
|
|||
|
|
memberDomain "apps/backend/internal/module/member/domain"
|
|||
|
|
memberRepo "apps/backend/internal/module/member/repository"
|
|||
|
|
memberUC "apps/backend/internal/module/member/usecase"
|
|||
|
|
notifDomain "apps/backend/internal/module/notification/domain"
|
|||
|
|
notifUC "apps/backend/internal/module/notification/usecase"
|
|||
|
|
tokenUC "apps/backend/internal/module/token/usecase"
|
|||
|
|
|
|||
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|||
|
|
"github.com/zeromicro/go-zero/rest"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// ServiceContext wires goctl middleware + modules.
|
|||
|
|
//
|
|||
|
|
// 持久化(關機不丟):
|
|||
|
|
// - members / settings / refresh_tokens / auth_codes → Mongo
|
|||
|
|
// Redis(可丟、可重建):
|
|||
|
|
// - monc 實體快取 only(CacheRedis)
|
|||
|
|
type ServiceContext struct {
|
|||
|
|
Config config.Config
|
|||
|
|
AuthJWT rest.Middleware
|
|||
|
|
AdminAuth rest.Middleware
|
|||
|
|
Members memberDomain.Repository
|
|||
|
|
Auth *memberUC.AuthService
|
|||
|
|
Token *tokenUC.JWTIssuer
|
|||
|
|
Notification notifDomain.UseCase
|
|||
|
|
Storage fsDomain.Storage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewServiceContext(c config.Config) *ServiceContext {
|
|||
|
|
if len(c.CacheRedis) == 0 {
|
|||
|
|
logx.Must(errString("CacheRedis is required for monc entity cache"))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
repo := memberRepo.NewMoncStore(c.Mongo.URI, c.Mongo.Database, c.CacheRedis)
|
|||
|
|
logx.Infof("mongo monc ready db=%s redis_cache=%s", c.Mongo.Database, c.CacheRedis[0].Host)
|
|||
|
|
|
|||
|
|
issuer := tokenUC.NewJWTIssuer(
|
|||
|
|
c.Auth.AccessSecret,
|
|||
|
|
c.Auth.RefreshSecret,
|
|||
|
|
c.Auth.AccessExpire,
|
|||
|
|
c.Auth.RefreshExpire,
|
|||
|
|
)
|
|||
|
|
cost := c.Bcrypt.Cost
|
|||
|
|
if cost <= 0 {
|
|||
|
|
cost = 10
|
|||
|
|
}
|
|||
|
|
auth := memberUC.NewAccountService(repo, issuer, cost)
|
|||
|
|
auth.GoogleClientID = c.OAuth.GoogleClientID
|
|||
|
|
auth.LineClientID = c.OAuth.LineClientID
|
|||
|
|
auth.LineClientSecret = c.OAuth.LineClientSecret
|
|||
|
|
auth.LineRedirectURI = c.OAuth.LineRedirectURI
|
|||
|
|
|
|||
|
|
notify := newNotification(c)
|
|||
|
|
store := newStorage(c)
|
|||
|
|
|
|||
|
|
return &ServiceContext{
|
|||
|
|
Config: c,
|
|||
|
|
Members: repo,
|
|||
|
|
Auth: auth,
|
|||
|
|
Token: issuer,
|
|||
|
|
Notification: notify,
|
|||
|
|
Storage: store,
|
|||
|
|
AuthJWT: middleware.NewAuthJWTMiddleware(issuer, repo).Handle,
|
|||
|
|
AdminAuth: middleware.NewAdminAuthMiddleware().Handle,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// newStorage 只接 MinIO/S3(S3 API);不落本機磁碟。
|
|||
|
|
func newStorage(c config.Config) fsDomain.Storage {
|
|||
|
|
os := c.ObjectStorage
|
|||
|
|
if strings.TrimSpace(os.Endpoint) == "" {
|
|||
|
|
logx.Error("object storage: Endpoint empty — uploads disabled (MinIO/S3 required)")
|
|||
|
|
return noop.New()
|
|||
|
|
}
|
|||
|
|
s, err := s3store.New(s3store.Config{
|
|||
|
|
Endpoint: os.Endpoint,
|
|||
|
|
Region: os.Region,
|
|||
|
|
Bucket: os.Bucket,
|
|||
|
|
AccessKey: os.AccessKey,
|
|||
|
|
SecretKey: os.SecretKey,
|
|||
|
|
UsePathStyle: os.UsePathStyle,
|
|||
|
|
PublicBaseURL: os.PublicBaseURL,
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
logx.Errorf("object storage MinIO/S3 init failed: %v — uploads disabled", err)
|
|||
|
|
return noop.New()
|
|||
|
|
}
|
|||
|
|
logx.Infof("object storage: minio/s3 endpoint=%s bucket=%s", os.Endpoint, os.Bucket)
|
|||
|
|
return s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func newNotification(c config.Config) notifDomain.UseCase {
|
|||
|
|
base := strings.TrimRight(c.PublicWebBase, "/")
|
|||
|
|
if base == "" {
|
|||
|
|
base = "http://127.0.0.1:5173"
|
|||
|
|
}
|
|||
|
|
brand := notifDomain.Brand{
|
|||
|
|
Name: c.Brand.Name,
|
|||
|
|
Link: base,
|
|||
|
|
LogoURL: c.Brand.LogoURL,
|
|||
|
|
Copyright: c.Brand.Copyright,
|
|||
|
|
}
|
|||
|
|
if brand.Name == "" {
|
|||
|
|
brand.Name = "Harbor Desk"
|
|||
|
|
}
|
|||
|
|
if brand.LogoURL == "" {
|
|||
|
|
// apps/web/public/brand-mark.jpg — 郵件客戶端需絕對 URL
|
|||
|
|
brand.LogoURL = base + "/brand-mark.jpg"
|
|||
|
|
}
|
|||
|
|
if brand.Copyright == "" {
|
|||
|
|
brand.Copyright = "© Harbor Desk"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cfg := notifUC.Config{
|
|||
|
|
Sender: c.Mail.Sender,
|
|||
|
|
DevExposeCode: c.Mail.DevExposeCode,
|
|||
|
|
Brand: brand,
|
|||
|
|
}
|
|||
|
|
if c.Mail.SMTP.Host != "" {
|
|||
|
|
cfg.Providers = []notifDomain.EmailDelivery{
|
|||
|
|
notifUC.NewSMTPDelivery(notifUC.SMTPConfig{
|
|||
|
|
Host: c.Mail.SMTP.Host,
|
|||
|
|
Port: c.Mail.SMTP.Port,
|
|||
|
|
User: c.Mail.SMTP.User,
|
|||
|
|
Password: c.Mail.SMTP.Password,
|
|||
|
|
}),
|
|||
|
|
}
|
|||
|
|
logx.Infof("notification mail: smtp host=%s port=%d logo=%s expose_code=%v",
|
|||
|
|
c.Mail.SMTP.Host, c.Mail.SMTP.Port, brand.LogoURL, c.Mail.DevExposeCode)
|
|||
|
|
} else {
|
|||
|
|
cfg.Providers = []notifDomain.EmailDelivery{notifUC.NewLogDelivery()}
|
|||
|
|
logx.Info("notification mail: log-only (set Mail.SMTP.Host to send real email)")
|
|||
|
|
}
|
|||
|
|
return notifUC.NewService(cfg)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type errString string
|
|||
|
|
|
|||
|
|
func (e errString) Error() string { return string(e) }
|