Files
voicepaste/main.go
imbytecat b87fead2fd refactor: 优化代码质量,遵循 KISS 原则
- 移除自签证书回退逻辑,简化为仅使用 AnyIP 证书
- 删除 internal/tls/generate.go(不再需要)
- 重构 main.go:提取初始化逻辑,main() 从 156 行降至 13 行
- 重构 internal/ws/handler.go:提取消息处理,handleConn() 从 131 行降至 25 行
- 重构 internal/config/load.go:使用 map 驱动消除重复代码
- 优化前端 startRecording():使用标准 AbortController API
- 优化前端 showToast():预定义 DOM 元素,代码减少 50%

代码行数减少 90 行,复杂度显著降低,所有构建通过
2026-03-02 00:25:14 +08:00

186 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
crypto_tls "crypto/tls"
"embed"
"fmt"
"io/fs"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/imbytecat/voicepaste/internal/asr"
"github.com/imbytecat/voicepaste/internal/config"
"github.com/imbytecat/voicepaste/internal/paste"
"github.com/imbytecat/voicepaste/internal/server"
vpTLS "github.com/imbytecat/voicepaste/internal/tls"
"github.com/imbytecat/voicepaste/internal/ws"
)
//go:embed all:web/dist
var webFS embed.FS
var version = "dev"
func main() {
initLogger()
slog.Info("VoicePaste", "version", version)
cfg := mustLoadConfig()
config.WatchAndReload("")
initClipboard()
lanIPs := mustDetectLANIPs()
lanIP := lanIPs[0]
tlsResult, scheme := setupTLS(cfg, lanIP)
printBanner(cfg, tlsResult, lanIPs, scheme)
srv := createServer(cfg, lanIP, tlsResult)
runWithGracefulShutdown(srv)
}
func initLogger() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
})))
}
func mustLoadConfig() config.Config {
cfg, err := config.Load("")
if err != nil {
slog.Error("failed to load config", "error", err)
os.Exit(1)
}
return cfg
}
func initClipboard() {
if err := paste.Init(); err != nil {
slog.Warn("clipboard init failed, paste will be unavailable", "err", err)
}
}
func mustDetectLANIPs() []string {
lanIPs, err := server.GetLANIPs()
if err != nil {
slog.Error("failed to detect LAN IP", "error", err)
os.Exit(1)
}
return lanIPs
}
func setupTLS(cfg config.Config, lanIP string) (*vpTLS.Result, string) {
if !cfg.Server.TLSAuto {
return nil, "http"
}
tlsResult, err := vpTLS.GetTLSConfig(lanIP)
if err != nil {
slog.Error("TLS setup failed", "error", err)
os.Exit(1)
}
return tlsResult, "https"
}
func printBanner(cfg config.Config, tlsResult *vpTLS.Result, lanIPs []string, scheme string) {
fmt.Println()
fmt.Println("╔══════════════════════════════════════╗")
fmt.Println("║ VoicePaste 就绪 ║")
fmt.Println("╚══════════════════════════════════════╝")
fmt.Println()
printAddresses(cfg, tlsResult, lanIPs, scheme)
printCertInfo(tlsResult, cfg.Server.TLSAuto)
printAuthInfo(cfg.Security.Token)
fmt.Println()
fmt.Println(" 在手机浏览器中打开上方地址")
fmt.Println(" 按 Ctrl+C 停止服务")
fmt.Println()
}
func printAddresses(cfg config.Config, tlsResult *vpTLS.Result, lanIPs []string, scheme string) {
token := cfg.Security.Token
if len(lanIPs) == 1 {
host := lanIP(tlsResult, lanIPs[0])
fmt.Printf(" 地址: %s\n", buildURL(scheme, host, cfg.Server.Port, token))
return
}
fmt.Println(" 地址:")
for _, ip := range lanIPs {
host := lanIP(tlsResult, ip)
fmt.Printf(" - %s\n", buildURL(scheme, host, cfg.Server.Port, token))
}
}
func lanIP(tlsResult *vpTLS.Result, ip string) string {
if tlsResult != nil {
return vpTLS.AnyIPHost(ip)
}
return ip
}
func printCertInfo(tlsResult *vpTLS.Result, tlsAuto bool) {
if tlsResult != nil {
fmt.Println(" 证书: AnyIP浏览器信任")
} else if tlsAuto {
fmt.Println(" 证书: 配置错误TLS 启用但未获取证书)")
}
}
func printAuthInfo(token string) {
if token != "" {
fmt.Println(" 认证: 已启用")
} else {
fmt.Println(" 认证: 未启用(无需 token")
}
}
func createServer(cfg config.Config, lanIP string, tlsResult *vpTLS.Result) *server.Server {
webContent, _ := fs.Sub(webFS, "web/dist")
var tlsConfig *crypto_tls.Config
if tlsResult != nil {
tlsConfig = tlsResult.Config
}
srv := server.New(cfg.Security.Token, lanIP, webContent, tlsConfig)
asrFactory := buildASRFactory(cfg)
wsHandler := ws.NewHandler(cfg.Security.Token, paste.Paste, asrFactory)
wsHandler.Register(srv.App())
return srv
}
func buildASRFactory(cfg config.Config) func(chan<- ws.ServerMsg) (func([]byte), func(), error) {
asrCfg := asr.Config{
AppID: cfg.Doubao.AppID,
AccessToken: cfg.Doubao.AccessToken,
ResourceID: cfg.Doubao.ResourceID,
}
return func(resultCh chan<- ws.ServerMsg) (func([]byte), func(), error) {
client, err := asr.Dial(asrCfg, resultCh)
if err != nil {
return nil, nil, err
}
sendAudio := func(pcm []byte) {
if err := client.SendAudio(pcm, false); err != nil {
slog.Warn("send audio to asr", "err", err)
}
}
cleanup := func() {
client.Finish()
}
return sendAudio, cleanup, nil
}
}
func runWithGracefulShutdown(srv *server.Server) {
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
slog.Info("shutting down...")
srv.Shutdown()
}()
if err := srv.Start(); err != nil {
slog.Error("server error", "error", err)
os.Exit(1)
}
}
func buildURL(scheme, host string, port int, token string) string {
if token != "" {
return fmt.Sprintf("%s://%s:%d/?token=%s", scheme, host, port, token)
}
return fmt.Sprintf("%s://%s:%d/", scheme, host, port)
}