117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
|
|
package app
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"git.apinb.com/ops/license/internal/config"
|
||
|
|
"git.apinb.com/ops/license/internal/dashboard"
|
||
|
|
"git.apinb.com/ops/license/internal/database"
|
||
|
|
"git.apinb.com/ops/license/internal/httpapi"
|
||
|
|
"git.apinb.com/ops/license/internal/issuance"
|
||
|
|
"git.apinb.com/ops/license/internal/signing"
|
||
|
|
"git.apinb.com/ops/license/internal/subject"
|
||
|
|
"git.apinb.com/ops/license/internal/webui"
|
||
|
|
)
|
||
|
|
|
||
|
|
const shutdownTimeout = 10 * time.Second
|
||
|
|
|
||
|
|
// Run 装配并运行许可证管理服务,直到上下文被取消。
|
||
|
|
func Run(ctx context.Context, cfg config.Config) (resultErr error) {
|
||
|
|
if ctx == nil {
|
||
|
|
return errors.New("上下文不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
db, err := database.Open(ctx, cfg.DatabaseDSN)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("打开数据库: %w", err)
|
||
|
|
}
|
||
|
|
sqlDB, err := db.DB()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("获取数据库连接池: %w", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
resultErr = errors.Join(resultErr, sqlDB.Close())
|
||
|
|
}()
|
||
|
|
|
||
|
|
if err := database.Migrate(ctx, db); err != nil {
|
||
|
|
return fmt.Errorf("执行数据库迁移: %w", err)
|
||
|
|
}
|
||
|
|
activeSigner, err := signing.LoadActive(cfg.SigningPrivateKeyFile, cfg.SigningCertificateFile)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
registry, err := signing.NewRegistry(db)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := registry.Activate(ctx, activeSigner); err != nil {
|
||
|
|
return fmt.Errorf("激活当前签发密钥: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
subjectRepository, err := subject.NewRepository(db)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
subjectService, err := subject.NewService(subjectRepository)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
issuanceRepository, err := issuance.NewRepository(db)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
fileSigner, err := issuance.NewFileSigner(activeSigner)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
issuanceService, err := issuance.NewService(issuanceRepository, subjectRepository, fileSigner)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
dashboardService, err := dashboard.NewService(db)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
apiRouter, err := httpapi.NewRouter(subjectService, issuanceService, dashboardService)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
handler := http.NewServeMux()
|
||
|
|
handler.Handle("/api/", apiRouter)
|
||
|
|
handler.Handle("/", webui.Handler())
|
||
|
|
server := &http.Server{
|
||
|
|
Addr: cfg.ListenAddr,
|
||
|
|
Handler: handler,
|
||
|
|
ReadHeaderTimeout: 10 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
|
serveResult := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
serveResult <- server.ListenAndServe()
|
||
|
|
}()
|
||
|
|
|
||
|
|
select {
|
||
|
|
case err := <-serveResult:
|
||
|
|
if errors.Is(err, http.ErrServerClosed) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return fmt.Errorf("运行 HTTP 服务: %w", err)
|
||
|
|
case <-ctx.Done():
|
||
|
|
shutdownContext, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||
|
|
defer cancel()
|
||
|
|
if err := server.Shutdown(shutdownContext); err != nil {
|
||
|
|
return fmt.Errorf("关闭 HTTP 服务: %w", err)
|
||
|
|
}
|
||
|
|
serveErr := <-serveResult
|
||
|
|
if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
|
||
|
|
return fmt.Errorf("关闭 HTTP 服务: %w", serveErr)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|