- main.go/auth_middleware: web 静态目录托管与 history 回退、Chrome DevTools 探测处理 - 新增 scripts/build_web.sh(缓存判断构建 web)与 scripts/dev.sh(一键构建+启动) - 删除已过时的 routes.sh(goframe 自动生成 api.json)与 split_db(数据库已拆分完毕) - docs: 补充联调与测试规范(测试必须用真实用户数据 wenwu901/123456)
104 lines
3.2 KiB
Go
104 lines
3.2 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"os"
|
||
"os/signal"
|
||
"path/filepath"
|
||
"strings"
|
||
"syscall"
|
||
"time"
|
||
|
||
commonHttp "slogan-agent/common"
|
||
|
||
"slogan-agent/styleagent/controller"
|
||
"slogan-agent/styleagent/service"
|
||
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/net/ghttp"
|
||
|
||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||
)
|
||
|
||
func main() {
|
||
// ==================== Web 静态资源(Flutter web 构建产物) ====================
|
||
// 静态目录不存在时优雅跳过(仅 API 模式运行);注册顺序在 Auth 之后,history 回退先经鉴权放行
|
||
if dir := commonHttp.WebStaticDir(); dir != "" {
|
||
commonHttp.Httpserver.SetServerRoot(dir)
|
||
commonHttp.Httpserver.BindMiddlewareDefault(func(r *ghttp.Request) {
|
||
r.Middleware.Next()
|
||
if r.Response.Status == http.StatusNotFound && commonHttp.IsWebHistoryRoute(r.URL.Path) {
|
||
r.Response.ClearBuffer()
|
||
r.Response.ServeFile(filepath.Join(dir, "index.html"))
|
||
}
|
||
})
|
||
|
||
// Chrome DevTools 探测请求:.well-known 协议处理器探测返回 204;
|
||
// flutter.js 尾部保留 sourceMappingURL 注释但 release 构建不生成 map,返回合法空 sourcemap,
|
||
// 避免每次打开 DevTools 都产生 404(覆盖本地直服与 Docker 两种部署)
|
||
commonHttp.Httpserver.BindHandler("/.well-known/appspecific/com.chrome.devtools.json", func(r *ghttp.Request) {
|
||
r.Response.WriteStatus(http.StatusNoContent)
|
||
})
|
||
commonHttp.Httpserver.BindHandler("/flutter.js.map", func(r *ghttp.Request) {
|
||
r.Response.WriteJson(map[string]interface{}{
|
||
"version": 3,
|
||
"sources": []string{},
|
||
"names": []string{},
|
||
"mappings": "",
|
||
})
|
||
})
|
||
}
|
||
|
||
// ==================== API 路由(RouteRegister 反射注册,kebab-case 前缀) ====================
|
||
commonHttp.RouteRegister([]interface{}{
|
||
controller.User,
|
||
controller.UserPhoto,
|
||
controller.Wardrobe,
|
||
controller.BodyMeasurement,
|
||
controller.Avatar,
|
||
controller.Hairstyle,
|
||
controller.Outfit,
|
||
controller.PartnerStore,
|
||
controller.Member,
|
||
controller.Ad,
|
||
controller.Cps,
|
||
})
|
||
|
||
// 虎皮棋支付回调(裸文本 "success",不走统一 JSON 包装)
|
||
commonHttp.Httpserver.Group("/member/order", func(group *ghttp.RouterGroup) {
|
||
group.POST("/notify", controller.MemberNotify)
|
||
})
|
||
|
||
// ==================== Workspace 文件服务(鉴权保护) ====================
|
||
commonHttp.Httpserver.BindHandler("/workspace/*", func(r *ghttp.Request) {
|
||
relPath := strings.TrimPrefix(r.URL.Path, "/workspace/")
|
||
if relPath == "" || strings.Contains(relPath, "..") {
|
||
r.Response.WriteStatus(http.StatusForbidden)
|
||
return
|
||
}
|
||
filePath := filepath.Join("workspace", relPath)
|
||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||
r.Response.WriteStatus(http.StatusNotFound)
|
||
return
|
||
}
|
||
r.Response.ServeFile(filePath)
|
||
})
|
||
|
||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||
defer stop()
|
||
|
||
// 恢复未完成的生成任务(重启后标记失败,避免重复消耗 LLM 费用)
|
||
service.OutfitService.StartWorker(ctx)
|
||
|
||
// CPS 联盟商品定时同步(未配置 key 时空转)
|
||
service.CpsProductService.StartSyncLoop(ctx)
|
||
|
||
g.Log().Info(ctx, "slogan-agent started on :3007")
|
||
|
||
<-ctx.Done()
|
||
g.Log().Info(ctx, "shutting down...")
|
||
time.Sleep(1 * time.Second)
|
||
g.Log().Info(ctx, "bye")
|
||
}
|